A fundamental concept to achieving real time collaboration is to allow files to be loaded by any number of users but only allow one user to modify the contents of the file at one time.
The files loaded in the project but not obtained edit access is said to be in “read-only” for that user and is managed by Collaboration.
To ensure users are not able to modify contents of a read-only file, various UI elements, animations and tools have been enhanced to respect the read-only state of Snappers, Spaces, Papers, Data etc. These enhancements may or may not have affected custom animations and tools in various Extensions. However, it is expected for all Extensions to migrate accordingly to ensure compatibility with the Collaboration working model.
This article covers how the various elements of CET are modified to be compatible with Collaboration read-only. Refer to them on how to implement the same for the custom elements in each Extension.
Read-Only Snappers
Snappers belonging to a read-only Section file will have its readOnly
prop set to true. To check whether a Snapper is read-only, access the Snapper’s readOnly
method. (cm/core/snapper.cm)
public class Snapper extends Entity { ...
/** * Read only. */ final public bool readOnly() { return holder.readOnly(); } /** * Set read only. */ final public void setReadOnly() { holder.setReadOnly(); } /** * Remove read only. */ final public void removeReadOnly() { holder.removeReadOnly(); } /** * Read only changed. */ extend public void readOnlyChanged(bool oldV, bool newV) { } ... }
Using the SnapperFilter in Animations
Animations that modify properties of Snappers should exclude read-only Snappers. Since most animations use a candidate filter, adding the editableHolderSnapperFilter
to the filter combination will correctly exclude read-only Snapper in the drawing space.
For example, Animations that extend from AlignAnimationG2
handle for this by including the editableHolderSnapperFilter
in the candidateFilter()
method (cm/core/alignAnimationG2.cm)
public class AlignAnimationG2 extends ToolAnimationG2 : abstract { ... /** * Candidate filter. */ public SnapperFilter candidateFilter() { CombinedFilter filter(editableHolderSnapperFilter, super()); return filter; } ... }
Similarly, editableHolderSnapperFilter
should be included for Animations that use FeatureSearch
. E.g., for PlaceAnimation
(cm/core/PlaceAnimation.cm)
public class PlaceAnimation extends BoundTool3DAnimation { ... extend public void moveInState0(AnimationMouseInfo mi) { ... SnapperFilter filter = CombinedFilter(movableSnapperFilter, editableHolderSnapperFilter); if (candidate and animationProperties.bool("lc")) { filter = CombinedFilter(filter, IncludeSnappersFilter([candidate])); } FeatureSearch fs(mi.currentView, mi.currentLine, {feature.end, feature.mid, feature.intersection}, snapperFilter=filter); ... } ... }
Excluding Read-Only Snappers for a Set
Tools, such as FIKA Panel Manager, enable the user to modify/remove multiple Snappers in space. For such tools, it is required that they exclude read-only Snappers accordingly.
In the following example, FIKA Panel Manager (its dialog extends from OfficeSnapperManagerDialog
), the read-only Snappers are subtracted from the set of Snappers using the editableSnappers
helper function, before the apply()
is performed. (cm/abstract/office/officeSnapperManagerDialog.cm)
public class OfficeSnapperManagerDialog extends DialogWindow : abstract { ... /** * Apply * Transforms main space snappers into copies of preview snappers. */ extend public void apply() { if (manager) if (previewSnapper) { OfficeSnapperManagerApplyFilter applyFilter = manager.currentApplyFilter; str typeKey = toTypeDD ? toTypeDD.anySelectedKey : manager.noTypeChosenKey; if (mainSpace) { Snapper{} snappers = mainSpace.snappers.editableSnappers(); // Excluding read-only snappers here snappers = applyFilter.allAccepted(snappers, this); snappers = manager.filteredByType(snappers, typeKey); if (snappers and snappers.count > 0) { performApply(snappers); } } } ... } }
Quick-Properties for Read-Only Snappers
Quick properties (both G1 and G2) is a commonly used interface for users to modify the Snapper's properties. For read-only Snappers, quick properties will still show (including the property values), however they will no longer be enabled for the users to interface. I.e. all controls of the quick properties will be disabled.
If there's a case where some Snapper properties and its QP control should be still allowed for the user to use despite the Snapper being read-only, the disableQuickPropertyOnReadOnlyG2
from the Snapper class may be overridden and return false.
public class Snapper extends Entity { ... /** * Disable quick property for read only sections? */ extend public bool disableQuickPropertyOnReadOnlyG2(Object property) { // QuickProperties G1 -> QuickProperty // QuickProperties G2 -> AbstractPropertyFactory return true; } }
Right-Click Menu Items and Double-Click Actions
If a custom Snapper
- appends custom menu popup items on user right-click
- performs an action/operation on user double-click
evaluate those items and actions and determine where they should be blocked or disabled if the Snapper is read-only. Simply checking the Snapper's readOnly
method should be enough in most cases.
Read-Only Spaces
Like Snappers, Spaces, particularly BlockSpaces and PaperSpaces are also subject to “read-only” rules when the owning project file is read-only in Collaboration.
Spaces have a method readOnlyG2
that can be checked whether it is read-only in the context of Collaboration G3 (and G2, both currently share the same method).
public class Space { ... /** * Get collaboration readOnly status. */ extend public bool readOnlyG2() { if (isInCollWorldG3) { return collIsSpaceReadOnlyG3(this); } ... return false; } }
Look out for miscellaneous UI elements in each Extension that expose the Space to be modified by the user and check for the Space's readOnlyG2
value. For example, in Paper View, some buttons and fields are disabled with selected PaperSpace is read-only.
Animations in Read-Only Space
Most Animations enable users to modify Spaces, e.g., by inserting/modifying/removing Snappers. Each animation that performs these operations often has customized steps to do so (e.g., with states or Vessels).
In a broad stroke approach to restrict modifications to read-only Spaces, most of the user input events (mouse or keyboard) are blocked out in read-only Spaces. This is done through the method allowInput
in Animation. (cm/core/animation.cm)
public class Animation extends CoreObject { ... /** * Allow an input from a window view? */ extend public bool allowInput(WindowView view) { Space space = view.space; return space and (!space.readOnlyG2 or allowInputIfReadOnly); } ... }
Input events that are not permitted in read-only Space include:
- Mouse Click, Release
- Mouse Click2, Release2
- Mouse Drag
- Right and middle mouse button operations
- Enter key, Space key
- Etc.
Some input events are excluded from this filter because they typically provide navigational control to the user instead of operational, such as:
- Cursor move
- Hover
- Mouse wheel rotate
- QP-navigation keys e.g., the Tab key
All animations are subject to this behavior for read-only Space, except a few where input events may be permitted. These are animations that don't expose the Space for modification by the user, such as Move3DAnimation
or SelectRect3DAnimation
. Animations like this may simply override the allowInputIfReadOnly
and return true. The AnimationInputController
would receive all input events as normal.
public class Animation extends CoreObject { ... /** * Allow input if space is read only. */ extend public bool allowInputIfReadOnly() { return false; } ... }
Custom Animations that wish to permit only some of the blocked input events for read-only Space can follow similarly by returning true for allowInputIfReadOnly
. Subsequently, the Space's readOnlyG2()
method should be checked and accounted for explicitly in the Animation
or the AnimationInputController
to ensure the Space can't be modified the user.
Read-Only Calculation Data
Calculation shares the same read-only state as the Scenario that it is a part of. As a result, the calculation view must be enhanced to block off user modifications if the Scenario is in read-only.
The following interfaces were introduced for this purpose.
// In Space (cm/core/space.cm) extend public bool isCollCalcReadOnlyG3() // In CalculationView (cm/core/calc/calculationView.cm) extend public bool readOnly() // In BasicPartColumn (cm/core/part/basicPartColumn.cm) extend public bool readOnly(Part part, Space space) extend public bool readOnly(PartListRow row, Space space)
Making Customs Columns/Cells Read-Only
Many calculation columns/cells present an input interface for the user to modify the calculation (or related info) of the drawing. However, those interfaces may be required to be blocked if the CalculationView
is readOnly.
E.g. For the CalcDoubleInputCell
, the "double-click to edit" action is blocked if the view/column is read-only. (cm/core/calc/cells/calcDoubleInputCell.cm)
public class CalcDoubleInputCell extends DoubleInputGridCell { //... /** * Show cell popup. */ final private void showCellPopup(GridWindow gw) { if (column.isAdjusted(part, space) and !column.readOnly(part, space)) { if (gw and gw.valid) { cellPopup = CalcGridCellPopup(gw, column, part, space, gw.cellBound(gw.currentC, gw.currentR)); } } } //... /** * Open this cell for editing, return true if done. */ public bool open(GridWindow gw) { // Make sure we have the right value if (part) { if (Double d = column.actualValue(part, space).Double) this.v = d.v; } removeCellPopup(gw); if (column.readOnly(part, space)) return false; //... } //... }
Comments
0 comments
Please sign in to leave a comment.