summaryrefslogtreecommitdiff
path: root/trunk/HACKING
blob: cafab2993bf713697c8df5253b310fec75ae6b7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
HACKING Dimbola for fun and very little profit
==============================================


Introduction
------------

This file has some notes about how Dimbola should be developed.

All the generally accepted good Python coding stuff applies: PEP8 
formatting, using only spaces and no tabs for indentation, etc. We
cover here only things specific to the Dimbola project.


Overview of the code base
-------------------------

* dimbola-gtk -- the main program; this is intended to be a one-liner

* dimbola/*.py -- Python package with the core of the app

* dimbola/plugins/*_plugin.py -- plugins that come with the core app;
  as much as possible is put into plugins

* dimbola/ui.ui and dimbola/plugins/*.ui -- glade/GtkBuilder files; these
  are loaded automatically

* dimbola/*_tests.py and dimbola/plugins/*_tests.py -- unit tests for
  the corresponding files


Development priorities
----------------------

The main priority should always be to fix bugs. Each release should
fix all bugs, if at all possible. It is obvious that this is not
always possible, but it is the goal anyway. Better to attempt something
really good and almost get it than to play it safe.


Version numbering
-----------------

The canonical location of the version number is in dimbola/__init__.py
as the version variable. setup.py will extract it from there. ui.py will 
use dimbola.version to set the version number in the Help/About dialog
box.

Version number until 1.0 will be as follows:

    0.0.x       pre-alpha versions, not intended to be usable at all
    0.x.0       alpha and beta versions; these are intended to be usable,
                though they are probably really buggy
    1.0.0       first release intended to be usable by a non-hacker
    
Eventually, a ROADMAP file will be written to specify what 1.0.0 should
contain.


Writing plugins
---------------

To write a plugin, import the dimbola package, and subclass dimbola.Plugin.
The initializer MUST have the following signature:

    def __init__(self, mwc):
    
To use the plugin, put it in a file in dimbola/plugins/foo_plugin.py.

Plugins may access the MainWindowController's attributes and methods.

When a Plugin subclass is instantiated, it MUST NOT cause any side
effects: it must not modify the user interface, connect to signals,
or whatever. All of that must be done in the enable() method, and
un-done in the disable() method. This is necessary so that the
user may enable and disable any installed plugin.

Plugins may add new items to menus via MWC.add_to_menu. The UI file
needs to specify a name for the menu widget, and this name is used
with add_to_menu. Additionally, the menu may have separators whose
names begin with prepend_separator or append_separator, and add_to_menu
will put the new menu item before or after such a separator.

To remove the menu item (when the plugin is disabled), the 
MWC.remove_from_menu method may be used.

Similarily for sidebar sections: see MWC.add_to_sidebar and
MWC.remove_from_sidebar.

Plugins may have their own user interface definition files. For
a plugin named foo_plugin.py, the file foo.ui is loaded automatically,
if it exists. The plugin may access the widgets via 
MainWindowController.widgets.


Signals and hooks
-----------------

We try use GObject signals for communicating between parts of
the code. This decouples different modules, which is a good thing.
For example, the thumbnail grid is implemented using 
model/view/controller. When the list of photos in the model
changes, the view must redraw itself. Rather than having the model
call a method on the view, the view connects to a signal in the
model. Thus, the model need not know anything about the view.
Indeed, other things than the view can make use of the same
signal.

In order to connect to a signal, you need to know the object the
signal applies to. This is awkward when a plugin provides the
object and the signal. We work around this by adding the signal
to a well-known object, the MainWindowController, using the
new_hook method.

Thus, in Dimbola, a hook is a custom signal added to MWC.


Unit tests and coverage
-----------------------

The goal is to make Dimbola a very good program. Part of that is to make
it very reliable, and the way to get there is to have an automated unit test
suite with very good coverage.

Currently, there is abysmal coverage. The current code was written in haste,
to get a proof-of-concept prototype done very quickly. Writing tests would
have slowed things down.

The strategy to get to good coverage is as follows: all of the current
GUI code exists in dimbola/db.py and dimbola/ui.py. db.py may get some
unit tests later on, but ui.py is going to be test-less. However, as
much code as possible will be broken out of it, into other modules, and
those modules will have unit tests. The goal is to have those other
modules have 100% statement coverage.

Almost all the code will be called in response to GTK+ events and signals,
from callback functions. The general design principle here, as far as
testing is concerned, is that the actual callback function will be not
be tested, but it will be as simple as possible and call functions
elsewhere to do the actual work. Those functions will be unit tested.


Using Glade
-----------

The UI is defined using Glade and GtkBuilder. Everything that can
reasonably be done in Glade shall be done in Glade.

Widgets in the .glade file and their signal callbacks will be
connected automatically based on a naming convention. For a widget
named foo, and its signal named bar, a callback will be connected
automatically if the code has a method called on_foo_bar in a relevant
controller object.

This means the .glade file should not have any signal handlers defined.

Widgets that do not have callbacks can use whatever name Glade gives
them by default (e.g., "textview123"). If a callback is needed, the
widget should be renamed in a sensible way that indicates its use
(e.g., "photo_tags_textview").

See dimbola/gtkapp.py for details on the magic.


Background processing
---------------------

Threads are evil, at least in the context of Python and GTK+. We do not
use (explicit) threads in Dimbola. Instead, we use glib.idle_add and
the Python standard library module multiprocessing. The latter is wrapped
inside the BackgroundManager and BackgroundJob classes; see the
dimbola/bgjobs.py module.