First attempt at splitting out main GUI code into WimpWorks
[hermes] / package / src / wimpworks.py
1 #
2 # WimpWorks (for Python)                (c) Andrew Flegg 2009.
3 # ~~~~~~~~~~~~~~~~~~~~~~                Released under the Artistic Licence.
4 #                                       http://www.bleb.org/
5
6 import gtk, gobject
7 import gnome.gconf
8 import hildon, osso
9 import traceback
10 import time
11 import re
12 import thread
13 import os.path
14
15 # -- Work out environment...
16 #
17 try:
18   import hildon
19   _have_hildon = True
20 except ImportError:
21   _have_hildon = False
22   
23 try:
24   import osso
25   _have_osso = True
26 except ImportError:
27   _have_osso = False
28
29 gobject.threads_init()
30
31 # -- Main class...
32 #
33 class Wimpworks:
34   '''A framework for creating easy-to-use graphical user interfaces using
35      GTK+, Python, DBus and more.
36      
37      This is the base class. It should be constructed with a DBus name
38      and a version.
39        
40      Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
41      Released under the Artistic Licence.'''
42
43
44   # -----------------------------------------------------------------------
45   def __init__(self, application, version = '1.0.0', dbus_name = None):
46     '''Constructor. Initialises the gconf connection, DBus, OSSO and more.
47     
48        @param application User-facing name of the application.
49        @param version Version string of the application.
50        @param dbus_name Name to register with DBus. If unspecified, no
51               DBus registration will be performed.'''
52     
53     self.gconf = gnome.gconf.client_get_default()
54     self.name = application
55     self.dbus_name = dbus_name
56     self.menu = None
57     
58     if _have_hildon:
59       self.app = hildon.Program()
60       self.main_window = hildon.Window()
61     else:
62       self.app = None
63       self.main_window = gtk.Window()
64       
65     gtk.set_application_name(application)
66     self.window.connect("delete-event", gtk.main_quit)
67     
68     if _have_osso and dbus_name:
69       self.osso_context = osso.Context(dbus_name, version, False)
70       
71     if self.app:
72       self.app.add_window(self.main_window)
73       
74     if _have_hildon:
75       self._expose_hid = self.window.connect('expose-event', self._take_screenshot)
76
77       
78   # -----------------------------------------------------------------------
79   def set_background(self, file, window = None):
80     '''Set the background of the given (or main) window to that contained in
81        'file'.
82        
83        @param file File name to set. If not an absolute path, typical application
84                    directories will be checked.
85        @param window Window to set background of. If unset, will default to the
86                      main application window.'''
87     
88     file = "/opt/%s/share/%s" % (self.name, file) # TODO Handle other forms of path
89     if not window:
90       window = self.main_window
91       
92     self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
93     window.realize()
94     window.window.set_back_pixmap(self._background, False)
95
96     
97   # -----------------------------------------------------------------------
98   def add_menu_action(self, title, window = None):
99     '''Add a menu action to the given (or main) window. Once add_menu_action()
100        has been called with all the properties, 'self.menu.show_all()' should be
101        called.'''
102
103     if not window:
104       window = self.main_Window
105       
106     if not self.menu:
107       if _have_hildon:
108         self.menu = hildon.AppMenu()
109         window.set_app_menu(self.menu)
110       else:
111         raise Exception("Menu needs to be created, and no Hildon present")
112         
113     button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
114     button.set_label(title)
115     button.connect("clicked", self.callback, title)
116     self.menu.append(button)
117
118
119   # -----------------------------------------------------------------------
120   def run(self):
121     '''Once the application has been initialised, this will show the main window
122        and run the mainloop.'''
123        
124     self.window.show_all()
125     gtk.main()
126
127
128   # -----------------------------------------------------------------------
129   def _take_screenshot(self, event = None, data = None):
130     '''Used to provide a quick-loading screen.
131     
132        @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
133     
134     self.window.disconnect(self._expose_hid)
135     if not os.path.isfile("/home/user/.cache/launch/%s.pvr" % (self.dbus_name)):
136       gobject.timeout_add(80, hildon.hildon_gtk_window_take_screenshot, self.main_window, True)
137
138     
139   # -----------------------------------------------------------------------
140   def callback(self, event, method):
141     '''Call a method on this object, using the given string to derive
142        the name. If no method is found, no action is taken.
143        
144        @param event Event which triggered the callback.
145        @param method String which will be lowercased to form a method
146               called 'do_method'.'''
147
148     method = re.sub('[^a-z0-9_]', '', method.lower())
149     self.attr("do_%s" % (method))(event.window)
150
151     
152   # -----------------------------------------------------------------------
153   def new_checkbox(self, label, box = None):
154     '''Create a new checkbox, adding it to the given container.
155     
156        @param label Label for the checkbox.
157        @param box Optional container to add the created checkbox to.
158        @return The newly created checkbox.'''
159        
160     checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
161     checkbox.set_label(label)
162     if box:
163       box.add(checkbox)
164     return checkbox
165
166
167   # -----------------------------------------------------------------------
168   def new_indent(self, box):
169     '''Create an indent which can be used to show items related to each other.
170     
171        @param box Container to add the indent to.'''
172        
173     outer = gtk.HBox()
174     indent = gtk.VBox()
175     outer.pack_start(indent, padding=48)
176     box.add(outer)
177     return indent
178
179  
180   # -----------------------------------------------------------------------
181   def new_input(self, label, box = None, password = False):
182     '''Create a new input with the given label, optionally adding it to a
183        container.
184        
185        @param label Text describing the purpose of the input field.
186        @param box Optional container to add the input to.
187        @param password Boolean indicating if the input is used for passwords.
188        @return The newly created input.'''
189        
190     input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
191     input.set_placeholder(label)
192     input.set_property('is-focus', False)
193     
194     if password:
195       input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
196     else:
197       input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
198       
199     if box:
200       box.add(input)
201     return input
202
203
204   # -----------------------------------------------------------------------
205   def link_control(self, checkbox, ctrl, box = None):
206     '''Link a checkbox to a control, such that the editability of the
207        control is determined by the checkbox state.
208        
209        @param checkbox Checkbox which will control the state.
210        @param ctrl Control to add.
211        @param box Optional container to add 'ctrl' to.
212        @return The added control.'''
213        
214     if box:
215       box.add(ctrl)
216       
217     self._sync_edit(checkbox, ctrl)
218     checkbox.connect('toggled', self._sync_edit, ctrl)
219     return ctrl
220   
221     
222   # -----------------------------------------------------------------------
223   def _sync_edit(self, checkbox, edit):
224     edit.set_property('sensitive', checkbox.get_active())
225     
226
227       
228 # -----------------------------------------------------------------------
229 class HildonMainScreenLayout():
230     '''Provides a mechanism for creating a traditional multi-button button
231        selection, as made popular by Maemo 5's Media Player, Clock, Application
232        Manager and HIG.
233        
234        This does *not* require Hildon, however.
235     '''
236
237     # ---------------------------------------------------------------------
238     def __init__(self, container, offset = 0.5):
239       '''Create a new layout.
240       
241          @param container Container to add layout to. If unspecified,
242                 the application's main window will be used.
243          @param offset The vertical offset for the buttons. If unspecified,
244                 they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
245                 
246       print self
247       self._container = container
248       alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
249       self._box = gtk.HButtonBox()
250       alignment.add(self._box)
251       container.add(alignment)
252       self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
253
254       
255     # ---------------------------------------------------------------------
256     def add_button(self, title, subtitle = ''):
257       '''Add a button to the layout with the specified title. Upon clicking
258          the button, a method of the name 'do_title' will be invoked on the
259          main class.
260          
261          @param title Value of the button, and used to derive the callback method.
262          @param subtitle An optional subtitle containing more information.'''
263
264     if _have_hildon:         
265       button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
266                              title = title, value = subtitle)
267     else:
268       button = gtk.Button(title)
269
270     button.set_property('width-request', 250)
271     button.connect('clicked', self.wimpworks.callback, title)
272     self._box.add(button)