Add Eclipse project settings and make indent-level consistent
[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