Minor cleanup using reusable exception logger and moving constants out of main file
[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 _hildon_set_pix_cell_thumb_selectable(renderer):
188         renderer.set_property("stock-size", 48)
189
190
191 def _null_set_pix_cell_thumb_selectable(renderer):
192         pass
193
194
195 if IS_HILDON_SUPPORTED:
196         set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable
197 else:
198         set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable
199
200
201 def _fremantle_show_information_banner(parent, message):
202         hildon.hildon_banner_show_information(parent, "", message)
203
204
205 def _hildon_show_information_banner(parent, message):
206         hildon.hildon_banner_show_information(parent, None, message)
207
208
209 def _null_show_information_banner(parent, message):
210         pass
211
212
213 if IS_FREMANTLE_SUPPORTED:
214         show_information_banner = _fremantle_show_information_banner
215 else:
216         try:
217                 hildon.hildon_banner_show_information
218                 show_information_banner = _hildon_show_information_banner
219         except AttributeError:
220                 show_information_banner = _null_show_information_banner
221
222
223 def _fremantle_show_busy_banner_start(parent, message):
224         hildon.hildon_gtk_window_set_progress_indicator(parent, True)
225         return parent
226
227
228 def _fremantle_show_busy_banner_end(banner):
229         hildon.hildon_gtk_window_set_progress_indicator(banner, False)
230
231
232 def _hildon_show_busy_banner_start(parent, message):
233         return hildon.hildon_banner_show_animation(parent, None, message)
234
235
236 def _hildon_show_busy_banner_end(banner):
237         banner.destroy()
238
239
240 def _null_show_busy_banner_start(parent, message):
241         return None
242
243
244 def _null_show_busy_banner_end(banner):
245         assert banner is None
246
247
248 try:
249         hildon.hildon_gtk_window_set_progress_indicator
250         show_busy_banner_start = _fremantle_show_busy_banner_start
251         show_busy_banner_end = _fremantle_show_busy_banner_end
252 except AttributeError:
253         try:
254                 hildon.hildon_banner_show_animation
255                 show_busy_banner_start = _hildon_show_busy_banner_start
256                 show_busy_banner_end = _hildon_show_busy_banner_end
257         except AttributeError:
258                 show_busy_banner_start = _null_show_busy_banner_start
259                 show_busy_banner_end = _null_show_busy_banner_end
260
261
262 def _hildon_hildonize_text_entry(textEntry):
263         textEntry.set_property('hildon-input-mode', 7)
264
265
266 def _null_hildonize_text_entry(textEntry):
267         pass
268
269
270 if IS_HILDON_SUPPORTED:
271         hildonize_text_entry = _hildon_hildonize_text_entry
272 else:
273         hildonize_text_entry = _null_hildonize_text_entry
274
275
276 def _hildon_mark_window_rotatable(window):
277         # gtk documentation is unclear whether this does a "=" or a "|="
278         window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT)
279
280
281 def _null_mark_window_rotatable(window):
282         pass
283
284
285 try:
286         hildon.HILDON_PORTRAIT_MODE_SUPPORT
287         mark_window_rotatable = _hildon_mark_window_rotatable
288 except AttributeError:
289         mark_window_rotatable = _null_mark_window_rotatable
290
291
292 def _hildon_window_to_portrait(window):
293         # gtk documentation is unclear whether this does a "=" or a "|="
294         window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT)
295
296
297 def _hildon_window_to_landscape(window):
298         # gtk documentation is unclear whether this does a "=" or a "&= ~"
299         window.unset_flags(hildon.HILDON_PORTRAIT_MODE_REQUEST)
300
301
302 def _null_window_to_portrait(window):
303         pass
304
305
306 def _null_window_to_landscape(window):
307         pass
308
309
310 try:
311         hildon.HILDON_PORTRAIT_MODE_SUPPORT
312         hildon.HILDON_PORTRAIT_MODE_REQUEST
313
314         window_to_portrait = _hildon_window_to_portrait
315         window_to_landscape = _hildon_window_to_landscape
316 except AttributeError:
317         window_to_portrait = _null_window_to_portrait
318         window_to_landscape = _null_window_to_landscape
319
320
321 def get_device_orientation():
322         bus = dbus.SystemBus()
323         try:
324                 rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request")
325                 mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request")
326                 orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation()
327         except dbus.exception.DBusException:
328                 # catching for documentation purposes that when a system doesn't
329                 # support this, this is what to expect
330                 raise
331
332         if orientation == "":
333                 return gtk.ORIENTATION_HORIZONTAL
334         elif orientation == "":
335                 return gtk.ORIENTATION_VERTICAL
336         else:
337                 raise RuntimeError("Unknown orientation: %s" % orientation)
338
339
340 def _hildon_hildonize_password_entry(textEntry):
341         textEntry.set_property('hildon-input-mode', 7 | (1 << 29))
342
343
344 def _null_hildonize_password_entry(textEntry):
345         pass
346
347
348 if IS_HILDON_SUPPORTED:
349         hildonize_password_entry = _hildon_hildonize_password_entry
350 else:
351         hildonize_password_entry = _null_hildonize_password_entry
352
353
354 def _hildon_hildonize_combo_entry(comboEntry):
355         comboEntry.set_property('hildon-input-mode', 1 << 4)
356
357
358 def _null_hildonize_combo_entry(textEntry):
359         pass
360
361
362 if IS_HILDON_SUPPORTED:
363         hildonize_combo_entry = _hildon_hildonize_combo_entry
364 else:
365         hildonize_combo_entry = _null_hildonize_combo_entry
366
367
368 def _fremantle_hildonize_scrollwindow(scrolledWindow):
369         pannableWindow = hildon.PannableArea()
370
371         child = scrolledWindow.get_child()
372         scrolledWindow.remove(child)
373         pannableWindow.add(child)
374
375         parent = scrolledWindow.get_parent()
376         if parent is not None:
377                 parent.remove(scrolledWindow)
378                 parent.add(pannableWindow)
379
380         return pannableWindow
381
382
383 def _hildon_hildonize_scrollwindow(scrolledWindow):
384         hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True)
385         return scrolledWindow
386
387
388 def _null_hildonize_scrollwindow(scrolledWindow):
389         return scrolledWindow
390
391
392 try:
393         hildon.PannableArea
394         hildonize_scrollwindow = _fremantle_hildonize_scrollwindow
395         hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
396 except AttributeError:
397         try:
398                 hildon.hildon_helper_set_thumb_scrollbar
399                 hildonize_scrollwindow = _hildon_hildonize_scrollwindow
400                 hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
401         except AttributeError:
402                 hildonize_scrollwindow = _null_hildonize_scrollwindow
403                 hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow
404
405
406 def _hildon_request_number(parent, title, range, default):
407         spinner = hildon.NumberEditor(*range)
408         spinner.set_value(default)
409
410         dialog = gtk.Dialog(
411                 title,
412                 parent,
413                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
414                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
415         )
416         dialog.set_default_response(gtk.RESPONSE_CANCEL)
417         dialog.get_child().add(spinner)
418
419         try:
420                 dialog.show_all()
421                 response = dialog.run()
422
423                 if response == gtk.RESPONSE_OK:
424                         return spinner.get_value()
425                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
426                         raise RuntimeError("User cancelled request")
427                 else:
428                         raise RuntimeError("Unrecognized response %r", response)
429         finally:
430                 dialog.hide()
431                 dialog.destroy()
432
433
434 def _null_request_number(parent, title, range, default):
435         adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0)
436         spinner = gtk.SpinButton(adjustment, 0, 0)
437         spinner.set_wrap(False)
438
439         dialog = gtk.Dialog(
440                 title,
441                 parent,
442                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
443                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
444         )
445         dialog.set_default_response(gtk.RESPONSE_CANCEL)
446         dialog.get_child().add(spinner)
447
448         try:
449                 dialog.show_all()
450                 response = dialog.run()
451
452                 if response == gtk.RESPONSE_OK:
453                         return spinner.get_value_as_int()
454                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
455                         raise RuntimeError("User cancelled request")
456                 else:
457                         raise RuntimeError("Unrecognized response %r", response)
458         finally:
459                 dialog.hide()
460                 dialog.destroy()
461
462
463 try:
464         hildon.NumberEditor # TODO deprecated in fremantle
465         request_number = _hildon_request_number
466 except AttributeError:
467         request_number = _null_request_number
468
469
470 def _hildon_touch_selector(parent, title, items, defaultIndex):
471         model = gtk.ListStore(gobject.TYPE_STRING)
472         for item in items:
473                 model.append((item, ))
474
475         selector = hildon.TouchSelector()
476         selector.append_text_column(model, True)
477         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
478         selector.set_active(0, defaultIndex)
479
480         dialog = hildon.PickerDialog(parent)
481         dialog.set_selector(selector)
482
483         try:
484                 dialog.show_all()
485                 response = dialog.run()
486
487                 if response == gtk.RESPONSE_OK:
488                         return selector.get_active(0)
489                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
490                         raise RuntimeError("User cancelled request")
491                 else:
492                         raise RuntimeError("Unrecognized response %r", response)
493         finally:
494                 dialog.hide()
495                 dialog.destroy()
496
497
498 def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData):
499         dialog.response(gtk.RESPONSE_OK)
500         pathData[0] = path
501
502
503 def _null_touch_selector(parent, title, items, defaultIndex = -1):
504         parentSize = parent.get_size()
505
506         model = gtk.ListStore(gobject.TYPE_STRING)
507         for item in items:
508                 model.append((item, ))
509
510         cell = gtk.CellRendererText()
511         set_cell_thumb_selectable(cell)
512         column = gtk.TreeViewColumn(title)
513         column.pack_start(cell, expand=True)
514         column.add_attribute(cell, "text", 0)
515
516         treeView = gtk.TreeView()
517         treeView.set_model(model)
518         treeView.append_column(column)
519         selection = treeView.get_selection()
520         selection.set_mode(gtk.SELECTION_SINGLE)
521         if 0 < defaultIndex:
522                 selection.select_path((defaultIndex, ))
523
524         scrolledWin = gtk.ScrolledWindow()
525         scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
526         scrolledWin.add(treeView)
527
528         dialog = gtk.Dialog(
529                 title,
530                 parent,
531                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
532                 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
533         )
534         dialog.set_default_response(gtk.RESPONSE_CANCEL)
535         dialog.get_child().add(scrolledWin)
536         dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
537
538         scrolledWin = hildonize_scrollwindow(scrolledWin)
539         pathData = [None]
540         treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData)
541
542         try:
543                 dialog.show_all()
544                 response = dialog.run()
545
546                 if response == gtk.RESPONSE_OK:
547                         if pathData[0] is None:
548                                 raise RuntimeError("No selection made")
549                         return pathData[0][0]
550                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
551                         raise RuntimeError("User cancelled request")
552                 else:
553                         raise RuntimeError("Unrecognized response %r", response)
554         finally:
555                 dialog.hide()
556                 dialog.destroy()
557
558
559 try:
560         hildon.PickerDialog
561         hildon.TouchSelector
562         touch_selector = _hildon_touch_selector
563 except AttributeError:
564         touch_selector = _null_touch_selector
565
566
567 def _hildon_touch_selector_entry(parent, title, items, defaultItem):
568         # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way
569         try:
570                 selector = hildon.TouchSelectorEntry(text=True)
571         except TypeError:
572                 selector = hildon.hildon_touch_selector_entry_new_text()
573         defaultIndex = -1
574         for i, item in enumerate(items):
575                 selector.append_text(item)
576                 if item == defaultItem:
577                         defaultIndex = i
578
579         dialog = hildon.PickerDialog(parent)
580         dialog.set_selector(selector)
581
582         if 0 < defaultIndex:
583                 selector.set_active(0, defaultIndex)
584         else:
585                 selector.get_entry().set_text(defaultItem)
586
587         try:
588                 dialog.show_all()
589                 response = dialog.run()
590         finally:
591                 dialog.hide()
592
593         if response == gtk.RESPONSE_OK:
594                 return selector.get_entry().get_text()
595         elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
596                 raise RuntimeError("User cancelled request")
597         else:
598                 raise RuntimeError("Unrecognized response %r", response)
599
600
601 def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex):
602         custom = entry.get_text().strip()
603         if custom:
604                 result[0] = custom
605                 selection.unselect_all()
606         else:
607                 result[0] = None
608                 selection.select_path((defaultIndex, ))
609
610
611 def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result):
612         dialog.response(gtk.RESPONSE_OK)
613         result[0] = customEntry.get_text()
614
615
616 def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result):
617         dialog.response(gtk.RESPONSE_OK)
618         model = treeView.get_model()
619         itr = model.get_iter(path)
620         if itr is not None:
621                 result[0] = model.get_value(itr, 0)
622
623
624 def _null_touch_selector_entry(parent, title, items, defaultItem):
625         parentSize = parent.get_size()
626
627         model = gtk.ListStore(gobject.TYPE_STRING)
628         defaultIndex = -1
629         for i, item in enumerate(items):
630                 model.append((item, ))
631                 if item == defaultItem:
632                         defaultIndex = i
633
634         cell = gtk.CellRendererText()
635         set_cell_thumb_selectable(cell)
636         column = gtk.TreeViewColumn(title)
637         column.pack_start(cell, expand=True)
638         column.add_attribute(cell, "text", 0)
639
640         treeView = gtk.TreeView()
641         treeView.set_model(model)
642         treeView.append_column(column)
643         selection = treeView.get_selection()
644         selection.set_mode(gtk.SELECTION_SINGLE)
645
646         scrolledWin = gtk.ScrolledWindow()
647         scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
648         scrolledWin.add(treeView)
649
650         customEntry = gtk.Entry()
651
652         layout = gtk.VBox()
653         layout.pack_start(customEntry, expand=False)
654         layout.pack_start(scrolledWin)
655
656         dialog = gtk.Dialog(
657                 title,
658                 parent,
659                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
660                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
661         )
662         dialog.set_default_response(gtk.RESPONSE_CANCEL)
663         dialog.get_child().add(layout)
664         dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
665
666         scrolledWin = hildonize_scrollwindow(scrolledWin)
667
668         result = [None]
669         if 0 < defaultIndex:
670                 selection.select_path((defaultIndex, ))
671                 result[0] = defaultItem
672         else:
673                 customEntry.set_text(defaultItem)
674
675         customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result)
676         customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex)
677         treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result)
678
679         try:
680                 dialog.show_all()
681                 response = dialog.run()
682
683                 if response == gtk.RESPONSE_OK:
684                         _, itr = selection.get_selected()
685                         if itr is not None:
686                                 return model.get_value(itr, 0)
687                         else:
688                                 enteredText = customEntry.get_text().strip()
689                                 if enteredText:
690                                         return enteredText
691                                 elif result[0] is not None:
692                                         return result[0]
693                                 else:
694                                         raise RuntimeError("No selection made")
695                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
696                         raise RuntimeError("User cancelled request")
697                 else:
698                         raise RuntimeError("Unrecognized response %r", response)
699         finally:
700                 dialog.hide()
701                 dialog.destroy()
702
703
704 try:
705         hildon.PickerDialog
706         hildon.TouchSelectorEntry
707         touch_selector_entry = _hildon_touch_selector_entry
708 except AttributeError:
709         touch_selector_entry = _null_touch_selector_entry
710
711
712 if __name__ == "__main__":
713         app = get_app_class()()
714
715         label = gtk.Label("Hello World from a Label!")
716
717         win = gtk.Window()
718         win.add(label)
719         win = hildonize_window(app, win)
720         if False:
721                 print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
722         if True:
723                 print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
724                 print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
725         if False:
726                 import pprint
727                 name, value = "", ""
728                 goodLocals = [
729                         (name, value) for (name, value) in locals().iteritems()
730                         if not name.startswith("_")
731                 ]
732                 pprint.pprint(goodLocals)
733         if False:
734                 import time
735                 show_information_banner(win, "Hello World")
736                 time.sleep(5)
737         if False:
738                 import time
739                 banner = show_busy_banner_start(win, "Hello World")
740                 time.sleep(5)
741                 show_busy_banner_end(banner)