Overview
Collaboration G3 (known as CollabPro in the marketplace) introduces a new working model to CET Designer which is conceptually different from the regular use of CET Designer to create and manage drawings. Collaboration G3 is expected to support all customer extensions, however there may be some programming required from each extension to ensure that it is fully compliant to the rules and concepts introduced by this Collaboration G3 working model.
This wiki highlights the differences and new concepts in Collaboration G3 for developers to take note of and provide a guideline on how to accommodate for them in their extensions. Section summary as follows.
Part A: Introduction to Collaboration working model
This section highlights the differences in a user's work flow using CET in Collaboration as well as how various parts of CET drawings is managed.
Part B: World in Collaboration
This section describes how the World object is handled and used in a multiple-user environment. There are some points to take note when trying to migrate extensions to work correctly in Collaboration
Part C: Assigning section IDs
This section explains how sectionID is assigned. Explicit assignment may be necessary in custom extensions: you will find the interfaces to use as well as examples on when and how to do so.
Part D: Read-only
Collaboration multi-user environment requires user conflicts to be avoided. This section describes how this is achieved and extensions may require some migration to ensure compliance with Collaboration.
Part E: Marking files as modified
This section describes how modifications to files are detected and notified to the user. Refer to this section on ways to ensure this feedback is consistent in each extension.
Part F: Miscellaneous
This section covers other miscellaneous items.
Part A: Introduction to Collaboration
In the normal CET working model, a user creates and makes changes to a CET Drawing. A drawing contains spaces, snappers, and papers. Other elements in the drawing is the calculation (prices and article views), part tags and categories information. The entire drawing is saved as a single entity in a drawing file (.cmdrw).
In the Collaboration working model, users create, connect to and work on a Collaboration Project. Instead of a single storage entity, a CET Drawing is saved as a set of specialized project files which are synchronized online. These files would need to be loaded to a local workspace to allow a user to work on them. Any changes to these files can then be submitted to the cloud.
As Collaboration presents a multi-user working environment, the conflict management approach taken is to make each project file editable by only one user at a time. Each user may obtain an edit access (edit lock) of a project file, and other users will not be able to modify the contents until lock is released.
Drawing Space
The snappers on main drawing space is divided into Sections. Each Section is a project file that contains information of snappers and their positions in the drawing space. Each snapper on the main space exclusively belong on one Section.
Alternatives in Collaboration
In a Collaboration project, the Alternatives feature is not supported. Users will be not allowed to create Alternatives. There will be only a single drawing space in the World. Users may instead duplicate a section to create variations.
Blocks in Collaboration
Unlike the main drawing Space, the Block Space sub snappers are not divided into different sections. A BlockSpace will wholly be part of one Section, or a Shared Block (project file). The block’s owners (BlockSnapper) in the drawing’s main space may belong to different sections as with normal snappers.
Papers
Papers (instances of PaperSpace) are grouped, where each Paper Group (project file) may contain one or more PaperSpace instances.
Scenarios
Scenarios hold a collection of project files. Loading a Scenario will automatically load its project files too. A project file can be part of multiple Scenarios at a time. Preset calculations can exist only as part of a Scenario.
Part B: World in Collaboration
One of fundamental differences between the normal CET working model and the Collaboration working model is how the World (cm/core/world.cm) is being used.
In the normal CET working model, spaces, snappers, papers and miscellaneous data gets stored in the World, and the World in its entirety is streamed to a .cmdrw file. The .cmdrw file can be used to reopen the drawing or shared to other users.
In the Collaboration working model, users create, connect to and work on a Collaboration Project, which is accessible via the server.
The World in not stored in the server, instead an empty world will be instantiated every time a user connects to a Project. What is stored in the server are project files and a database that ledges those files and versions. When a user loads a project file, the file will be downloaded from the server to a local workspace and subsequently the contents are loaded into the drawing (World).
Important points to take note of:
- Anything streamed with the World but not part of any project files will not be available to other users in the project. (E.g. data stored in the World auxiliary.)
- Reopening the same project on the same machine may have the auxiliary data retained, but this is due to caching of the project locally, not necessarily consistent across the Collaboration working model with multiple users/machines.
- The World auxiliary should be used as a temporary cache rather than persistent storage.
- An alternative for sharing data (e.g. Objects) is to derive from CollDataFileType, which is an abstract class for storing generic data that is formatted as a project file. Being a project file, this enables it to be shared in the project, similar to a file attachment.
Part C: Assigning section IDs
With Collaboration, when snappers are added, they will be assigned a Section ID via a CollInfo instance (Snapper._collInfo._sectionId). This identifies the specific project file that the snapper belongs to, while the type of CollInfo determines the Collaboration type that the project is in (e.g. CollInfoG3 for Collaboration G3).
Snappers are assigned with IDs automatically when they are inserted into space, i.e. assigned with the active section. The user may create new sections and select the section they want to be active. Any snapper they inserted from the toolbox into the drawing space will be assigned with the ID of the active section.
The assignSectionIdG3 method in Entity (cm/core/entity.cm) is called to get a sectionID assignment from Collaboration. This also ensures the correct CollInfo type is used.
public class Entity extends CoreObject : modify notice, abstract { ... /** * Set section Id G3 */ extend public void assignSectionIdG3(Space space, symbol id=null) { if (space and assignCollSectionIdCBG3) assignCollSectionIdCBG3(this, ..); } ... }
Automatic assignment happens when the snapper is inserted into Space or changed to Space.
public class Snapper extends Entity { ... /** * This snapper is inserted into 'space'. */ extend public void inserted(Space space) { ... if (!undoRestoreInProgress) assignSectionIdG3(space); if (space.isInCollWorldG3) { assignSectionIdG3(space); ... } /** * This snapper has changed space to space 'to'. */ extend public void changeToSpace(Space to) { ... if (!undoRestoreInProgress) assignSectionIdG3(space); if (to.isInCollWorldG3) { assignSectionIdG3(to); ... } ... } } }
In most cases, the automatic assignment is sufficient and taken care of, however there are cases where explicit assignment is required. This can be done by passing the non-null id parameter into the same assignSectionIdG3 method. (Refer to the example below Block Dialog Explode)
It is also important not to assign chunkID (used in Collaboration G1) as this may block CollInfoG3 (or CollSpaceInfoG3) from being assigned. This will prevent snappers from being attached to the correct section.
Example: Block Dialog Explode
Similar to the previous example, when the user explodes a block from the Block Dialog, they would expect the sub-snappers remain in the respective sections as owning BlockSnapper. This is also achieved by calling assignSectionIdG3 with the ID parameter. (cm/core/block/blockFuns.cm)
/** * Explode blocks. */ public SnapperSelection explodeBlockSnappers(Snapper{} blocks, bool unfreeze=false, bool selectUnfrozen=true, bool snapAligned=true) { //... for (z in sset) { // 'z' is BlockSnapper to be exploded Snapper{} snappers = copySnappers(blockSpace.snappers); ... for (s in snappers) if (s !in FloorSnapper) { ... space.undoableInsert(s, putInBsp=true); if (z.sectionId) { s.assignSectionIdG3(space, z.sectionIdG3); } ... } ... } }
Part D: Read-only
Another 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 edit access is managed by Collaboration. For those files loaded in the project but the user has not obtained edit access, the file is said to be in “read-only” for that user.
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 extension. However, it is expected for all extensions to migrate accordingly to ensure compatibility with the Collaboration working model.
The following subsections cover how the various elements of CET is modified to be compatible with Collaboration read-only. Refer to them on how to implement the same for the custom elements in the 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, so 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 to 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 user's 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 it's 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 overriden and return false.
Note: This is a common interface for both G1 and G2 properties. If the return value should be conditional to the property key, cast the property argument accordingly and access the respective key field. (cm/core/snapper.cm)
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; } }
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
Similar to 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 the 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 is 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 has to be enhanced in order to block off user modifications if the Scenario is in read-only.
The following interfaces where 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)
Important note: In the efforts to make Calculation read-only capable, many of the UI elements (dialog, settings, buttons etc) in Calculation have been enhanced to be disabled whilst in read-only state. Take note of what users can or cannot do when they don't have the Scenario (and by extension the Calculation) in edit mode.
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; //... } //... }
Part E: Marking files as 'modified'
In the normal CET working model, users work on the drawing (World) as a whole. Whenever the user makes changes to the drawing, the modified flag in the World is set. The flag is reset after the World have been saved (to cmdrw file).
For the working model in Collaboration G3, users work on a subset of the project files at a time. And changes are made to the project files independently. As a result, each project file has a modified flag and this flag will be set when Collaboration detects that a change to that file have been made that file. The equivalent of saving those changes is submitting the file. This is when the changes have been committed to the project and flag is reset.
Marking the files as modified correctly is important as it:
- Provides users with indication of which files have been changed
- Warns the users of potential loss of work if they attempted to close the file or project without submitting the changes
Typical examples of when a file is marked as modified:
- When snappers are added to the drawing (assigned to active section), the active section file is marked as modified
- When snappers in the drawing are removed/modified, the corresponding section file will be marked as modified.
- When the section file have been renamed
- When papers have been added/removed from a paper group, the paper group file will be marked as modified
- When snappers have been added to the Paper Space, the owning paper group will be marked as modified.
- When the orientation/resolution of the Paper Space have been modified, the owning paper group is marked as modified.
Using the Undo Hooks system
For drawing space related actions, Collaboration uses the Undo system to detect the user’s actions and identify appropriate sections that have been affected. Refer to the example below on how this works for user insert snapper actions.
Example: Inserting Snappers
Let us consider the case where the user inserts a new snapper into the drawing space. The expected result is that the inserted snapper gets assigned with the section Id (of the active section) and that section file is marked as modified.
The associated UndoOp (in this case, UndoInsertOp) should meet the following
- return couldModifyModifiedState as true (default)
- populate the appendReferred or appendReferredSpace appropriately. For the case of UndoInsertOp, appendReferred returns the inserted snappers
Since the referred snappers (or spaces) store the section Id, Collaboration can retrieve the affected sectionIds from the Undo hook and identify the file to be marked as modified.
For custom user actions/tools
It is recommended that all user actions to the drawing spaces enabled by each extension to be undoable. This ensures proper handling of the modified states of Collaboration project files.
Use a suitable UndoOp classes available from core or defined your own (ensure the couldModifyModifiedState, appendReferred and appendReferredSpace is returned appropriately!).
Using Collaboration interface to mark modified
For cases where the Undo system to can't be used mark files as modified upon some user action, there is an interface available to explicitly mark as modified.
Using the setSectionModifiedG3 method in Entity, pass in the desired modified state. The project file owning the entity will be marked accordingly. (cm/core/entity.cm)
public class Entity extends CoreObject : modify notice, abstract { //... /** * Set the section file as modified. */ extend public void setSectionModifiedG3(bool modified=true) { if (isCollabEntityG3()) setSectionModified(this, modified=modified); } //... }
Part F: Miscellaneous
Limit for project files
Only 7 project files can be open in edit-mode on a workspace at any given time. Once this limit has been reached, any actions that will cause this limit to be exceeded will be blocked (e.g. creating new files, changing read-only file to edit-mode, etc...).
Revision History
Do not call RevHData.updateSnappers (cm/abstract/revHistory/revHData.cm) with null parameter for the Snapper[] argument. The previous fallback logic of all RevHSnappers in World in not applicable in Collaboration G3 context. Hence be deliberate in which snappers to assign the data to or call RevHData.updateAffectedSnappers which will update all snappers that already has that data assigned.
Comments
0 comments
Please sign in to leave a comment.