Features Help Download

Lomse Hacking Guide

3.2. The graphical model in detail

3.2.1. Overview

The graphical model is an specific representation for an LDP document, oriented to describe its visual appearance.

The graphical model is, basically, a tree of objects, derived from abstract class GmoObj. All these objects have ‘Gmo’ as class prefix and are sometimes named in this document as GMOs: Graphical Model Objects.

The approach followed to describe the visual appearance of a document has been to capture the graphical structure by splitting the model in containers and visible objects. All container classes derive from abstract class GmoBox. And all visible objects derive from abstract class GmoShape.

GmoBox objects are used to organize the layout space. They define areas, in the final image or paper, in which shapes will be positioned. For instance, GmoBoxParagraph is a box delimiting the space occupied by the text in a paragraph. Boxes can be nested but can not partially overlap other boxes. That is, a box is either independent (covers a region of paper) or is fully contained in another box, subdividing it. For instance, GmoBoxSystem, that represents the space ocuppied by a system, is subdivided by GmoBoxSlice, representing vertical slices of the system (i.e. measures)..

GmoShape objects represent visible symbols, such as a line, a glyph, an arch, a note head, a text, etc. Shapes can be composite, that is a shape can be a container for other shapes. For instance, a GmoShapeNote represents a note, and it is a container for atomic shapes such as the notehead, the stem, the flag, the dots, ledger lines and accidentals.

The graphical model is a tree of GmoBox and GmoShape objects. The root is always a node of class GmoBoxDocument, that represents the whole LDP document. Only one instance of this class is created for a document. GmoBoxDocument contains one or more instances of class GmoBoxDocPage, which represent the pages of the document.

The GmoObj objects correspond to the ImoObj objects, but the relation is not one to one as an ImoObj can generate multiple GmoObjs (i.e. a tie can generate two arches, or a key signature can generate as many shapes as systems in the score). Also, the tree structure is different.

The hierarchy of GmoObj objects will be described in detail later. For now, as an illustration, consider the following LDP document:

(lenmusdoc (vers 0.0)
    (content
        (score (vers 1.6)
            (instrument
                (musicData
                    (clef G)
                    (key A)
                    (n a4 q)
                    (n c5  q)
                )
            )
        )
    )
)

It will render as:

Rendering of previous LDP score.

Figure: Rendering of previous LDP score.

And will generate the following graphical model:

GmoBoxDocument
  |
  +-- GmoBoxDocPage
        |
        +-- GmoBoxDocContent
              |
              +-- GmoBoxScorePage
                    |
                    +-- GmoBoxSystem
                          |
                          +-- GmoShapeStaff
                          +-- GmoShapeBarline
                          |
                          +-- GmoBoxSlice
                          |     |
                          |     +-- GmoBoxSliceInstr
                          |           |
                          |           +-- GmoShapeClef
                          |           +-- GmoShapeKey
                          |           +-- GmoShapeNote
                          |
                          +-- GmoBoxSlice
                                |
                                +-- GmoBoxSliceInstr
                                      |
                                      +-- GmoShapeNote

Observe that the staff lines (GmoShapeStaff) and the initial barline (GmoShapeBarline) are included in the system box.

GmoBoxSystem bounds are displayed in following picture:

The same score highlighting the GmoBoxSystem bounds

Figure: The same score highlighting the GmoBoxSystem bounds.

The two GmoBoxSlice boxes are displayed in this picture:

The same score highlighting GmoBoxSlice objects bounds.

Figure: The same score highlighting GmoBoxSlice objects bounds.

3.2.2. The GraphicModel class

Class GraphicModel represents the graphical model. It is basically a container object for the root node of the graphical model. The root is always a node of class GmoBoxDocument, that represents the whole LDP document.

GraphicModel object contains some member variables. The two most important are:

  • The root node of the graphical model (GmoBoxDocument* m_root),
  • Information for mapping all ImoObj objects to the boxes or shapes they create (std::map m_imoToGmo)

There is a need to determine which GmoObj object or objects are generated by an ImoObj. As the correspondance between internal model objects and graphical model objects is not one-to-one, the GraphicModel includes a map relating the internal model objects to the graphical model objects they generate. This issue is reviewd later in detail, in section imo-gmo-relation.

The GraphicModel class provides methods for some main services on the graphical model:

  • Access to the root of the graphical model (get_root method).
  • Access to the box for any page of the document (get_page method).
  • Rendering a page (draw_page method).
  • Hit testing (hit_test method).
  • Selecting objects (select_objects_in_rectangle method).
  • Locating the box or shape at a certain point of the screen (find_shape_at and find_inner_box_at methods).

Note that for rendering, instead of having a method to trigger the rendering of all the graphical model, it has been decided to control rendering on a page-by-page basis.

3.2.3. Class GmoObj

It is an abstract class from which all graphic model objects must derive. All GmoObj` objects have a type, for classification and type identification.

Any GmoObj knows in which container box it is included (parent box):

GmoBox* get_owner_box();

As GmoBoxDocument is the root of the graphic model, it is the only box without owner box. Therefore, method get_owner_box will always return NULL.

Any GmoObj also knows from which ImoObj it derives (creator or parent imo):

ImoObj* get_creator_imo();

But there are two exceptions:

  • Class GmoBoxDocument. Its creator ImoObj is the ImoDocument, but it is implicit and parent imo returns NULL. (Should be changed to return the ImoDocument object [TODO1]).
  • Class GmoBoxDocPage. Its parent Imo is also the ImoDocument object. It is also implicit, so parent imo returns NULL. ([TODO2]: Should be changed to return the ImoDocument object).

All GmoObj objects have a bounds rectangle defined by an origin point and a size. Origin point (left-top point) is always relative:

  • For a box, origin is relative to its parent page (GmoBoxDocPage).
  • For a shape, it is relative to its to owner box.

GmoObj objects also have state information (selected, dirty, mouse hover, part of a link).

The following picture shows current hierarchy of objects:

GmoObj
  |
  +-- GmoBox
  |     GmoBoxDocument
  |     GmoBoxDocPage
  |     GmoBoxDocPageContent
  |     GmoBoxInline
  |     GmoBoxLink
  |     GmoBoxParagraph
  |     GmoBoxControl
  |     GmoBoxScorePage
  |     GmoBoxSystem
  |     GmoBoxSlice
  |     GmoBoxSliceInstr
  |
  +-- GmoShape
        |
        +-- GmoSimpleShape
        |       GmoShapeBeam
        |       GmoShapeBarline
        |       GmoShapeButton
        |       GmoShapeImage
        |       GmoShapeInvisible
        |       GmoShapeRectangle
        |       GmoShapeSlur
        |       GmoShapeStaff
        |       GmoShapeText
        |       GmoShapeTie
        |       GmoShapeWord
        |       GmoShapeSimpleLine
        |           GmoShapeStem
        |       GmoShapeBracketBrace
        |           GmoShapeBracket
        |           GmoShapeBrace
        |       GmoShapeGlyph
        |           GmoShapeAccidental
        |           GmoShapeClef
        |           GmoShapeDigit  (time signature digit)
        |           GmoShapeDot
        |           GmoShapeFermata
        |           GmoShapeFlag
        |           GmoShapeNotehead
        |           GmoShapeRestGlyph
        |
        +-- GmoCompositeShape
                GmoShapeAccidentals
                GmoShapeKeySignature
                GmoShapeNote
                GmoShapeRest
                GmoShapeTimeSignature
                GmoShapeTuplet

3.2.4. GmoBox objects

GmoBox objects are used to organize the layout space. They define areas, in the final image or paper, in which shapes will be positioned. For instance, GmoBoxParagraph is a box delimiting the space occupied by the text in a paragraph. Boxes can be nested but can not partially overlap other boxes. That is, a box is either independent (covers a region of paper) or is fully contained in another box, subdividing it. For instance, GmoBoxSystem, that represents the space ocuppied by a system, is subdivided by GmoBoxSlice, representing vertical slices of the system (i.e. measures).

As boxes are GmoObj objects they have a bounds rectangle, that defines the box area. This rectangle defines reference bounds for layouting other objects (the box content).

Important

When I started all this, the focus was in rendering scores. Later, when I extended the software to render full documents, I realized that the work to do was similar to that in HTML browsers, and that in some aspects I was re-inventing the wheel. Therefore, I started to borrow ideas from HTML browsers, shuch as separating content from style. Or the margin, border, padding and content model for boxes.

Boxes have additional attributes related to the bounds rectangle, so that the resulting box model is similar (but not equal) to that used in HTML browsers and CSS. The image below illustrates the box model:

+-------------------------    <--- This external rectangle is the bounds
|Margin (transparent)              rectangle defined in GmoObj base class
|  +----------------------
|  |Border (solid pixels, border color)
|  |  +-------------------
|  |  | Padding (empty, backgound color)
|  |  |  +----------------
|  |  |  |
|  |  |  |
|  |  |  |    Content (real content, background color)
|  |  |  |
|  |  |  |
|  |  |  +----------------
|  |  |
|  |  +-------------------
|  |
|  +----------------------
|
+-------------------------

Explanation of the different parts:

  • Margin - Clear transparent area around the border. The margin does not have a background color, it is completely transparent.
  • Border - A border that goes around the padding and content. The color for the border can be independently specified, but transparent areas of the border line (i.e. spaces in a dotted line) are drawn in the background color of the box.
  • Padding - Clear area around the content. Its color is the background color of the box.
  • Content - The content of the box, where texts, scores and images are placed.

Apart of defining the content area, all GmoBox objects are containers for other boxes and for shapes. GmoBox has two collections:

std::vector<GmoBox*> m_childBoxes;
std::list<GmoShape*> m_shapes;                  //contained shapes

The first one is a vector with all other GmoBox objects contained in this box. And the second one is a list of all GmoShape objects directly contained in this box.

For hit testing and rendering, all shapes are organised in layers. For this, an ordered collection with all the shapes in a page is maintained in box GmoBoxDocPage. The collection of GmoShape objects inside a GmoBox is used for rendering, and the collection in box GmoBoxDocPage is used for hit testing. This is explained in section Layers.

3.2.5. Notes about some box objects

3.2.5.1. GmoBoxDocument

This is the main container for the whole document. Only one instance of this class is created for a document. GmoBoxDocument contains one or more instances of class GmoBoxDocPage, which represent the pages of the document.

GmoBoxDocument is a ‘degenerated’ box, as margins, padding, border, etc. doesn’t mean anything and are ignored. Probably this object can be suppressed but it is comfortable to have a root GmoObj, although its only purpose is to access its children!

3.2.5.2. GmoBoxDocPage

Represents a page of the document. It contains the boxes that define the page content (i.e. GmoBoxParagraph, GmoBoxScore, GmoBoxImage, etc.) as well as shapes for specific elements (i.e. background image).

For hit testing and rendering, all shapes are organised in layers. For this, an ordered collection with all the shapes in a page is maintained in box GmoBoxDocPage:

std::list<GmoShape*> m_allShapes;           //contained shapes, ordered by layer and creation order

The collection of GmoShape objects inside a GmoBox is used for rendering, and the collection in box GmoBoxDocPage is used for hit testing. This is explained in section Layers.

3.2.5.3. GmoBoxScorePage

Represents a page in the printed score. It contains one or more instances of class GmoBoxSystem. Also contains shapes for page level elements, such as headers and footers.

3.2.5.4. GmoBoxSystem

It represents a line of music in the printed score. It is made up from a collection of consecutive measures. Internally it contains a collection of GmoBoxSlice.

Its bounds go from left margin to right margin, and from top line (5th line) of first staff to bottom line (1st line) of last staff.

Group name and abbreviation, braces and brackets, system left barline and prolog shapes are owned by the GmoBoxSystem object.

Box system limits are defined as follows: The vertical distance from the bottom line of the last staff in previous system to the top line of the first staff in this system is divided by 2. Half of this distance is assigned to prevous system and half to this one.

Analogously is done to establish staff limits.

3.2.5.5. GmoBoxSlice

It represents a vertical slice of a system, for example, one measure.

Internally it contains a collection of GmoBoxSliceInstr.

3.2.5.6. GmoBoxSliceInstr

It represents an instrument inside a box slice: a fragment of a line of music (measure) but only for an instrument.

It is made up from a collection of consecutive staves for that instrument.

Its bounds go from left barline to rigth barline (that is, it includes only a measure), and from top line (5th line) of first staff to bottom line (1st line) of last staff. For last measure, if no final barline, its right margin is the right limit of last staffobj in the measure.

The first GmoBoxSliceInstr owns the instrument name and the brace/bracket shapes, but are out of its box bounds.

3.2.6. GmoShape objects

All visible objects derive from abstract class GmoShape. It represents any visible object, such as a line, a glyph, an arch, a note head, etc.

All shapes have:

  • A bounds rectangle, that defines the space really occupied by the shape. It is used to detect visual collisions with other shapes during layout process. Also, during layout phase, to know the limits (x left and x right positions, width)
  • A selection rectangle, that defines the visual area to select the object by clicking on this area with the mouse. Usually it coincides with the bounds rectangle but might differ. For example ?
  • A name, useful for debugging, to identify the symbol it represents.
  • They must react to commands to change theirs positions / geometry. This is needed during Layout phase, to accomodate the shapes to its definitive layout. Possible, it will be also needed during score edition, again to accomodate the shapes to the new layout after some score editing action.

All staff objects must have a shape, althought it can be not visible. This is required to store staffobj positioning info, for example, to paint the cursor. Shape GmoShapeInvisible has been defined for this case and others in which an empty shape is needed

3.2.7. Layers

When objects overlap, it is necessary to decide which one will be partially hidden by the other. For this Lomse uses a layers system. Layers are like transparencies stacked one on top of one another to create the final image you see. You can consider each layer as a piece of transparent paper (i.e. cellophane) on which something is drawn. All layers are then stacked each one on top of another. When the layers are stacked, the images appear as if they are all a single image. Top layer images could hide parts of bottom layers thus creating the final image you finally see.

By default the following layers are defined:

  • background layer: default layer.
  • staff layer: staff lines, brackets, instrument name, etc.
  • barlines layer: barlines, repetition signs and related (da capo, etc.)
  • notes layer: staff objects
  • annotations layer: aux objects
  • top layer: currently nothing on it.
  • user layer: currently nothing on it.

Each GmoShape has an attribute that defines the layer on which it will be placed. This attribute is an enumeration defined in class GmoShape.

Layers are implemented by maintaining, in each page box (GmoBoxDocPage) a global collection containing all the shapes in the page box and all contained boxes. This table is updated when a box is layouted and added to the graphical model. See Maintaining model coherence. This global collection is ordered by layer and shape creation order

The decision to keep this globlal list of shapes at page level (GmoBoxDocPage) is due to several reasons. The most important are: * if placed at lower box level it would be complex (or impossible) to change the rendering order of a shape, as main ordering woul be imposed by traversing the tree of boxes. * Also it would be more complex, for hit testing, to take z-order into account, as the list would not be global but local to each GmoBox.

But if the list of shapes is at page level then it is complex to constrain checking to a certain box, i.e. a paragraph, a system, or a slice, as the list has no the structure of an R-tree (http://en.wikipedia.org/wiki/R-tree). This implies that for hit tests, in the worst case all the layers must be traversed.

[TODO4]: GmoBoxDocPage serves as container for the list of shapes in that page, ordered by layer and creation order. This collection should be splitted and moved to each main box (see [TODO3]), to improve performance when a box is dirty and must be re-layouted or re-rendered. See Layers.

(To be continued ...)

3.2.8. To do

[TODO1]Class GmoBoxDocument. As it is the root of the hierarchy, its owner ImoObj is the ImoDocument, but it is implicit and method get_creator_imo() will return NULL. Should be changed to return the ImoDocument object. See Class GmoObj.
[TODO2]Class GmoBoxDocPage. Its parent box is GmoBoxDocument and its parent Imo is ImoDocument, but it is also implicit, so method get_creator_imo() returns NULL. Should be changed to return the ImoDocument object. See Class GmoObj.
[TODO3]For layouting, boxes should be splitted into two groups: GmoMainBox and GmoSecondaryBox. Main boxes will define independent, isolated content items that do not overlap, and will define the units for layouting. Secondary boxes will define a sub-area totally inside a main box. See Class GmoObj.
[TODO4]GmoBoxDocPage serves as container for the list of shapes in that page, ordered by layer and creation order. This collection should be splitted and moved to each main box (see [TODO3]), to improve performance when a box is dirty and must be re-layouted or re-rendered. See Layers.