Concepts

The program is setup in a classic Model-View-Controller (MVC) design. The roles of the three parts are as follows:

  • Model Data storage and application logic.
  • View Presentation to the user, based on the model.
  • Controller Process user input. Update model and view when demanded.

Here’s a scheme giving essentially the same information:

_images/mvc.png

Model

Handling pictures gives the following basic data:

  • Image: The image files.
  • Thumbnails: Basically an additional image, scaled down and related to the original image file.
  • Tags: A list of tags for each image.
  • Metadata: Metadata for internal use, such as timestamps, access statistics, counts, flags, references, etc.

The Model defines how and where this data is stored. Due to the non-intrusiveness of TagIt, tags and metadata can be stored in an SQLite database. Thumbnails can be stored within the database or as extra files. If desired, tags and thumbnails can also be written into the Image file directly, via EXIF and IPTC. The specific behaviour of the Model is controlled via the configuration.

Controller

The controller processes user input and modifies the model on the user’s behalf. If necessary, it can demand the view to update. Most of the application logic is therefore implemented in the controller. Another reason for upholding this concept strictly is that the view is typically strongly dependent on the UI framework chosen. The controller is not. Logic being static across frameworks is therefore moved to the controller.

The controller comes in a hierarchy, along but not identitcal to the typical widget hierarchy. Since the application is controlled by the UI framework, the view classes have to take care of the controller instantiation. How this is done exactly is defined in the view. However, you start with a root Controller. Using its add_child method, new controllers can be created within the hierarchy. Note that the hierarchy is only towards the top, via the parent member, not downwards.

If done right in the view, any widget can easily create a new controller which is attached to a parent. While the controller hierarchy normally follows the widget hierarchy, this is not at all necessary. See the image below to get a grasp of how it’s done in TagIt.

_images/hierarchy.png

Having a tree of controllers allows passing events. The event infrastructure is kept rather simple, yet effective. Use bind to bind a callback and then dispatch the event to run all callbacks. Similar to how kivy handles this. Consider the following example.

class MyListener(Controller):
    def __init__(self, widget, settings, parent=None):
        super(MyListener, self).__init__(widget, settings, parent)
        self.parent.bind(on_change=self.change_callback)

    def change_callback(self, *args):
        print args # 1, 2, 3, 4
        return True # Stops event processing

class MyDispatcher(Controller):
    def on_action(self):
        self.parent.dispatch('on_change', 1, 2, 3, 4)

These events allow distributed keybindings. Each controller can attach itself to the keyboard events of CMainWindow. Then it can define its own keybindings in its own space while not interfering with other components.

View

There’s two parts here, the UI logic and the design. The design covers how structures are displayed in terms of layout, colors and graphics. The UI logic describes how elements interact with each other. Logic affecting data or application behaviour is implemented in the controller, though.

The view is heavily dependent on a UI framework. A framework change will likely result in re-writing the whole view. Currently, kivy was chosen as the only UI framework and view implementation.

The kivy sourced view implements its logic in python classes, the layout is described in the kv language. These two are somewhat seperate but fitted to each other and the boundary is flexible. This decoupling allows the design to be less independent from the UI logic and behaviour. For example, UI elements (let’s say a button) can be referred to by name or throw generic events, so the specific layout is irrelevant for the actions of that element. This way makes it easier to modify the UI in minor ways.

Since kivy takes care if input - keyboard as well as clicks and touches - they have to be handed over to the Controller. This happens through the VMainWindow class, which controlls key events on the kivy-side. Key presses are passed to CMainWindow and distributed further with the controller events system. Mouse events are directly passed from the widgets to the respective controller.

Controller creation is achieved by extending the Widget class during runtime. The widget tree is traversed upwards until a controller is found. From this controller, a child of the specified type is generated and stored within the widget. With this method, a controller is always guaranteed to have a parent (except for the root of course).