094186b9b92ccfe6fdebd297217c196bbb52779e
[hermes] / package / src / org / bleb / 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, glib
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         try:
100             self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
101             window.realize()
102             window.window.set_back_pixmap(self._background, False)
103         except glib.GError, e:
104             print "Couldn't find background:", e.message
105     
106       
107     # -----------------------------------------------------------------------
108     def add_menu_action(self, title, window = None):
109         '''Add a menu action to the given (or main) window. Once add_menu_action()
110            has been called with all the properties, 'self.menu.show_all()' should be
111            called.
112         
113            @param title The label of the action, and used to compute the callback
114                         method. This should be the UN-i18n version: gettext is used
115                         on the value.'''
116         
117         if not window:
118             window = self.main_window
119           
120         if not self.menu:
121             if _have_hildon:
122                 self.menu = hildon.AppMenu()
123                 window.set_app_menu(self.menu)
124             else:
125                 raise Exception("Menu needs to be created, and no Hildon present")
126             
127         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
128         button.set_label(_(title))
129         button.connect("clicked", self.callback, title)
130         self.menu.append(button)
131     
132     
133     # -----------------------------------------------------------------------
134     def run(self):
135         '''Once the application has been initialised, this will show the main window
136            and run the mainloop.'''
137          
138         self.main_window.show_all()
139         gtk.main()
140     
141     
142     # -----------------------------------------------------------------------
143     def _take_screenshot(self, event = None, data = None):
144         '''Used to provide a quick-loading screen.
145         
146            @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
147         
148         self.main_window.disconnect(self._expose_hid)
149         if not os.path.isfile("/home/user/.cache/launch/%s.pvr" % (self.dbus_name)):
150             gobject.timeout_add(80, hildon.hildon_gtk_window_take_screenshot, self.main_window, True)
151     
152       
153     # -----------------------------------------------------------------------
154     def callback(self, event, method):
155         '''Call a method on this object, using the given string to derive
156            the name. If no method is found, no action is taken.
157            
158            @param event Event which triggered the callback.
159            @param method String which will be lowercased to form a method
160                   called 'do_method'.'''
161         
162         method = re.sub('[^a-z0-9_]', '', method.lower())
163         getattr(self, "do_%s" % (method))(event.window)
164     
165       
166     # -----------------------------------------------------------------------
167     def new_checkbox(self, label, box = None):
168         '''Create a new checkbox, adding it to the given container.
169         
170            @param label Label for the checkbox.
171            @param box Optional container to add the created checkbox to.
172            @return The newly created checkbox.'''
173            
174         checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
175         checkbox.set_label(label)
176         if box:
177             box.add(checkbox)
178         return checkbox
179     
180     
181     # -----------------------------------------------------------------------
182     def new_indent(self, box):
183         '''Create an indent which can be used to show items related to each other.
184         
185            @param box Container to add the indent to.'''
186            
187         outer = gtk.HBox()
188         indent = gtk.VBox()
189         outer.pack_start(indent, padding=48)
190         box.add(outer)
191         return indent
192     
193     
194     # -----------------------------------------------------------------------
195     def new_input(self, label, box = None, password = False):
196         '''Create a new input with the given label, optionally adding it to a
197            container.
198            
199            @param label Text describing the purpose of the input field.
200            @param box Optional container to add the input to.
201            @param password Boolean indicating if the input is used for passwords.
202            @return The newly created input.'''
203            
204         input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
205         input.set_placeholder(label)
206         input.set_property('is-focus', False)
207         
208         if password:
209             input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
210         else:
211             input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
212           
213         if box:
214             box.add(input)
215         return input
216     
217     
218     # -----------------------------------------------------------------------
219     def link_control(self, checkbox, ctrl, box = None):
220         '''Link a checkbox to a control, such that the editability of the
221            control is determined by the checkbox state.
222            
223            @param checkbox Checkbox which will control the state.
224            @param ctrl Control to add.
225            @param box Optional container to add 'ctrl' to.
226            @return The added control.'''
227            
228         if box:
229             box.add(ctrl)
230           
231         self._sync_edit(checkbox, ctrl)
232         checkbox.connect('toggled', self._sync_edit, ctrl)
233         return ctrl
234     
235       
236     # -----------------------------------------------------------------------
237     def _sync_edit(self, checkbox, edit):
238         edit.set_property('sensitive', checkbox.get_active())
239     
240
241       
242 # -----------------------------------------------------------------------
243 class HildonMainScreenLayout():
244     '''Provides a mechanism for creating a traditional multi-button button
245        selection, as made popular by Maemo 5's Media Player, Clock, Application
246        Manager and HIG.
247        
248        This does *not* require Hildon, however.
249     '''
250
251     # ---------------------------------------------------------------------
252     def __init__(self, container, offset = 0.5):
253         '''Create a new layout.
254         
255            @param container Container to add layout to. If unspecified,
256                   the application's main window will be used.
257            @param offset The vertical offset for the buttons. If unspecified,
258                   they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
259                   
260         self._container = container
261         alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
262         self._box = gtk.HButtonBox()
263         alignment.add(self._box)
264         container.main_window.add(alignment)
265         self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
266
267       
268     # ---------------------------------------------------------------------
269     def add_button(self, title, subtitle = ''):
270         '''Add a button to the layout with the specified title. Upon clicking
271            the button, a method of the name 'do_title' will be invoked on the
272            main class.
273            
274            @param title Value of the button, and used to derive the callback method. This
275                         should be the UN-i18n version: gettext is used on the value.
276            @param subtitle An optional subtitle containing more information.'''
277         
278         if _have_hildon:         
279             button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
280                                  title = _(title), value = subtitle)
281         else:
282             button = gtk.Button(label = _(title))
283         
284         button.set_property('width-request', 250)
285         button.connect('clicked', self._container.callback, title)
286         self._box.add(button)
287