Locator
Organize and find your stuff


   Main Manual Compile Development About

For this application, a model-view approach has been followed. The engine of the application has been developped with C++11, while the presentation is fully based on the Qt library.

Sometimes the division may seem artificial (e.g. when having to continously convert from std::string to QString and vice versa), but if hopefully well done, it can allow to use a completely different library for the view with moderate effort. I would also hope that Qt would have a deeper integration with the C++11 and STL standards, but maybe it is soon and convergence will happen in the future.

Another library is also used to draw the ontology dependency tree: Graphviz. I didn't want again to rely on the graph capabilities and internal structures of the library and depend on any future change. The price to pay is that the same structure is stored twice in memory, first time in the Graphviz library and again in the internal structures of the engine, based on the STL containers.

I have to also mention the lack of a database backend. I guess it wouldn't be difficult to add it if need be. The .loc file if an XML formatted file. The idea was that that file could be edited by a person in case of necessity or, as it is the case in the version of the application at the time of writing this, if certain functionality isn't provided. The most important functionality the application lacks now is dealing with ontologies. At this moment, the only way to change ontologies is editing the .loc file.

To have a general view of the design of the model part of the application a shallow class diagram is provided. This diagram was produced with Dia and the file, locator.dia can be found in the source directory.

Shallow class diagram

Let's see with an example, in the following sections, the different components of the diagram.

Object and Container classes

Object and Container classes

These are perhaps the most important classes of the application. Containers have the ability of holding other objects or containers inside, but they are also objects themselves.

Objects belong to one or several classes. Classes give objects hints about the properties they can define. An example of an object is My dear cat Trino. This object belongs to class Cats. Cats class gives one property: Number of mice eaten by week, but Trino doesn't define it since he is a strict vegan, safe from flies, which he likes the most.

For each property an object defines, there is an instance of the Datum class.

Objects can define custom properties that don't come from any class they belong to. Finally, objects are in a location. The main goal of the application is to record and find where objects are.

Class class

I think that I've written this application only to be able to write the header of this section. This class represents classes. Shall it represent itself? Fortunately not!

Class class

Classes are intended to hold a list of properties that will be provided to objects as hints, just in case they want to store that information.

But classes also form a hierarchy, with more general classes at the top. For example, class Cats can be the descendant of class Animals and sibling of class Dogs. If class Animals has the property (attribute) Height then an object belonging to class Cats, like Trino, will be suggested to fill in its height, if he wants to.

Classes belongs to a ClassGraph that is used to draw the class diagram that shows the hierarchy of classes in the ontology.

Attribute class

Attribute class

Attributes are named properties in the program interface. They express something that can be said about an object. For example, Height can be an attribute. Although classes has associated attributes, objects can also define their own. For example, Trino, the cat, belongs to class Cats and, therefore, belongs to class Animals. Attribute Height is associated with class Animals. Thus, when editing the properties of Trino, a height input is suggested. You can input Trino's height there, but always optionally. Besides, Trino, as an object, can define additional attributes and give values for them. For instance, Trino can define a property: Date of last headache, not quite usual for a cat, but for Trino.

All attributes have a type that defines the kind of value they can hold. Possible types are fixed and quite obvious. For instance, Height attribute is of type length and Date of last headache is of type date

Type class

Type class

Attributes have types. Although class Type is subclassed in different descendants, real types are defined in the static map types. An attribute can choose among a limited amount of types. At the time of writing this, these are: text, weight, power, money, date, length, rectangle, and box. This list will surely grow in the future. Each one of these is an instance of one of the classes of the family of Type.

Of all subclasses, Measure Type is perhaps the more difficult to understand. It holds all types that have something equivalent to physical units. For instance, typle length belong to class ScalarType, which is a one dimensional MeasureType. The length can be expressed in several units: meters, inches, and so on. An attribute has default units but the user can express the data in the unit he likes more among the available, and conversions are done automatically.

mainUnit is a property of the ancestor of all this family. For classes that don't have units, it is the empty string. For classes that use them, it is the unit in which the stored values are expressed. For instance, I can express and want my length shown in inches, but the data is stored internally in meters, if meter is the mainUnit of length. Although it may seem at first sight that mainUnit ought to be a property of MeasureType, it isn't the case. There can be units outside this class: currencies, groups (dozens), or even general classes with arbitrary conversions in TextType

Datum class

Attribute class

The picture is completed with this class. Each property an object defines is an instance of the Datum class. Imagine that object Trino(*) defines property Height to be 43 cm. An instance of Datum, in this case, more exactly, of FloatDatum is created by factory method createDatum of class Type. This instance can hold a float value that will express the height of Trino in meters (0.43 m), although it will always be shown in centimeters. The Attribute involved is, of course, Height, which is of type length.

Units and decimal places

Some data associated with properties have units. One has to consider model-view structure when talking about units. Regarding the model, all data stored in files, or eventually a database, is stored in Type::mainUnit unit.

Regarding presentation, there is a great deal more of flexibility. First, not only the unit in which I want my property to be expressed can be chosen, but also the number of decimal places, if necessary. And for the unit part, there are several defaults.

First, you can choose arbitrary units for the data you input. But if not, if the attribute comes from a class the object belongs to, there is a default unit and decimal places. And if not, the very Attribute object provides a default value.

Let's see it with an example: think of an attribute named Height of class Animals. Trino is an animal, so he has that attribute. If I don't leave the height of Trino blank, I can express it in, let's say inches. If I don't set a unit, the default unit would be the default for Height of Animals, for instance, cm. And if Animals doesn't set a default unit, the default unit of the attribute is used. In the case of Height, it could be meters.

The rationale is that the same attribute Height could be reused to record the height of a tower or a mountain and adapted depending on the class that uses it.

Model view interaction

Access to model classes from the view is easy. There is a singleton AppModel::app, accessible everywhere.

In order to preserve view independence, whenever the model needs to signal something like a change of state to the view, it is done by callback machanics. A class of the view registers one of its methods in the model and, from then on, it will be notified.

An example: MainWindow object wants to be notified whenever the application is dirty, i.e. data contents differ from what is stored in the disk. Here are the steps:

  1. First, define a method in class MainWindow where objects of the class will be notified:
    class MainWindow
     {
      [...]
      public:
        void dirtyStateChangedCallback(bool dirty);
      [...]
      };
    
  2. Second, in MainWindow constructor, registe it in the model:
    MainWindow::MainWindow()
     {
      [...]
      app.setDirtyStateChangedCallbackp(
        std::bind(&MainWindow::dirtyStateChangedCallback,this,
                  std::placeholders::_1));
      [...]
      }
    
  3. In the AppModel class, the infrastructure to do this is present:
    class AppModel
     {
      [...]
      private:
        std::function<void(bool)> dirtyStateChangedCallbackp=0;
      public:
        void setDirtyStateChangedCallbackp(decltype(dirtyStateChangedCallbackp) p)
         {dirtyStateChangedCallbackp=p;}
      [...]
      };
    
  4. And so is a function and variable to notify that the dirty state has changed:
    class AppModel
     {
      [...]
      private:
        bool dirty=false;
      public:
        bool isDirty() const {return dirty;}
        void setDirty(bool b=true)
         {dirty=b;
          if (dirtyStateChangedCallbackp) dirtyStateChangedCallbackp(dirty);}
      [...]
      };
    

With all this we get view independence. We can use whatever library for the view and register the relevant methods to be notified. An easy alternative would be to use the nice SIGNAL-SLOT mechanics of Qt, but that would tie the application to the library.

_________________________
(*) I hope Trino will excuse me for calling him an object...
   Main Manual Compile Development About