The GUI and editor parts

The source directory src/editor contains the source files of the GUI and related classes. This means the "application" itself, widget classes, QT ui files, other classes for the document-view system. Some resource files (images) are stored separately in a res directory.

Document

The document is a class to contain all data of an open document in the application. It contains the data structure "root" object of the model namespace, this contains the essential data. Other "support data" is the filename, a "firework length" setting, and all other settings of the document (if added later) should belong here. The undo-redo command history is stored here too. And objects to generate the "UUID"s of the data objects. The whole document with most of its data can be saved and restored, this includes even the command history.

Instance variables

  • fw_root - The root of the data structure ("data model"), described in page data model.
  • id_gen id_map - The generator of the UUIDs for objects in fw_root and a map that maps these to real pointers. The map contains always the currently existing objects in the document (if an object was created and the create was undone, that object is not stored here).
  • history_root - The undo history tree.
  • current_operation - Position in the undo history tree.
  • modified - "Document changed since last save" flag.

Usage

  • After construction the document must be initialized by calling initialize_new or load.
  • Save is done by calling save of course.
  • Data change functions of the document do not emit any change signals, these must be called separately by the command-view system.
  • generate_id generates a new UUID for an object of type Identified. This ID must be set then by calling set_id on the object. After a new object is inserted in the data structure (at fw_root) function map_object must be called with that object to put the object into the UUID map. Similarly the unmap function should be called when an object is deleted (removed). lookup_object finds the object with the given UUID. (These are no standard UUIDs but "universal unique identifiers" in the document.)
  • Signals pre_primitive, post_primitive, fail_primitive are emitted before a primitive is executed, after it was executed successfully, or after it was executed with error (possible sequences are <pre, post> and <pre, fail>, at special cases only <post>). Additionally there is a signal for before-post-primitive and after-post-primitive, these are needed at view update.

Primitives

A primitive (class Primitive) is a single operation on the data in the document (fw_root) that leads from one consistent state to another. (It is possible that a "consistent state" is some intermediate state that normally does not appear in the GUI editor, as some specific pairs of primitives is always executed together.) There are primitives for all possible data operations in the document, including create and delete objects, modify the parent-child relations, change data of objects. This means a lot of classes but these are mostly very simple. The primitive contains the data needed to execute the operation and to undo and redo it. This is realized by storing the UUIDs of involved objects rather than pointers. By redoing a create the object is created again (new class instance with new pointer) and inserted with the original UUID. The original UUID is generated when the create is executed for the first time. A primitive contains the data of the operation, so it is used by the view update functionality too. After a primitive was executed (and before too) all views are notified about it and get the primitive object.

Primitives are grouped into a primitive group (class PrimitiveGroup). Every GUI command creates a primitive group and adds the needed primitives to the group. Then the group is executed and the group as whole can be undone and redone by the document, not the individual primitives. (The group is basically a sequence of primitives that are executed when the group is executed.)

Usage

A PrimitiveGroup is created on heap by specifying the document. A description should be set on it that contains a short text of the performed command. Then add_exec is called on the group by passing a new Primitive instance (created on the heap) to it. This results in immediately executing the primitive and adding it to the group. This is repeated for multiple primitives if needed. Finally finalize is called on the group. The whole sequence of add_exec and finalize can result in exception if some error happens. In this case only the group object should be deleted, the error is handled otherwise by the primitive group object. If no error happened the group should not be deleted, the document takes over ownership. The primitive objects should not be deleted by the "user", these are deleted internally. Update of the views and undo history is handled by the primitive group object and document.

A primitive subclass can perform some initializing operations or checks even at the constructor (but do not modify the document). It can throw an exception if it is not possible to perform the operation with the given parameters. Finally prepare_base must be called from the constructor. Virtual functions exec_impl and undo_impl are called when the primitive is executed or undone ("execute" means here the redo too). The primitive has a state that tells if it is before first execution ("never done"), after the execution, or before the (non-first) execution ("undone"). At exec_impl the state can be used to distinguish between the first and non-first execution. These two functions (and the constructor) may throw an exception of type UiError if some error happened. At error the document should be left in a state that was before the execution of the function (before execution or before undo). Other virtual functions are there to store the data to a stream and restore it, and to dynamically create a primitive object.

The primitives should have data access functions to access the internal data of the performed operation (only for reading). This data may be needed by the views to update themselves. Additional "helper" function is was_undo which can be used in a post_primitive handler in a view to get is the primitive was actually undone (true) or performed (false). Similarly about_to_undo can be used in a pre_primitive handler to check if an undo is performed (true) or the normal execution.

Views

A view is any object that is derived from class View. It contains an "attribute group" and has access to the document. Furthermore, callbacks (virtual functions) are called when a primitive is executed on the document. Callbacks are called too when something in the "attribute group" changes.

Atribute group

The attribute group is an object that contains some special values used by the view (these values in the attribute group are called attributes). Multiple views can have the same attribute group, then these will get the same values for the attributes. This is used for example when multiple views use the same object to display its data (only a part of the data). The object is contained as value of an attribute in an attribute group, it is enough to change the attribute once and all views have the new value then. A view contains only a single attribute group. The attribute group can contain attributes that are not used by all its views. The attribute group object is managed by the entity that creates the view, not the view itself. (It is possible that a view manages its own attribute group.) The attribute group contains the document object used by the view too. It can simply be set by a function, then all views are notified about this. Attributes can be added to a group and the value of these can be changed, the view gets notifications about these events (value change). The attribute objects get notifications when something in the document has changed, before all views. But only after a primitive was executed. If an attribute contains a value that references for example to an object that was deleted by the primitive, the attribute can change its value. All attributes in the group have a "name", this is an integer value to find the attribute (multiple attributes of the same type are distinguished by the name).

View

The view gets notifications before a primitive is executed (handle_pre_primitive), after a primitive was (successfully) executed (handle_post_primitive), and after a primitive was failed (handle_fail_primitive) (this is needed if the view does some preparations in the pre primitive handler and must do something then after the primitive was executed, or must "rollback" its actions if the primitive was failed). handle_set_attr_group is called when the attribute group itself was changed. This means often that the document was replaced, so the entire view must be refreshed. The document of a view may be zero. handle_attr_event is called when (after) something in the attribute group was changed (value of an attribute). The type of change and what was changed is contained in an event object. Currently only one type exists that tells that the value of an attribute was changed.

The view is otherwise not related to the document, only through the attribute group and through QT signal-slot connections for the change events. A view can manage other view objects or even the document object. Currently a view called MainView contains the document object. It simply sets its own attribute group, with the document. The MainView is the main application window too and handles the common load-save and other commands.

Last edited Dec 19, 2011 at 4:26 PM by fruitfruit, version 3

Comments

No comments yet.