Skip to content

Structural Concepts

ThomasMcVay edited this page Jan 11, 2015 · 62 revisions

This Wiki Page describes in explicit detail the concepts that are necessary to understanding the structure and scope of the MediaApp Framework. If you haven't already, please visit Structural Goals first to get a somewhat briefer outline of the Framework's goals before you start digging in here.


Near User End Framework Model (NUE Framework)

In order to better explain the purpose and scope of the MediaApp framework, I am inventing a term "Near User End Framework" to describe a model of programming I haven't seen anywhere before. For brevity I will shorten it to "NUE Framework"

There is likely a better term already in use by other developers to describe what I am doing, but I don't have the experience to say for sure. This invention should suffice for now, and I will replace this term in the documentation once I find a suitable substitute.

The development model in its simplest form can be broken into 3 layers, with layers 4 and 5 possible depending on the Structural Goals of the NUE Framework:

  1. Underlying Framework - Pyside(QT)
  2. Near User End Framework - MediaApp
  3. User End Application - Your Application
  4. User End Scripts - Scripts developed for the User End Applications's API
  5. User End Projects - Projects developed using your Application

In short, the idea behind the NUE Framework is to provide a developer with as many structures as are made obvious by considering both the Underlying Framework and a given set of Structural Goals

The primary difference between a NUE Framework and a regular Framework is that a NUE Framework has structural requirements that make it unsuitable for other Frameworks to be developed from it. Also because the explicit intent of the NUE Framework is to not be more than one step away from a User End Application, certain amounts of language abuse and magic are made less cumbersome. Of course this does not extend the developer of a NUE Framework unlimited license to make their code non-generic, but it does limit the range of supported use cases substantially enough to make these types of abuses easier to justify.

In the case of MediaApp, the primary structural goal is to provide Node Based GUIs and data structures. The complexity inherent in such a structure necessitates the use of the NUE Framework. An example of the abuses I mentioned can be found in the AppCore section below, where I am abusing the sys.modules dictionary for the purposes of simplifying the namespace of the api.

AppCore

AppCore is an instance of the AppCore.Core class registered in sys.modules as 'AppCore'. See here for the genesis of this idea. By using this obscure pattern I am able to expose the instance as if it were a module, and greatly simplify the namespace

import AppCore
print AppCore.allNodes()

AppCore is not explicitly defined as a singleton, but since it creates a QtGui.QApplication object as part of its __init__() method. PySide will raise an error if you attempt to call another instance of it.

AppCore is a special subclass of NodeOwningObject, containing its own nodes for the purpose of providing a root structure to the application.

AppCore also provides 3 different dictionaries of settings for various practical applications.

  • AppAttributes is used as a store of data that changes on a per project basis. AppAttributes are written out to project files each time a user saves. It is recommended that the User End Application store any additional settings that might change on a per project basis here.
  • AppPrefs are preference settings that can be defined by the user in the Preferences Window. The User End Application is also expected to provide a default version of AppPrefs.py that the user can revert to, and load on first use. It is recommended that the User End Application store any additional settings that might change on a per user basis here.
  • AppSettings are look and feel settings designed to allow the User End Application to customize various behaviors of the MediaApp Framework to suit the needs of their application. Also it is recommended that the User End Application store any additional settings that might change from version to version of your application here.

NodeOwningObject

A NodeOwningObject is an object that implements a number of methods for creating and managing nodes. NodeOwningObjects will usually do this by inheriting the NodeOwningObject class.

By default all of the nodes that a NodeOwningObject creates or registers are subsequently registered with AppCore . This can be overridden, but is highly discouraged as this will break default project saving/loading functionality, and any other methods that depend on the AppCore.allNodes() method.

AppCore is a special subclass of NodeOwningObject that has overridden most of the classes default methods.

NodeLinkedWidget or WidgetLinkedNode

A NodeLinkedWidget is a widget, that upon creation, will create a specialized node for itself with the NodeOwningObject it is assigned to. If no NodeOwningObject is assigned, it will create the node with

Alternatively, a WidgetLinkedNode is a node that will, upon creation, create an instance of the widget type that it is linked to, and call AppCore.dockThisWidget(widget)

ActiveNode

AppCore allows for exactly one node to be active at any given time. I currently can not think of a need for the use of an active Node for each NodeOwningObject.

Editing of a node is not precluded by its lack of active status. The status assignment is simply so that nodes and widgets may be sensitive to a node's active status.

If the node is providing anything to a particular class of objects, the ActiveNode is expected to know what attributes the object will attempt to call for, and implement them accordingly.

SensitiveObjects

Objects may be registered with AppCore as sensitive to the ActiveNode. SensitiveObjects are expected to implement setActiveNode as a method to receive pointers to the active node. This is necessary because the ActiveNode will often provide QT Objects such as a QToolBar, and QT's design makes it impractical to call AppCore.getActiveObject() each time these objects are painted on screen.

When AppCore.setActiveNode is called, setActiveNode will be called on each registered with AppCore. The object provided may be None.

When attempting to call or retrieve attributes from the ActiveNode, the SensitiveObject is expected to call hasattr() and callable() accordingly on the ActiveNode and attribute so that an exception does not occur. The ActiveNode may or may not have implemented the attribute the SensitiveObject is asking for.

SoftDelete

When a user asks to delete a Node or Knob it is not actually deleted. SoftDelete is called and several things happen:

  • Any timelineCaches or frameCaches are emptied.
  • Linked Widgets and properties Widgets are hidden.
  • The Node is "Disowned" by the NodeOwningObject but not "emancipated".
  • It still contains a pointer to its "estranged" owner
  • The last remaining pointer to the Node/Knob is left in the UndoQueue.

The Node/Knob is deleted permanently by the garbage collector once UndoQueue discards it. Discards will happen only when the UndoQueue is beyond it's preferred threshold.

If SoftDelete is called again (because of an UndoAction):

  • The Node/Knob ReRegisters itself with it's estranged owner

UndoQueue

Knobs are the only modifiable objects in MediaApp that can either be undone or redone. There are 2 exceptions to this rule:

  • Node creation and deletion. (Because they contain Knobs)
  • Widgets that implement their own QUndoStack such as QTextEdit (Used in the ScriptEditor Widget) Undo operations in a QUndoStack will not be registered with any MediaApp UndoQueue, and are simply left to be useful in their own contexts.

An UndoQueue item can be either an UndoAction or an UndoSequence. A given UndoQueue may contain a mixture of both.

There are 4 types of UndoQueues:

  1. AppCore.UndoQueue provides a global undo context, in which all undo/redo objects are ultimately registered. Can be accessed via edit menu, or an assigned keyboard shortcut. Order of objects is subject to change

  2. NodeOwningObject.UndoQueue Lists of changes to multiple knobs at a time are registered (ie xpos, ypos). Additionally Creation or Deletion of Nodes will be registered by the NodeOwningObject. The primary case for this is drag events, in which a special watch flag is set with AppCore.UndoQueue to watch for and return changes for a given period of time. Undo/Redo events are broadcast to AppCore so that the object may be flipped.

  3. Node.UndoQueue Changes to any Knob are registered. Additionally Creation or Deletion of Knobs will be registered by the Node. Undo/Redo events are broadcast to Parents/AppCore, so the object may be flipped.

  4. Knob.UndoQueue is the lowest level of UndoQueue, objects are registered with both the parent Node, and with AppCore. In the event parent is not a node, or there is no parent, objects are not registered at all. Undo Objects are added exclusively by setValue or setScript. setValue and setScript both accept the kwarg undo = False for instances where one of these functions is called repeatedly, and multiple undo events are not desirable (ie a drag event). Can be accessed via assigned keyboard shortcuts when the Knob has focus.

Each time an undo/redo event occurs, processUndoAction(action) or processRedoAction(action) is called on the Knob, passing the set parameter to it. In both cases the first thing the Knob does is call flipUndoAction(action), or flipRedoAction(action) on all of its ancestors. No matter where the action is at in any queue, unless it is part of an UndoSequence, it is flipped from undo/redo to the top of the opposite stack. If the action is part of an UndoSequence it is left alone.

Special Cases:

  • Node or Knob Creation or deletion. Changes to order AppCore.UndoQueue does not affect or be affected by creation or deletion of objects because once they are deleted, their contextual UndoQueues exist only as serialized objects in AppCore.UndoQueue, and will not be available to the user to make any calls that would modify the order.
  • RunOnce Scripts that are designed to be undoable. ScriptButtons are a special class of Knob that implement an UndoQueue very similar to that of NodeOwningObject, before the script starts, the knob calls AppCore.captureUndoSequence(), AppCore sets a flag and starts adding Undo objects to a list as they come in. Where the ScriptButton diverges from a NodeOwningObject though is that it then calls AppCore.popUndoSequence() instead of AppCore.retreiveUndoSequence(). This is because it is not practical for a script button to accept a setFocus event by the user, the only obvious place to run the undo event is in the global context.
  • Looping Scripts that are designed not to be undoable should call AppCore.ignoreUndoEvents() at the beginning of the script, then call AppCore.registerUndoEvents() at the end. (A special wrapper for these kinds of scripts that does this automatically may be announced in the future.)

UndoAction

  1. A pointer to the corresponding object. If it is a Node/Knob that has been SoftDeleted, this will be the last persistent pointer to the object.
  2. An ActionString which is a string that contains a executable call that will either:
* set value or script of a Knob back to where it was. (example "['xpos'].setValue(50)").
* call [SoftDelete](https://github.com/ThomasMcVay/MediaApp/wiki/Structural-Concepts#softdelete) on a Node or Knob.

UndoSequence

Simply a list of UndoActions

Clone this wiki locally