Whyteboard - Help for developers Version: 1.0 Last updated: 1 May 2010 --------------------------------- This document serves to help a developer who wishes to contribute / understand Whyteboard's code. You'll probably need some knowledge of Python + the wxPython GUI toolkit, as Whyteboard is developed with these, using some advanced-ish concepts. ---------------------------------- The main class is GUI in gui.py. It consists of a wx.Frame - a "window" to the general end-user, containing the title and close/minimise button etc. It has references to its contained panels - the control panel on the left, menu bar, tool bar, the canvas itself and the panel on the right. The gui contains the global event bindings, e.g. undo/redo, copy/paste. It dispatches function calls to relevant classes, for example undo will tell the current tab to undo its last operation, Or, saving a file will involve prompting the user for the location to save the file to, and then call a function in the utility class to actually save the data. Since the program is tabbed, the gui keeps a reference to its active canvas. The gui updates its canvas reference when opening/closing/changing tabs. Often, we want to perform operations only on the current tab, e.g paste an image into a tab, export the current tab as an image and so on. Thus knowing the current tab is useful. The Canvas contains the drawable canvas - a scrollable window. It has its own event bindings and no title bar. Multiple Canvases will exists at a given time, and the GUI presents these views to the user through its tab list, thumbnails panel and the Notes tree. The Canvas class contains a list of shapes that has been drawn upon it. Each instance of this class will have its own unique shapes list. This is how each Whyteboard tab can perform its own undo and redo without affecting any other Whyteboard instances. Tools.py defines the shapes - different drawable tools that the program uses. At the bottom of the file is a list named "items" -- this is the list of tools the user can draw with. From this, buttons, icons, translated names, hotkeys are generated. Here are some interesting methods/properties each shape provides: - name - translated name - hotkey - keyboard shortcut key - left_up() - called when drawing - left button pressed - left_down() - left button up - double_click() - double clicked - right_up() - right button up - motion() - mouse motion while left mouse button is held down - hit_test() - a check if an x/y point is "inside" the shape - load() - called when loading the shape - save() - when saving - properties() - a text description of the shapes' properties, e.g. x/y coords - preview() - tool preview drawn in the left panel These allow you to customise your tools easily. There is an object hierarchy, as so: Tool | --------------------------------------------- | | | | Eyedrop OverlayShape Select Media | ------------------------------------------------------------------------- | | | | | | Image Rectangle Circle Polygon Text Line | | | | -------------------------------------- Pen Note Arrow | | | | BitmapSelect RoundedRectangle Ellipse ---------- | | Eraser Highlighter Many of the classes at the bottom of the list simply extend the base classes' behaviour - nested deeply in the hierarchy, they have many inherited methods. Most of these classes invoke their superclasses' methods. For example, draw() in OverlayShape will draw the shape overlayed on the canvas, meaning it is not permanently drawn on the canvas as the user moves their mouse around. Note needs to extend this behaviour by also drawing a background to the shape -- so first calls draw() on its superclass, Text, which will draw the text, and invokes draw() on OverlayShape. So, to create your own tool you'd need to extend from an existing class (most likely OverlayShape) and override the appropriate methods, remembering to call super() on its parent. Also, add your class to the items list at the bottom of tools.py, making sure the filename defined in its "icon" attribute exists under /images/tools/[icon].png The file "meta" contains read-only metadata used by the program. This may seem a nasty way of having global data, but in Python, but the values are read-only and the module isn't tied to any classes. It contains common data such as the version number, configuration scheme, list of translators, whether the host system supports transparency etc. ---------------------------------- The source files are organised as follows: lib/ - third party modules fakewidgets/ - mock GUI classes for unit testing canvas.py - canvas class dialogs.py - dialogs and their events, e.g. Progresss bar / resize / history event_ids.py - custom wxPython IDs for events functions.py - misc. stand-alone functions that can be used anywhere gui.py - main program frame, wx.App meta.py - meta data, meant to be dependent on wxPython only panels.py - control panel, notes, thumnbails preferences.py - preferences dialog tools.py - class definitions for the drawing tools unitests.py - unit tests utility.py - a class the GUI references that performs saving/loading files, getting file paths and misc. things ------------------------------------------ CODING PRACTICES * Try and follow Python's Pep 8 guide http://www.python.org/dev/peps/pep-0008 * 80 characters per line can be hard to use at times and can be broken in some cases. * Name methods like_this, not AsSuch. As wxPython uses CamelCased method names, it easily allows you to differentiate between wx and Whyteboard methods. Very useful for dialogs and custom panels. * Try and write a unit test for your functionality to assert it works correctly * Learn to use PubSub. It will help to decouple objects and even modules' dependencies on each other which is always a very good thing, and allows for easier testing * Be wary of performance issues when performing loops. * Python has a high overhead for functions calls so please be wary of that, too. * Translate every string like _("so"). Make sure to use variable placeholders instead of string concatination as this gives translators more context _("The value is %i and its name is %s") % (x, name) is better than _("The value is ") + x + _(" and its name is ") + name