--- /dev/null
+#
+# WimpWorks (for Python) (c) Andrew Flegg 2009.
+# ~~~~~~~~~~~~~~~~~~~~~~ Released under the Artistic Licence.
+# http://www.bleb.org/
+
+import gtk, gobject
+import gnome.gconf
+import hildon, osso
+import traceback
+import time
+import re
+import thread
+import os.path
+
+# -- Work out environment...
+#
+try:
+ import hildon
+ _have_hildon = True
+except ImportError:
+ _have_hildon = False
+
+try:
+ import osso
+ _have_osso = True
+except ImportError:
+ _have_osso = False
+
+gobject.threads_init()
+
+# -- Main class...
+#
+class Wimpworks:
+ '''A framework for creating easy-to-use graphical user interfaces using
+ GTK+, Python, DBus and more.
+
+ This is the base class. It should be constructed with a DBus name
+ and a version.
+
+ Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+ Released under the Artistic Licence.'''
+
+
+ # -----------------------------------------------------------------------
+ def __init__(self, application, version = '1.0.0', dbus_name = None):
+ '''Constructor. Initialises the gconf connection, DBus, OSSO and more.
+
+ @param application User-facing name of the application.
+ @param version Version string of the application.
+ @param dbus_name Name to register with DBus. If unspecified, no
+ DBus registration will be performed.'''
+
+ self.gconf = gnome.gconf.client_get_default()
+ self.name = application
+ self.dbus_name = dbus_name
+ self.menu = None
+
+ if _have_hildon:
+ self.app = hildon.Program()
+ self.main_window = hildon.Window()
+ else:
+ self.app = None
+ self.main_window = gtk.Window()
+
+ gtk.set_application_name(application)
+ self.window.connect("delete-event", gtk.main_quit)
+
+ if _have_osso and dbus_name:
+ self.osso_context = osso.Context(dbus_name, version, False)
+
+ if self.app:
+ self.app.add_window(self.main_window)
+
+ if _have_hildon:
+ self._expose_hid = self.window.connect('expose-event', self._take_screenshot)
+
+
+ # -----------------------------------------------------------------------
+ def set_background(self, file, window = None):
+ '''Set the background of the given (or main) window to that contained in
+ 'file'.
+
+ @param file File name to set. If not an absolute path, typical application
+ directories will be checked.
+ @param window Window to set background of. If unset, will default to the
+ main application window.'''
+
+ file = "/opt/%s/share/%s" % (self.name, file) # TODO Handle other forms of path
+ if not window:
+ window = self.main_window
+
+ self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
+ window.realize()
+ window.window.set_back_pixmap(self._background, False)
+
+
+ # -----------------------------------------------------------------------
+ def add_menu_action(self, title, window = None):
+ '''Add a menu action to the given (or main) window. Once add_menu_action()
+ has been called with all the properties, 'self.menu.show_all()' should be
+ called.'''
+
+ if not window:
+ window = self.main_Window
+
+ if not self.menu:
+ if _have_hildon:
+ self.menu = hildon.AppMenu()
+ window.set_app_menu(self.menu)
+ else:
+ raise Exception("Menu needs to be created, and no Hildon present")
+
+ button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+ button.set_label(title)
+ button.connect("clicked", self.callback, title)
+ self.menu.append(button)
+
+
+ # -----------------------------------------------------------------------
+ def run(self):
+ '''Once the application has been initialised, this will show the main window
+ and run the mainloop.'''
+
+ self.window.show_all()
+ gtk.main()
+
+
+ # -----------------------------------------------------------------------
+ def _take_screenshot(self, event = None, data = None):
+ '''Used to provide a quick-loading screen.
+
+ @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
+
+ self.window.disconnect(self._expose_hid)
+ if not os.path.isfile("/home/user/.cache/launch/%s.pvr" % (self.dbus_name)):
+ gobject.timeout_add(80, hildon.hildon_gtk_window_take_screenshot, self.main_window, True)
+
+
+ # -----------------------------------------------------------------------
+ def callback(self, event, method):
+ '''Call a method on this object, using the given string to derive
+ the name. If no method is found, no action is taken.
+
+ @param event Event which triggered the callback.
+ @param method String which will be lowercased to form a method
+ called 'do_method'.'''
+
+ method = re.sub('[^a-z0-9_]', '', method.lower())
+ self.attr("do_%s" % (method))(event.window)
+
+
+ # -----------------------------------------------------------------------
+ def new_checkbox(self, label, box = None):
+ '''Create a new checkbox, adding it to the given container.
+
+ @param label Label for the checkbox.
+ @param box Optional container to add the created checkbox to.
+ @return The newly created checkbox.'''
+
+ checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+ checkbox.set_label(label)
+ if box:
+ box.add(checkbox)
+ return checkbox
+
+
+ # -----------------------------------------------------------------------
+ def new_indent(self, box):
+ '''Create an indent which can be used to show items related to each other.
+
+ @param box Container to add the indent to.'''
+
+ outer = gtk.HBox()
+ indent = gtk.VBox()
+ outer.pack_start(indent, padding=48)
+ box.add(outer)
+ return indent
+
+
+ # -----------------------------------------------------------------------
+ def new_input(self, label, box = None, password = False):
+ '''Create a new input with the given label, optionally adding it to a
+ container.
+
+ @param label Text describing the purpose of the input field.
+ @param box Optional container to add the input to.
+ @param password Boolean indicating if the input is used for passwords.
+ @return The newly created input.'''
+
+ input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
+ input.set_placeholder(label)
+ input.set_property('is-focus', False)
+
+ if password:
+ input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
+ else:
+ input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
+
+ if box:
+ box.add(input)
+ return input
+
+
+ # -----------------------------------------------------------------------
+ def link_control(self, checkbox, ctrl, box = None):
+ '''Link a checkbox to a control, such that the editability of the
+ control is determined by the checkbox state.
+
+ @param checkbox Checkbox which will control the state.
+ @param ctrl Control to add.
+ @param box Optional container to add 'ctrl' to.
+ @return The added control.'''
+
+ if box:
+ box.add(ctrl)
+
+ self._sync_edit(checkbox, ctrl)
+ checkbox.connect('toggled', self._sync_edit, ctrl)
+ return ctrl
+
+
+ # -----------------------------------------------------------------------
+ def _sync_edit(self, checkbox, edit):
+ edit.set_property('sensitive', checkbox.get_active())
+
+
+
+# -----------------------------------------------------------------------
+class HildonMainScreenLayout():
+ '''Provides a mechanism for creating a traditional multi-button button
+ selection, as made popular by Maemo 5's Media Player, Clock, Application
+ Manager and HIG.
+
+ This does *not* require Hildon, however.
+ '''
+
+ # ---------------------------------------------------------------------
+ def __init__(self, container, offset = 0.5):
+ '''Create a new layout.
+
+ @param container Container to add layout to. If unspecified,
+ the application's main window will be used.
+ @param offset The vertical offset for the buttons. If unspecified,
+ they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
+
+ print self
+ self._container = container
+ alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
+ self._box = gtk.HButtonBox()
+ alignment.add(self._box)
+ container.add(alignment)
+ self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
+
+
+ # ---------------------------------------------------------------------
+ def add_button(self, title, subtitle = ''):
+ '''Add a button to the layout with the specified title. Upon clicking
+ the button, a method of the name 'do_title' will be invoked on the
+ main class.
+
+ @param title Value of the button, and used to derive the callback method.
+ @param subtitle An optional subtitle containing more information.'''
+
+ if _have_hildon:
+ button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
+ title = title, value = subtitle)
+ else:
+ button = gtk.Button(title)
+
+ button.set_property('width-request', 250)
+ button.connect('clicked', self.wimpworks.callback, title)
+ self._box.add(button)