Add about dialogue
[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         self.version = version
60         
61         if _have_gconf:
62             self.gconf = gnome.gconf.client_get_default()
63         
64         if _have_hildon:
65             self.app = hildon.Program()
66             self.main_window = hildon.Window()
67             gtk.set_application_name(application)
68         else:
69             self.app = None
70             self.main_window = gtk.Window()
71         
72         self.main_window.set_title(application)
73         self.main_window.connect("delete-event", gtk.main_quit)
74         
75         if _have_osso and dbus_name:
76             self.osso_context = osso.Context(dbus_name, version, False)
77           
78         if self.app:
79             self.app.add_window(self.main_window)
80           
81         if _have_hildon:
82             self._expose_hid = self.main_window.connect('expose-event', self._take_screenshot)
83     
84         
85     # -----------------------------------------------------------------------
86     def set_background(self, file, window = None):
87         '''Set the background of the given (or main) window to that contained in
88            'file'.
89            
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.'''
94         
95         # TODO Handle other forms of path
96         file = "/opt/%s/share/%s" % (re.sub('[^a-z0-9_]', '', self.name.lower()), file)
97         if not window:
98             window = self.main_window
99
100         try:
101             self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
102             window.realize()
103             window.window.set_back_pixmap(self._background, False)
104         except glib.GError, e:
105             print "Couldn't find background:", e.message
106     
107       
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
112            called.
113         
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
116                         on the value.'''
117         
118         if not window:
119             window = self.main_window
120           
121         if not self.menu:
122             if _have_hildon:
123                 self.menu = hildon.AppMenu()
124                 window.set_app_menu(self.menu)
125             else:
126                 raise Exception("Menu needs to be created, and no Hildon present")
127             
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)
132     
133     
134     # -----------------------------------------------------------------------
135     def run(self):
136         '''Once the application has been initialised, this will show the main window
137            and run the mainloop.'''
138          
139         self.main_window.show_all()
140         gtk.main()
141     
142     
143     # -----------------------------------------------------------------------
144     def _take_screenshot(self, event = None, data = None):
145         '''Used to provide a quick-loading screen.
146         
147            @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
148         
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)
152     
153       
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.
158            
159            @param event Event which triggered the callback.
160            @param method String which will be lowercased to form a method
161                   called 'do_method'.'''
162         
163         method = re.sub('[^a-z0-9_]', '', method.lower())
164         getattr(self, "do_%s" % (method))(event.window)
165     
166       
167     # -----------------------------------------------------------------------
168     def new_checkbox(self, label, box = None):
169         '''Create a new checkbox, adding it to the given container.
170         
171            @param label Label for the checkbox.
172            @param box Optional container to add the created checkbox to.
173            @return The newly created checkbox.'''
174            
175         checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
176         checkbox.set_label(label)
177         if box:
178             box.add(checkbox)
179         return checkbox
180     
181     
182     # -----------------------------------------------------------------------
183     def new_indent(self, box):
184         '''Create an indent which can be used to show items related to each other.
185         
186            @param box Container to add the indent to.'''
187            
188         outer = gtk.HBox()
189         indent = gtk.VBox()
190         outer.pack_start(indent, padding=48)
191         box.add(outer)
192         return indent
193     
194     
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
198            container.
199            
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.'''
204            
205         input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
206         input.set_placeholder(label)
207         input.set_property('is-focus', False)
208         
209         if password:
210             input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
211         else:
212             input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
213           
214         if box:
215             box.add(input)
216         return input
217     
218     
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.
223            
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.'''
228            
229         if box:
230             box.add(ctrl)
231           
232         self._sync_edit(checkbox, ctrl)
233         checkbox.connect('toggled', self._sync_edit, ctrl)
234         return ctrl
235     
236       
237     # -----------------------------------------------------------------------
238     def _sync_edit(self, checkbox, edit):
239         edit.set_property('sensitive', checkbox.get_active())
240     
241
242       
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
247        Manager and HIG.
248        
249        This does *not* require Hildon, however.
250     '''
251
252     # ---------------------------------------------------------------------
253     def __init__(self, container, offset = 0.5):
254         '''Create a new layout.
255         
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).'''
260                   
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)
267
268       
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
273            main class.
274            
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.'''
278         
279         if _have_hildon:         
280             button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
281                                  title = _(title), value = subtitle)
282         else:
283             button = gtk.Button(label = _(title))
284         
285         button.set_property('width-request', 250)
286         button.connect('clicked', self._container.callback, title)
287         self._box.add(button)
288