A simple application ******************** This section describes the process of creation of a sample application, from the design with *Glade*, to the integration of views and code inside the *MVC-O* Infrastructure. We want to design and implement a simple application constituted by only one window, containing two string labels. One label shows a text, while the other shows the number of characters displayed (i.e. the length of the string) by the first one. There is also a button the user can press. By pressing the button, the user can change the displayed text, and of course this action might change also the displayed text length accordingly. Figure :ref:`EX:f` gives an idea on how the application should appear. .. _EX:f: .. figure:: images/example.png :width: 6 cm :align: center The sample Application .. _GLEX: Glade ===== Figure :ref:`GL:f` shows *Glade* and a project named ``example``. The sample *GUI* has only one top-level window (named ``window1``). .. _GL:f: .. figure:: images/example_glade.png :width: 15 cm :align: center Designing the example by means of *Glade* for GTK2 The *Widget Tree Window* shows the widgets hierarchy. There are essentially the three main components (one button and two labels), grouped inside a set of *containers*, which supplies alignments and resizing capabilities. On the right side of Figure :ref:`GL:f`, the *Properties Window* shows that the widget named ``button1`` has signal ``clicked`` associated with function ``on_button1_clicked``. This means that the Controller will have to supply this function in order to handle the ``click`` event occurring in ``button1``. Implementation ============== The implementation is slightly elaborate for this example, because the goal here is to show how the sample application can be implemented by using the *MVC-O* Infrastructure. A basic knowledge of any Object Oriented programming language is sufficient to understand how this example has been pushed inside the *MVC-O* framework. On the contrary, a fair knowledge of the Python language is required in order to understand the code details. More description section is :doc:`impl`. Model ----- Class ``ExampleModel`` is as simple as class ``ExampleView``. As for ``ExampleView``, it extends a base class of the *MVC-O* Infrastructure, class ``Model``. The state is represented by a set of possible messages, as well as by the current message index. The current message index is also an observable property. A couple of methods are supplied in order to access the state. :: # file model.py from gtkmvc import Model class ExampleModel (Model): """The model contains a set of messages and an observable property that represent the current message index""" # Observable property: code for that is automatically generated # by metaclass constructor. The controller will be the observer # for this property message_index = -1 # -1 is the initial value __observables__ = ("message_index",) def __init__(self): Model.__init__(self) self.messages= ("I am patient with stupidity", "but not with those", "who are proud of it.", "(Edith Sitwell)", ) return def get_message(self, index): return self.messages[index] def set_next_message(self): # this changes the observable property: self.message_index = (self.message_index + 1) % len(self.messages) return pass # end of class Notice that class instance members are declared to be observable through the special class variable ``__observables__``, which is a list of names (string) of the properties that are observable. The base class Model belongs to a meta-class which automatically searches for observable properties and generates the needed code to handle the notification. When the value of variable ``message_index`` changes, all registered observers will be notified. View ---- In the example, the View is implemented inside the class ``ExampleView`` shown below. :: # file view.py import os.path from gtkmvc import View GLADE_PATH = "./" class ExampleView (View): """The application view. Contains only the main window1 tree.""" builder = os.path.join(GLADE_PATH, "example.glade") top = "window1" def set_msg(self, msg): self['label_text'].set_text(msg) self['label_text_len'].set_text(str(len(msg))) return pass # end of class Class ``ExampleView`` extends the generic ``View`` class, which performs most of the job, as described above. Class members ``builder`` and ``top`` are used instead of calling ``View`` constructor directly. Controller ---------- Class ``ExampleController`` contains the *GUI logic* of the application. The controller handles two signals and the observable property notification. Signals are the ``destroy`` event, invoked when the application quits, and the ``on_button1_clicked``, fired when ``button1`` is pressed. :: # file ctrl.py from gtk import main_quit from gtkmvc import Controller class ExampleController(Controller): """The only one controller. Handles the button clicked signal, and notifications about one observable property.""" def register_view(self, view): # Connects the exiting signal: view.get_top_widget().connect("destroy", main_quit) return # Signal def on_button1_clicked(self, button): """Handles the signal clicked for button1. Changes the model.""" self.model.set_next_message() return # Observables notifications @Controller.observe("message_index", assign=True) def value_change(self, model, name, info): """The model is changed and the view must be updated""" msg = self.model.get_message(info.new) self.view.set_msg(msg) return pass # end of class The ``destroy`` signal is connected when the View registers itself inside the controller, by using the method override of ``register_view``. Method ``on_button1_clicked`` calls a method inside the model which changes a part of the state inside the model. Since that part of the state is an observable property, the associated observer (which is the controller itself) is notified of the modification, by calling method ``value_change``. This method updates the view connected to the controller. The Launcher (main) ------------------- The :func:`main` code creates a `(m,v,c)` triple and launches :func:`gtk.main()`. All the rest is cosmetics. :: import gtk from model import ExampleModel from ctrl import ExampleController from view import ExampleView import gtkmvc def check_requirements(): gtkmvc.require("1.99.1") return def setup_env(development_state=False): # This is how developers should set gtkmvc logging level (by # default debugging info is not shown): if development_state: import logging logging.getLogger("gtkmvc").setLevel(logging.DEBUG) pass return def main(): m = ExampleModel() v = ExampleView() c = ExampleController(m, v) gtk.main() return if __name__ == "__main__": check_requirements() setup_env(development_state=True) main() pass