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