Polishing things up in prep for a 1.0
[gonvert] / src / hildonize.py
1 #!/usr/bin/env python
2
3 """
4 Open Issues
5         @bug Buttons are too small
6 """
7
8
9 import gobject
10 import gtk
11 import dbus
12
13
14 class _NullHildonModule(object):
15         pass
16
17
18 try:
19         import hildon as _hildon
20         hildon  = _hildon # Dumb but gets around pyflakiness
21 except (ImportError, OSError):
22         hildon = _NullHildonModule
23
24
25 IS_HILDON_SUPPORTED = hildon is not _NullHildonModule
26
27
28 class _NullHildonProgram(object):
29
30         def add_window(self, window):
31                 pass
32
33
34 def _hildon_get_app_class():
35         return hildon.Program
36
37
38 def _null_get_app_class():
39         return _NullHildonProgram
40
41
42 try:
43         hildon.Program
44         get_app_class = _hildon_get_app_class
45 except AttributeError:
46         get_app_class = _null_get_app_class
47
48
49 def _hildon_set_application_title(window, title):
50         pass
51
52
53 def _null_set_application_title(window, title):
54         window.set_title(title)
55
56
57 if IS_HILDON_SUPPORTED:
58         set_application_title = _hildon_set_application_title
59 else:
60         set_application_title = _null_set_application_title
61
62
63 def _fremantle_hildonize_window(app, window):
64         oldWindow = window
65         newWindow = hildon.StackableWindow()
66         oldWindow.get_child().reparent(newWindow)
67         app.add_window(newWindow)
68         return newWindow
69
70
71 def _hildon_hildonize_window(app, window):
72         oldWindow = window
73         newWindow = hildon.Window()
74         oldWindow.get_child().reparent(newWindow)
75         app.add_window(newWindow)
76         return newWindow
77
78
79 def _null_hildonize_window(app, window):
80         return window
81
82
83 try:
84         hildon.StackableWindow
85         hildonize_window = _fremantle_hildonize_window
86 except AttributeError:
87         try:
88                 hildon.Window
89                 hildonize_window = _hildon_hildonize_window
90         except AttributeError:
91                 hildonize_window = _null_hildonize_window
92
93
94 def _fremantle_hildonize_menu(window, gtkMenu, buttons):
95         appMenu = hildon.AppMenu()
96         for button in buttons:
97                 appMenu.append(button)
98         window.set_app_menu(appMenu)
99         gtkMenu.get_parent().remove(gtkMenu)
100         return appMenu
101
102
103 def _hildon_hildonize_menu(window, gtkMenu, ignoredButtons):
104         hildonMenu = gtk.Menu()
105         for child in gtkMenu.get_children():
106                 child.reparent(hildonMenu)
107         window.set_menu(hildonMenu)
108         gtkMenu.destroy()
109         return hildonMenu
110
111
112 def _null_hildonize_menu(window, gtkMenu, ignoredButtons):
113         return gtkMenu
114
115
116 try:
117         hildon.AppMenu
118         GTK_MENU_USED = False
119         IS_FREMANTLE_SUPPORTED = True
120         hildonize_menu = _fremantle_hildonize_menu
121 except AttributeError:
122         GTK_MENU_USED = True
123         IS_FREMANTLE_SUPPORTED = False
124         if IS_HILDON_SUPPORTED:
125                 hildonize_menu = _hildon_hildonize_menu
126         else:
127                 hildonize_menu = _null_hildonize_menu
128
129
130 def _hildon_set_button_auto_selectable(button):
131         button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT)
132
133
134 def _null_set_button_auto_selectable(button):
135         pass
136
137
138 try:
139         hildon.HILDON_SIZE_AUTO_HEIGHT
140         gtk.Button.set_theme_size
141         set_button_auto_selectable = _hildon_set_button_auto_selectable
142 except AttributeError:
143         set_button_auto_selectable = _null_set_button_auto_selectable
144
145
146 def _hildon_set_button_finger_selectable(button):
147         button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT)
148
149
150 def _null_set_button_finger_selectable(button):
151         pass
152
153
154 try:
155         hildon.HILDON_SIZE_FINGER_HEIGHT
156         gtk.Button.set_theme_size
157         set_button_finger_selectable = _hildon_set_button_finger_selectable
158 except AttributeError:
159         set_button_finger_selectable = _null_set_button_finger_selectable
160
161
162 def _hildon_set_button_thumb_selectable(button):
163         button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT)
164
165
166 def _null_set_button_thumb_selectable(button):
167         pass
168
169
170 try:
171         hildon.HILDON_SIZE_THUMB_HEIGHT
172         gtk.Button.set_theme_size
173         set_button_thumb_selectable = _hildon_set_button_thumb_selectable
174 except AttributeError:
175         set_button_thumb_selectable = _null_set_button_thumb_selectable
176
177
178 def _hildon_set_cell_thumb_selectable(renderer):
179         renderer.set_property("scale", 1.5)
180
181
182 def _null_set_cell_thumb_selectable(renderer):
183         pass
184
185
186 if IS_HILDON_SUPPORTED:
187         set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable
188 else:
189         set_cell_thumb_selectable = _null_set_cell_thumb_selectable
190
191
192 def _fremantle_show_information_banner(parent, message):
193         hildon.hildon_banner_show_information(parent, "", message)
194
195
196 def _hildon_show_information_banner(parent, message):
197         hildon.hildon_banner_show_information(parent, None, message)
198
199
200 def _null_show_information_banner(parent, message):
201         pass
202
203
204 if IS_FREMANTLE_SUPPORTED:
205         show_information_banner = _fremantle_show_information_banner
206 else:
207         try:
208                 hildon.hildon_banner_show_information
209                 show_information_banner = _hildon_show_information_banner
210         except AttributeError:
211                 show_information_banner = _null_show_information_banner
212
213
214 def _fremantle_show_busy_banner_start(parent, message):
215         hildon.hildon_gtk_window_set_progress_indicator(parent, True)
216         return parent
217
218
219 def _fremantle_show_busy_banner_end(banner):
220         hildon.hildon_gtk_window_set_progress_indicator(banner, False)
221
222
223 def _hildon_show_busy_banner_start(parent, message):
224         return hildon.hildon_banner_show_animation(parent, None, message)
225
226
227 def _hildon_show_busy_banner_end(banner):
228         banner.destroy()
229
230
231 def _null_show_busy_banner_start(parent, message):
232         return None
233
234
235 def _null_show_busy_banner_end(banner):
236         assert banner is None
237
238
239 try:
240         hildon.hildon_gtk_window_set_progress_indicator
241         show_busy_banner_start = _fremantle_show_busy_banner_start
242         show_busy_banner_end = _fremantle_show_busy_banner_end
243 except AttributeError:
244         try:
245                 hildon.hildon_banner_show_animation
246                 show_busy_banner_start = _hildon_show_busy_banner_start
247                 show_busy_banner_end = _hildon_show_busy_banner_end
248         except AttributeError:
249                 show_busy_banner_start = _null_show_busy_banner_start
250                 show_busy_banner_end = _null_show_busy_banner_end
251
252
253 def _hildon_hildonize_text_entry(textEntry):
254         textEntry.set_property('hildon-input-mode', 7)
255
256
257 def _null_hildonize_text_entry(textEntry):
258         pass
259
260
261 if IS_HILDON_SUPPORTED:
262         hildonize_text_entry = _hildon_hildonize_text_entry
263 else:
264         hildonize_text_entry = _null_hildonize_text_entry
265
266
267 def _hildon_mark_window_rotatable(window):
268         # gtk documentation is unclear whether this does a "=" or a "|="
269         window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT)
270
271
272 def _null_mark_window_rotatable(window):
273         pass
274
275
276 try:
277         hildon.HILDON_PORTRAIT_MODE_SUPPORT
278         mark_window_rotatable = _hildon_mark_window_rotatable
279 except AttributeError:
280         mark_window_rotatable = _null_mark_window_rotatable
281
282
283 def _hildon_window_to_portrait(window):
284         # gtk documentation is unclear whether this does a "=" or a "|="
285         window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT)
286
287
288 def _hildon_window_to_landscape(window):
289         # gtk documentation is unclear whether this does a "=" or a "&= ~"
290         window.unset_flags(hildon.HILDON_PORTRAIT_MODE_REQUEST)
291
292
293 def _null_window_to_portrait(window):
294         pass
295
296
297 def _null_window_to_landscape(window):
298         pass
299
300
301 try:
302         hildon.HILDON_PORTRAIT_MODE_SUPPORT
303         hildon.HILDON_PORTRAIT_MODE_REQUEST
304
305         window_to_portrait = _hildon_window_to_portrait
306         window_to_landscape = _hildon_window_to_landscape
307 except AttributeError:
308         window_to_portrait = _null_window_to_portrait
309         window_to_landscape = _null_window_to_landscape
310
311
312 def get_device_orientation():
313         bus = dbus.SystemBus()
314         try:
315                 rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request")
316                 mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request")
317                 orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation()
318         except dbus.exception.DBusException:
319                 # catching for documentation purposes that when a system doesn't
320                 # support this, this is what to expect
321                 raise
322
323         if orientation == "":
324                 return gtk.ORIENTATION_HORIZONTAL
325         elif orientation == "":
326                 return gtk.ORIENTATION_VERTICAL
327         else:
328                 raise RuntimeError("Unknown orientation: %s" % orientation)
329
330
331 def _hildon_hildonize_password_entry(textEntry):
332         textEntry.set_property('hildon-input-mode', 7 | (1 << 29))
333
334
335 def _null_hildonize_password_entry(textEntry):
336         pass
337
338
339 if IS_HILDON_SUPPORTED:
340         hildonize_password_entry = _hildon_hildonize_password_entry
341 else:
342         hildonize_password_entry = _null_hildonize_password_entry
343
344
345 def _hildon_hildonize_combo_entry(comboEntry):
346         comboEntry.set_property('hildon-input-mode', 1 << 4)
347
348
349 def _null_hildonize_combo_entry(textEntry):
350         pass
351
352
353 if IS_HILDON_SUPPORTED:
354         hildonize_combo_entry = _hildon_hildonize_combo_entry
355 else:
356         hildonize_combo_entry = _null_hildonize_combo_entry
357
358
359 def _fremantle_hildonize_scrollwindow(scrolledWindow):
360         pannableWindow = hildon.PannableArea()
361
362         child = scrolledWindow.get_child()
363         scrolledWindow.remove(child)
364         pannableWindow.add(child)
365
366         parent = scrolledWindow.get_parent()
367         parent.remove(scrolledWindow)
368         parent.add(pannableWindow)
369
370         return pannableWindow
371
372
373 def _hildon_hildonize_scrollwindow(scrolledWindow):
374         hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True)
375         return scrolledWindow
376
377
378 def _null_hildonize_scrollwindow(scrolledWindow):
379         return scrolledWindow
380
381
382 try:
383         hildon.PannableArea
384         hildonize_scrollwindow = _fremantle_hildonize_scrollwindow
385         hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
386 except AttributeError:
387         try:
388                 hildon.hildon_helper_set_thumb_scrollbar
389                 hildonize_scrollwindow = _hildon_hildonize_scrollwindow
390                 hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
391         except AttributeError:
392                 hildonize_scrollwindow = _null_hildonize_scrollwindow
393                 hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow
394
395
396 def _hildon_request_number(parent, title, range, default):
397         spinner = hildon.NumberEditor(*range)
398         spinner.set_value(default)
399
400         dialog = gtk.Dialog(
401                 title,
402                 parent,
403                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
404                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
405         )
406         dialog.set_default_response(gtk.RESPONSE_CANCEL)
407         dialog.get_child().add(spinner)
408
409         try:
410                 dialog.show_all()
411                 response = dialog.run()
412
413                 if response == gtk.RESPONSE_OK:
414                         return spinner.get_value()
415                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
416                         raise RuntimeError("User cancelled request")
417                 else:
418                         raise RuntimeError("Unrecognized response %r", response)
419         finally:
420                 dialog.hide()
421                 dialog.destroy()
422
423
424 def _null_request_number(parent, title, range, default):
425         adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0)
426         spinner = gtk.SpinButton(adjustment, 0, 0)
427         spinner.set_wrap(False)
428
429         dialog = gtk.Dialog(
430                 title,
431                 parent,
432                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
433                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
434         )
435         dialog.set_default_response(gtk.RESPONSE_CANCEL)
436         dialog.get_child().add(spinner)
437
438         try:
439                 dialog.show_all()
440                 response = dialog.run()
441
442                 if response == gtk.RESPONSE_OK:
443                         return spinner.get_value_as_int()
444                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
445                         raise RuntimeError("User cancelled request")
446                 else:
447                         raise RuntimeError("Unrecognized response %r", response)
448         finally:
449                 dialog.hide()
450                 dialog.destroy()
451
452
453 try:
454         hildon.NumberEditor # TODO deprecated in fremantle
455         request_number = _hildon_request_number
456 except AttributeError:
457         request_number = _null_request_number
458
459
460 def _hildon_touch_selector(parent, title, items, defaultIndex):
461         model = gtk.ListStore(gobject.TYPE_STRING)
462         for item in items:
463                 model.append((item, ))
464
465         selector = hildon.TouchSelector()
466         selector.append_text_column(model, True)
467         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
468         selector.set_active(0, defaultIndex)
469
470         dialog = hildon.PickerDialog(parent)
471         dialog.set_selector(selector)
472
473         try:
474                 dialog.show_all()
475                 response = dialog.run()
476
477                 if response == gtk.RESPONSE_OK:
478                         return selector.get_active(0)
479                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
480                         raise RuntimeError("User cancelled request")
481                 else:
482                         raise RuntimeError("Unrecognized response %r", response)
483         finally:
484                 dialog.hide()
485                 dialog.destroy()
486
487
488 def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData):
489         dialog.response(gtk.RESPONSE_OK)
490         pathData[0] = path
491
492
493 def _null_touch_selector(parent, title, items, defaultIndex = -1):
494         parentSize = parent.get_size()
495
496         model = gtk.ListStore(gobject.TYPE_STRING)
497         for item in items:
498                 model.append((item, ))
499
500         cell = gtk.CellRendererText()
501         set_cell_thumb_selectable(cell)
502         column = gtk.TreeViewColumn(title)
503         column.pack_start(cell, expand=True)
504         column.add_attribute(cell, "text", 0)
505
506         treeView = gtk.TreeView()
507         treeView.set_model(model)
508         treeView.append_column(column)
509         selection = treeView.get_selection()
510         selection.set_mode(gtk.SELECTION_SINGLE)
511         if 0 < defaultIndex:
512                 selection.select_path((defaultIndex, ))
513
514         scrolledWin = gtk.ScrolledWindow()
515         scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
516         scrolledWin.add(treeView)
517
518         dialog = gtk.Dialog(
519                 title,
520                 parent,
521                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
522                 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
523         )
524         dialog.set_default_response(gtk.RESPONSE_CANCEL)
525         dialog.get_child().add(scrolledWin)
526         dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
527
528         scrolledWin = hildonize_scrollwindow(scrolledWin)
529         pathData = [None]
530         treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData)
531
532         try:
533                 dialog.show_all()
534                 response = dialog.run()
535
536                 if response == gtk.RESPONSE_OK:
537                         if pathData[0] is None:
538                                 raise RuntimeError("No selection made")
539                         return pathData[0][0]
540                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
541                         raise RuntimeError("User cancelled request")
542                 else:
543                         raise RuntimeError("Unrecognized response %r", response)
544         finally:
545                 dialog.hide()
546                 dialog.destroy()
547
548
549 try:
550         hildon.PickerDialog
551         hildon.TouchSelector
552         touch_selector = _hildon_touch_selector
553 except AttributeError:
554         touch_selector = _null_touch_selector
555
556
557 def _hildon_touch_selector_entry(parent, title, items, defaultItem):
558         # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way
559         try:
560                 selector = hildon.TouchSelectorEntry(text=True)
561         except TypeError:
562                 selector = hildon.hildon_touch_selector_entry_new_text()
563         defaultIndex = -1
564         for i, item in enumerate(items):
565                 selector.append_text(item)
566                 if item == defaultItem:
567                         defaultIndex = i
568
569         dialog = hildon.PickerDialog(parent)
570         dialog.set_selector(selector)
571
572         if 0 < defaultIndex:
573                 selector.set_active(0, defaultIndex)
574         else:
575                 selector.get_entry().set_text(defaultItem)
576
577         try:
578                 dialog.show_all()
579                 response = dialog.run()
580         finally:
581                 dialog.hide()
582
583         if response == gtk.RESPONSE_OK:
584                 return selector.get_entry().get_text()
585         elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
586                 raise RuntimeError("User cancelled request")
587         else:
588                 raise RuntimeError("Unrecognized response %r", response)
589
590
591 def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex):
592         custom = entry.get_text().strip()
593         if custom:
594                 result[0] = custom
595                 selection.unselect_all()
596         else:
597                 result[0] = None
598                 selection.select_path((defaultIndex, ))
599
600
601 def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result):
602         dialog.response(gtk.RESPONSE_OK)
603         result[0] = customEntry.get_text()
604
605
606 def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result):
607         dialog.response(gtk.RESPONSE_OK)
608         model = treeView.get_model()
609         itr = model.get_iter(path)
610         if itr is not None:
611                 result[0] = model.get_value(itr, 0)
612
613
614 def _null_touch_selector_entry(parent, title, items, defaultItem):
615         parentSize = parent.get_size()
616
617         model = gtk.ListStore(gobject.TYPE_STRING)
618         defaultIndex = -1
619         for i, item in enumerate(items):
620                 model.append((item, ))
621                 if item == defaultItem:
622                         defaultIndex = i
623
624         cell = gtk.CellRendererText()
625         set_cell_thumb_selectable(cell)
626         column = gtk.TreeViewColumn(title)
627         column.pack_start(cell, expand=True)
628         column.add_attribute(cell, "text", 0)
629
630         treeView = gtk.TreeView()
631         treeView.set_model(model)
632         treeView.append_column(column)
633         selection = treeView.get_selection()
634         selection.set_mode(gtk.SELECTION_SINGLE)
635
636         scrolledWin = gtk.ScrolledWindow()
637         scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
638         scrolledWin.add(treeView)
639
640         customEntry = gtk.Entry()
641
642         layout = gtk.VBox()
643         layout.pack_start(customEntry, expand=False)
644         layout.pack_start(scrolledWin)
645
646         dialog = gtk.Dialog(
647                 title,
648                 parent,
649                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
650                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
651         )
652         dialog.set_default_response(gtk.RESPONSE_CANCEL)
653         dialog.get_child().add(layout)
654         dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
655
656         scrolledWin = hildonize_scrollwindow(scrolledWin)
657
658         result = [None]
659         if 0 < defaultIndex:
660                 selection.select_path((defaultIndex, ))
661                 result[0] = defaultItem
662         else:
663                 customEntry.set_text(defaultItem)
664
665         customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result)
666         customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex)
667         treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result)
668
669         try:
670                 dialog.show_all()
671                 response = dialog.run()
672
673                 if response == gtk.RESPONSE_OK:
674                         _, itr = selection.get_selected()
675                         if itr is not None:
676                                 return model.get_value(itr, 0)
677                         else:
678                                 enteredText = customEntry.get_text().strip()
679                                 if enteredText:
680                                         return enteredText
681                                 elif result[0] is not None:
682                                         return result[0]
683                                 else:
684                                         raise RuntimeError("No selection made")
685                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
686                         raise RuntimeError("User cancelled request")
687                 else:
688                         raise RuntimeError("Unrecognized response %r", response)
689         finally:
690                 dialog.hide()
691                 dialog.destroy()
692
693
694 try:
695         hildon.PickerDialog
696         hildon.TouchSelectorEntry
697         touch_selector_entry = _hildon_touch_selector_entry
698 except AttributeError:
699         touch_selector_entry = _null_touch_selector_entry
700
701
702 if __name__ == "__main__":
703         app = get_app_class()()
704
705         label = gtk.Label("Hello World from a Label!")
706
707         win = gtk.Window()
708         win.add(label)
709         win = hildonize_window(app, win)
710         if False:
711                 print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
712         if True:
713                 print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
714                 print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
715         if False:
716                 import pprint
717                 name, value = "", ""
718                 goodLocals = [
719                         (name, value) for (name, value) in locals().iteritems()
720                         if not name.startswith("_")
721                 ]
722                 pprint.pprint(goodLocals)
723         if False:
724                 import time
725                 show_information_banner(win, "Hello World")
726                 time.sleep(5)
727         if False:
728                 import time
729                 banner = show_busy_banner_start(win, "Hello World")
730                 time.sleep(5)
731                 show_busy_banner_end(banner)