Adding panning
[multilist] / 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):
96         appMenu = hildon.AppMenu()
97         window.set_app_menu(appMenu)
98         gtkMenu.get_parent().remove(gtkMenu)
99         return appMenu
100
101
102 def _hildon_hildonize_menu(window, gtkMenu):
103         hildonMenu = gtk.Menu()
104         for child in gtkMenu.get_children():
105                 child.reparent(hildonMenu)
106         window.set_menu(hildonMenu)
107         gtkMenu.destroy()
108         return hildonMenu
109
110
111 def _null_hildonize_menu(window, gtkMenu):
112         return gtkMenu
113
114
115 try:
116         hildon.AppMenu
117         GTK_MENU_USED = False
118         IS_FREMANTLE_SUPPORTED = True
119         hildonize_menu = _fremantle_hildonize_menu
120 except AttributeError:
121         GTK_MENU_USED = True
122         IS_FREMANTLE_SUPPORTED = False
123         if IS_HILDON_SUPPORTED:
124                 hildonize_menu = _hildon_hildonize_menu
125         else:
126                 hildonize_menu = _null_hildonize_menu
127
128
129 def _hildon_set_button_auto_selectable(button):
130         button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT)
131
132
133 def _null_set_button_auto_selectable(button):
134         pass
135
136
137 try:
138         hildon.HILDON_SIZE_AUTO_HEIGHT
139         gtk.Button.set_theme_size
140         set_button_auto_selectable = _hildon_set_button_auto_selectable
141 except AttributeError:
142         set_button_auto_selectable = _null_set_button_auto_selectable
143
144
145 def _hildon_set_button_finger_selectable(button):
146         button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT)
147
148
149 def _null_set_button_finger_selectable(button):
150         pass
151
152
153 try:
154         hildon.HILDON_SIZE_FINGER_HEIGHT
155         gtk.Button.set_theme_size
156         set_button_finger_selectable = _hildon_set_button_finger_selectable
157 except AttributeError:
158         set_button_finger_selectable = _null_set_button_finger_selectable
159
160
161 def _hildon_set_button_thumb_selectable(button):
162         button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT)
163
164
165 def _null_set_button_thumb_selectable(button):
166         pass
167
168
169 try:
170         hildon.HILDON_SIZE_THUMB_HEIGHT
171         gtk.Button.set_theme_size
172         set_button_thumb_selectable = _hildon_set_button_thumb_selectable
173 except AttributeError:
174         set_button_thumb_selectable = _null_set_button_thumb_selectable
175
176
177 def _hildon_set_cell_thumb_selectable(renderer):
178         renderer.set_property("scale", 1.5)
179
180
181 def _null_set_cell_thumb_selectable(renderer):
182         pass
183
184
185 if IS_HILDON_SUPPORTED:
186         set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable
187 else:
188         set_cell_thumb_selectable = _null_set_cell_thumb_selectable
189
190
191 def _hildon_set_pix_cell_thumb_selectable(renderer):
192         renderer.set_property("stock-size", 48)
193
194
195 def _null_set_pix_cell_thumb_selectable(renderer):
196         pass
197
198
199 if IS_HILDON_SUPPORTED:
200         set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable
201 else:
202         set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable
203
204
205 def _fremantle_show_information_banner(parent, message):
206         hildon.hildon_banner_show_information(parent, "", message)
207
208
209 def _hildon_show_information_banner(parent, message):
210         hildon.hildon_banner_show_information(parent, None, message)
211
212
213 def _null_show_information_banner(parent, message):
214         pass
215
216
217 if IS_FREMANTLE_SUPPORTED:
218         show_information_banner = _fremantle_show_information_banner
219 else:
220         try:
221                 hildon.hildon_banner_show_information
222                 show_information_banner = _hildon_show_information_banner
223         except AttributeError:
224                 show_information_banner = _null_show_information_banner
225
226
227 def _fremantle_show_busy_banner_start(parent, message):
228         hildon.hildon_gtk_window_set_progress_indicator(parent, True)
229         return parent
230
231
232 def _fremantle_show_busy_banner_end(banner):
233         hildon.hildon_gtk_window_set_progress_indicator(banner, False)
234
235
236 def _hildon_show_busy_banner_start(parent, message):
237         return hildon.hildon_banner_show_animation(parent, None, message)
238
239
240 def _hildon_show_busy_banner_end(banner):
241         banner.destroy()
242
243
244 def _null_show_busy_banner_start(parent, message):
245         return None
246
247
248 def _null_show_busy_banner_end(banner):
249         assert banner is None
250
251
252 try:
253         hildon.hildon_gtk_window_set_progress_indicator
254         show_busy_banner_start = _fremantle_show_busy_banner_start
255         show_busy_banner_end = _fremantle_show_busy_banner_end
256 except AttributeError:
257         try:
258                 hildon.hildon_banner_show_animation
259                 show_busy_banner_start = _hildon_show_busy_banner_start
260                 show_busy_banner_end = _hildon_show_busy_banner_end
261         except AttributeError:
262                 show_busy_banner_start = _null_show_busy_banner_start
263                 show_busy_banner_end = _null_show_busy_banner_end
264
265
266 def _hildon_hildonize_text_entry(textEntry):
267         textEntry.set_property('hildon-input-mode', 7)
268
269
270 def _null_hildonize_text_entry(textEntry):
271         pass
272
273
274 if IS_HILDON_SUPPORTED:
275         hildonize_text_entry = _hildon_hildonize_text_entry
276 else:
277         hildonize_text_entry = _null_hildonize_text_entry
278
279
280 def _hildon_window_to_portrait(window):
281         # gtk documentation is unclear whether this does a "=" or a "|="
282         flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST
283         hildon.hildon_gtk_window_set_portrait_flags(window, flags)
284
285
286 def _hildon_window_to_landscape(window):
287         # gtk documentation is unclear whether this does a "=" or a "&= ~"
288         flags = hildon.PORTRAIT_MODE_SUPPORT
289         hildon.hildon_gtk_window_set_portrait_flags(window, flags)
290
291
292 def _null_window_to_portrait(window):
293         pass
294
295
296 def _null_window_to_landscape(window):
297         pass
298
299
300 try:
301         hildon.PORTRAIT_MODE_SUPPORT
302         hildon.PORTRAIT_MODE_REQUEST
303         hildon.hildon_gtk_window_set_portrait_flags
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         if child is not None:
364                 scrolledWindow.remove(child)
365                 pannableWindow.add(child)
366
367         parent = scrolledWindow.get_parent()
368         if parent is not None:
369                 parent.remove(scrolledWindow)
370                 parent.add(pannableWindow)
371
372         return pannableWindow
373
374
375 def _hildon_hildonize_scrollwindow(scrolledWindow):
376         hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True)
377         return scrolledWindow
378
379
380 def _null_hildonize_scrollwindow(scrolledWindow):
381         return scrolledWindow
382
383
384 try:
385         hildon.PannableArea
386         hildonize_scrollwindow = _fremantle_hildonize_scrollwindow
387         hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
388 except AttributeError:
389         try:
390                 hildon.hildon_helper_set_thumb_scrollbar
391                 hildonize_scrollwindow = _hildon_hildonize_scrollwindow
392                 hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
393         except AttributeError:
394                 hildonize_scrollwindow = _null_hildonize_scrollwindow
395                 hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow
396
397
398 def _hildon_request_number(parent, title, range, default):
399         spinner = hildon.NumberEditor(*range)
400         spinner.set_value(default)
401
402         dialog = gtk.Dialog(
403                 title,
404                 parent,
405                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
406                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
407         )
408         dialog.set_default_response(gtk.RESPONSE_CANCEL)
409         dialog.get_child().add(spinner)
410
411         try:
412                 dialog.show_all()
413                 response = dialog.run()
414
415                 if response == gtk.RESPONSE_OK:
416                         return spinner.get_value()
417                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
418                         raise RuntimeError("User cancelled request")
419                 else:
420                         raise RuntimeError("Unrecognized response %r", response)
421         finally:
422                 dialog.hide()
423                 dialog.destroy()
424
425
426 def _null_request_number(parent, title, range, default):
427         adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0)
428         spinner = gtk.SpinButton(adjustment, 0, 0)
429         spinner.set_wrap(False)
430
431         dialog = gtk.Dialog(
432                 title,
433                 parent,
434                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
435                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
436         )
437         dialog.set_default_response(gtk.RESPONSE_CANCEL)
438         dialog.get_child().add(spinner)
439
440         try:
441                 dialog.show_all()
442                 response = dialog.run()
443
444                 if response == gtk.RESPONSE_OK:
445                         return spinner.get_value_as_int()
446                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
447                         raise RuntimeError("User cancelled request")
448                 else:
449                         raise RuntimeError("Unrecognized response %r", response)
450         finally:
451                 dialog.hide()
452                 dialog.destroy()
453
454
455 try:
456         hildon.NumberEditor # TODO deprecated in fremantle
457         request_number = _hildon_request_number
458 except AttributeError:
459         request_number = _null_request_number
460
461
462 def _hildon_touch_selector(parent, title, items, defaultIndex):
463         model = gtk.ListStore(gobject.TYPE_STRING)
464         for item in items:
465                 model.append((item, ))
466
467         selector = hildon.TouchSelector()
468         selector.append_text_column(model, True)
469         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
470         selector.set_active(0, defaultIndex)
471
472         dialog = hildon.PickerDialog(parent)
473         dialog.set_selector(selector)
474
475         try:
476                 dialog.show_all()
477                 response = dialog.run()
478
479                 if response == gtk.RESPONSE_OK:
480                         return selector.get_active(0)
481                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
482                         raise RuntimeError("User cancelled request")
483                 else:
484                         raise RuntimeError("Unrecognized response %r", response)
485         finally:
486                 dialog.hide()
487                 dialog.destroy()
488
489
490 def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData):
491         dialog.response(gtk.RESPONSE_OK)
492         pathData[0] = path
493
494
495 def _null_touch_selector(parent, title, items, defaultIndex = -1):
496         parentSize = parent.get_size()
497
498         model = gtk.ListStore(gobject.TYPE_STRING)
499         for item in items:
500                 model.append((item, ))
501
502         cell = gtk.CellRendererText()
503         set_cell_thumb_selectable(cell)
504         column = gtk.TreeViewColumn(title)
505         column.pack_start(cell, expand=True)
506         column.add_attribute(cell, "text", 0)
507
508         treeView = gtk.TreeView()
509         treeView.set_model(model)
510         treeView.append_column(column)
511         selection = treeView.get_selection()
512         selection.set_mode(gtk.SELECTION_SINGLE)
513         if 0 < defaultIndex:
514                 selection.select_path((defaultIndex, ))
515
516         scrolledWin = gtk.ScrolledWindow()
517         scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
518         scrolledWin.add(treeView)
519
520         dialog = gtk.Dialog(
521                 title,
522                 parent,
523                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
524                 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
525         )
526         dialog.set_default_response(gtk.RESPONSE_CANCEL)
527         dialog.get_child().add(scrolledWin)
528         dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
529
530         scrolledWin = hildonize_scrollwindow(scrolledWin)
531         pathData = [None]
532         treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData)
533
534         try:
535                 dialog.show_all()
536                 response = dialog.run()
537
538                 if response == gtk.RESPONSE_OK:
539                         if pathData[0] is None:
540                                 raise RuntimeError("No selection made")
541                         return pathData[0][0]
542                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
543                         raise RuntimeError("User cancelled request")
544                 else:
545                         raise RuntimeError("Unrecognized response %r", response)
546         finally:
547                 dialog.hide()
548                 dialog.destroy()
549
550
551 try:
552         hildon.PickerDialog
553         hildon.TouchSelector
554         touch_selector = _hildon_touch_selector
555 except AttributeError:
556         touch_selector = _null_touch_selector
557
558
559 def _hildon_touch_selector_entry(parent, title, items, defaultItem):
560         # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way
561         try:
562                 selector = hildon.TouchSelectorEntry(text=True)
563         except TypeError:
564                 selector = hildon.hildon_touch_selector_entry_new_text()
565         defaultIndex = -1
566         for i, item in enumerate(items):
567                 selector.append_text(item)
568                 if item == defaultItem:
569                         defaultIndex = i
570
571         dialog = hildon.PickerDialog(parent)
572         dialog.set_selector(selector)
573
574         if 0 < defaultIndex:
575                 selector.set_active(0, defaultIndex)
576         else:
577                 selector.get_entry().set_text(defaultItem)
578
579         try:
580                 dialog.show_all()
581                 response = dialog.run()
582         finally:
583                 dialog.hide()
584
585         if response == gtk.RESPONSE_OK:
586                 return selector.get_entry().get_text()
587         elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
588                 raise RuntimeError("User cancelled request")
589         else:
590                 raise RuntimeError("Unrecognized response %r", response)
591
592
593 def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex):
594         custom = entry.get_text().strip()
595         if custom:
596                 result[0] = custom
597                 selection.unselect_all()
598         else:
599                 result[0] = None
600                 selection.select_path((defaultIndex, ))
601
602
603 def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result):
604         dialog.response(gtk.RESPONSE_OK)
605         result[0] = customEntry.get_text()
606
607
608 def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result):
609         dialog.response(gtk.RESPONSE_OK)
610         model = treeView.get_model()
611         itr = model.get_iter(path)
612         if itr is not None:
613                 result[0] = model.get_value(itr, 0)
614
615
616 def _null_touch_selector_entry(parent, title, items, defaultItem):
617         parentSize = parent.get_size()
618
619         model = gtk.ListStore(gobject.TYPE_STRING)
620         defaultIndex = -1
621         for i, item in enumerate(items):
622                 model.append((item, ))
623                 if item == defaultItem:
624                         defaultIndex = i
625
626         cell = gtk.CellRendererText()
627         set_cell_thumb_selectable(cell)
628         column = gtk.TreeViewColumn(title)
629         column.pack_start(cell, expand=True)
630         column.add_attribute(cell, "text", 0)
631
632         treeView = gtk.TreeView()
633         treeView.set_model(model)
634         treeView.append_column(column)
635         selection = treeView.get_selection()
636         selection.set_mode(gtk.SELECTION_SINGLE)
637
638         scrolledWin = gtk.ScrolledWindow()
639         scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
640         scrolledWin.add(treeView)
641
642         customEntry = gtk.Entry()
643
644         layout = gtk.VBox()
645         layout.pack_start(customEntry, expand=False)
646         layout.pack_start(scrolledWin)
647
648         dialog = gtk.Dialog(
649                 title,
650                 parent,
651                 gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
652                 (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
653         )
654         dialog.set_default_response(gtk.RESPONSE_CANCEL)
655         dialog.get_child().add(layout)
656         dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
657
658         scrolledWin = hildonize_scrollwindow(scrolledWin)
659
660         result = [None]
661         if 0 < defaultIndex:
662                 selection.select_path((defaultIndex, ))
663                 result[0] = defaultItem
664         else:
665                 customEntry.set_text(defaultItem)
666
667         customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result)
668         customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex)
669         treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result)
670
671         try:
672                 dialog.show_all()
673                 response = dialog.run()
674
675                 if response == gtk.RESPONSE_OK:
676                         _, itr = selection.get_selected()
677                         if itr is not None:
678                                 return model.get_value(itr, 0)
679                         else:
680                                 enteredText = customEntry.get_text().strip()
681                                 if enteredText:
682                                         return enteredText
683                                 elif result[0] is not None:
684                                         return result[0]
685                                 else:
686                                         raise RuntimeError("No selection made")
687                 elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
688                         raise RuntimeError("User cancelled request")
689                 else:
690                         raise RuntimeError("Unrecognized response %r", response)
691         finally:
692                 dialog.hide()
693                 dialog.destroy()
694
695
696 try:
697         hildon.PickerDialog
698         hildon.TouchSelectorEntry
699         touch_selector_entry = _hildon_touch_selector_entry
700 except AttributeError:
701         touch_selector_entry = _null_touch_selector_entry
702
703
704 if __name__ == "__main__":
705         app = get_app_class()()
706
707         label = gtk.Label("Hello World from a Label!")
708
709         win = gtk.Window()
710         win.add(label)
711         win = hildonize_window(app, win)
712         if False and IS_FREMANTLE_SUPPORTED:
713                 appMenu = hildon.AppMenu()
714                 for i in xrange(5):
715                         b = gtk.Button(str(i))
716                         appMenu.append(b)
717                 win.set_app_menu(appMenu)
718                 win.show_all()
719                 appMenu.show_all()
720                 gtk.main()
721         elif False:
722                 print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
723         elif False:
724                 print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
725                 print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
726         elif False:
727                 import pprint
728                 name, value = "", ""
729                 goodLocals = [
730                         (name, value) for (name, value) in locals().iteritems()
731                         if not name.startswith("_")
732                 ]
733                 pprint.pprint(goodLocals)
734         elif False:
735                 import time
736                 show_information_banner(win, "Hello World")
737                 time.sleep(5)
738         elif False:
739                 import time
740                 banner = show_busy_banner_start(win, "Hello World")
741                 time.sleep(5)
742                 show_busy_banner_end(banner)