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