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