2 # WimpWorks (for Python) (c) Andrew Flegg 2009.
3 # ~~~~~~~~~~~~~~~~~~~~~~ Released under the Artistic Licence.
7 import gtk, gobject, glib
12 # -- Work out environment...
32 gobject.threads_init()
37 '''A framework for creating easy-to-use graphical user interfaces using
38 GTK+, Python, DBus and more.
40 This is the base class. It should be constructed with a DBus name
43 Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
44 Released under the Artistic Licence.'''
47 # -----------------------------------------------------------------------
48 def __init__(self, application, version = '1.0.0', dbus_name = None):
49 '''Constructor. Initialises the gconf connection, DBus, OSSO and more.
51 @param application User-facing name of the application.
52 @param version Version string of the application.
53 @param dbus_name Name to register with DBus. If unspecified, no
54 DBus registration will be performed.'''
56 self.name = application
57 self.dbus_name = dbus_name
59 self.version = version
62 self.gconf = gnome.gconf.client_get_default()
65 self.app = hildon.Program()
66 self.main_window = hildon.Window()
67 gtk.set_application_name(application)
70 self.main_window = gtk.Window()
72 self.main_window.set_title(application)
73 self.main_window.connect("delete-event", gtk.main_quit)
75 if _have_osso and dbus_name:
76 self.osso_context = osso.Context(dbus_name, version, False)
79 self.app.add_window(self.main_window)
82 self._expose_hid = self.main_window.connect('expose-event', self._take_screenshot)
85 # -----------------------------------------------------------------------
86 def set_background(self, file, window = None):
87 '''Set the background of the given (or main) window to that contained in
90 @param file File name to set. If not an absolute path, typical application
91 directories will be checked.
92 @param window Window to set background of. If unset, will default to the
93 main application window.'''
95 # TODO Handle other forms of path
96 file = "/opt/%s/share/%s" % (re.sub('[^a-z0-9_]', '', self.name.lower()), file)
98 window = self.main_window
101 self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
103 window.window.set_back_pixmap(self._background, False)
104 except glib.GError, e:
105 print "Couldn't find background:", e.message
108 # -----------------------------------------------------------------------
109 def add_menu_action(self, title, window = None):
110 '''Add a menu action to the given (or main) window. Once add_menu_action()
111 has been called with all the properties, 'self.menu.show_all()' should be
114 @param title The label of the action, and used to compute the callback
115 method. This should be the UN-i18n version: gettext is used
119 window = self.main_window
123 self.menu = hildon.AppMenu()
124 window.set_app_menu(self.menu)
126 raise Exception("Menu needs to be created, and no Hildon present")
128 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
129 button.set_label(_(title))
130 button.connect("clicked", self.callback, title)
131 self.menu.append(button)
134 # -----------------------------------------------------------------------
136 '''Once the application has been initialised, this will show the main window
137 and run the mainloop.'''
139 self.main_window.show_all()
143 # -----------------------------------------------------------------------
144 def _take_screenshot(self, event = None, data = None):
145 '''Used to provide a quick-loading screen.
147 @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
149 self.main_window.disconnect(self._expose_hid)
150 if not os.path.isfile("/home/user/.cache/launch/%s.pvr" % (self.dbus_name)):
151 gobject.timeout_add(80, hildon.hildon_gtk_window_take_screenshot, self.main_window, True)
154 # -----------------------------------------------------------------------
155 def callback(self, event, method):
156 '''Call a method on this object, using the given string to derive
157 the name. If no method is found, no action is taken.
159 @param event Event which triggered the callback.
160 @param method String which will be lowercased to form a method
161 called 'do_method'.'''
163 method = re.sub('[^a-z0-9_]', '', method.lower())
164 getattr(self, "do_%s" % (method))(event.window)
167 # -----------------------------------------------------------------------
168 def new_checkbox(self, label, box = None):
169 '''Create a new checkbox, adding it to the given container.
171 @param label Label for the checkbox.
172 @param box Optional container to add the created checkbox to.
173 @return The newly created checkbox.'''
175 checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
176 checkbox.set_label(label)
182 # -----------------------------------------------------------------------
183 def new_indent(self, box):
184 '''Create an indent which can be used to show items related to each other.
186 @param box Container to add the indent to.'''
190 outer.pack_start(indent, padding=48)
195 # -----------------------------------------------------------------------
196 def new_input(self, label, box = None, password = False):
197 '''Create a new input with the given label, optionally adding it to a
200 @param label Text describing the purpose of the input field.
201 @param box Optional container to add the input to.
202 @param password Boolean indicating if the input is used for passwords.
203 @return The newly created input.'''
205 input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
206 input.set_placeholder(label)
207 input.set_property('is-focus', False)
210 input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
212 input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
219 # -----------------------------------------------------------------------
220 def link_control(self, checkbox, ctrl, box = None):
221 '''Link a checkbox to a control, such that the editability of the
222 control is determined by the checkbox state.
224 @param checkbox Checkbox which will control the state.
225 @param ctrl Control to add.
226 @param box Optional container to add 'ctrl' to.
227 @return The added control.'''
232 self._sync_edit(checkbox, ctrl)
233 checkbox.connect('toggled', self._sync_edit, ctrl)
237 # -----------------------------------------------------------------------
238 def _sync_edit(self, checkbox, edit):
239 edit.set_property('sensitive', checkbox.get_active())
243 # -----------------------------------------------------------------------
244 class HildonMainScreenLayout():
245 '''Provides a mechanism for creating a traditional multi-button button
246 selection, as made popular by Maemo 5's Media Player, Clock, Application
249 This does *not* require Hildon, however.
252 # ---------------------------------------------------------------------
253 def __init__(self, container, offset = 0.5):
254 '''Create a new layout.
256 @param container Container to add layout to. If unspecified,
257 the application's main window will be used.
258 @param offset The vertical offset for the buttons. If unspecified,
259 they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
261 self._container = container
262 alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
263 self._box = gtk.HButtonBox()
264 alignment.add(self._box)
265 container.main_window.add(alignment)
266 self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
269 # ---------------------------------------------------------------------
270 def add_button(self, title, subtitle = ''):
271 '''Add a button to the layout with the specified title. Upon clicking
272 the button, a method of the name 'do_title' will be invoked on the
275 @param title Value of the button, and used to derive the callback method. This
276 should be the UN-i18n version: gettext is used on the value.
277 @param subtitle An optional subtitle containing more information.'''
280 button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
281 title = _(title), value = subtitle)
283 button = gtk.Button(label = _(title))
285 button.set_property('width-request', 250)
286 button.connect('clicked', self._container.callback, title)
287 self._box.add(button)