1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 """Main module for the relax graphical user interface."""
26
27
28 import os
29 from os import F_OK, access, getcwd, mkdir, sep
30 import platform
31 from re import search
32 from string import split
33 import sys
34 from textwrap import wrap
35 from thread import start_new_thread
36 from time import sleep
37 import webbrowser
38 import wx
39 from wx.lib import buttons
40
41
42 from data import Relax_data_store; ds = Relax_data_store()
43 from data.gui import Gui
44 from info import Info_box
45 from generic_fns import state
46 from generic_fns.pipes import cdp_name
47 from generic_fns.reset import reset
48 from relax_errors import RelaxError
49 from relax_io import io_streams_restore
50 from status import Status; status = Status()
51 import test_suite.test_suite_runner
52 from version import version
53
54
55 from gui.about import About_gui, About_relax
56 from gui.analyses import Analysis_controller
57 from gui.base_classes import Container
58 from gui.spin_viewer.frame import Spin_view_window
59 from gui.controller import Controller
60 from gui.filedialog import RelaxFileDialog
61 from gui.fonts import font
62 from gui.icons import Relax_task_bar_icon, relax_icons
63 from gui.interpreter import Interpreter
64 from gui.menu import Menu
65 from gui.message import error_message, Question
66 from gui.misc import gui_to_str, open_file, protected_exec
67 from gui import paths
68 from gui.pipe_editor import Pipe_editor
69 from gui.references import References
70 from gui.relax_prompt import Prompt
71 from gui.results_viewer import Results_viewer
72 from gui.settings import Free_file_format, load_sequence
73 from gui.user_functions import User_functions; user_functions = User_functions()
74
75
76 -class Main(wx.Frame):
77 """The main GUI class."""
78
79
80 min_width = 1000
81 min_height = 600
82
83 - def __init__(self, parent=None, id=-1, title="", script=None):
84 """Initialise the main relax GUI frame."""
85
86
87 status.wx_info = {}
88 status.wx_info["version"] = split(wx.__version__, '.')
89 status.wx_info["minor"] = "%s.%s" % (status.wx_info["version"][0], status.wx_info["version"][1])
90 status.wx_info["os"] = sys.platform
91 status.wx_info["build"] = None
92 if search('gtk2', wx.version()):
93 status.wx_info["build"] = 'gtk'
94 elif search('cocoa', wx.version()):
95 status.wx_info["build"] = 'cocoa'
96 elif search('mac-unicode', wx.version()):
97 status.wx_info["build"] = 'carbon'
98 status.wx_info["full"] = None
99 if status.wx_info["build"]:
100 status.wx_info["full"] = "%s-%s" % (status.wx_info["os"], status.wx_info["build"])
101
102
103 self.test_suite_flag = False
104
105
106 style = wx.DEFAULT_FRAME_STYLE
107 if not status.debug and status.wx_info["os"] != 'darwin':
108 style = style | wx.MAXIMIZE
109
110
111 super(Main, self).__init__(parent=parent, id=id, title=title, style=style)
112
113
114 if not status.debug and status.wx_info["os"] != 'darwin':
115 self.Maximize()
116
117
118 font.setup()
119
120
121 relax_icons.setup()
122 self.SetIcons(relax_icons)
123
124
125 if status.wx_info["os"] == 'darwin' and status.wx_info["build"] != 'gtk':
126 self.taskbar_icon = Relax_task_bar_icon(self)
127
128
129 self.launch_dir = getcwd()
130
131
132 self.Layout()
133 self.SetSize((self.min_width, self.min_height))
134 self.SetMinSize((self.min_width, self.min_height))
135 self.Centre()
136
137
138 self.analysis = Analysis_controller(self)
139
140
141 self.calc_threads = []
142
143
144 self.init_data()
145
146
147 self.menu = Menu(self)
148
149
150 self.toolbar()
151
152
153 self.controller = Controller(self)
154
155
156 self.SetTitle("relax " + version)
157
158
159 self.status_bar = self.CreateStatusBar(3, 0)
160 self.status_bar.SetStatusWidths([-4, -1, -2])
161 self.update_status_bar()
162
163
164 self.add_start_screen()
165
166
167 self.Bind(wx.EVT_CLOSE, self.exit_gui)
168
169
170 self.interpreter = Interpreter()
171
172
173 status.observers.pipe_alteration.register('status bar', self.update_status_bar)
174 status.observers.result_file.register('gui', self.show_results_viewer_no_warn)
175 status.observers.exec_lock.register('gui', self.enable)
176
177
178 if script:
179 wx.CallAfter(user_functions.script.script_exec, script)
180
181
182 - def about_gui(self, event):
183 """The about message for the relax GUI.
184
185 @param event: The wx event.
186 @type event: wx event
187 """
188
189
190 dialog = About_gui(None, -1, "")
191
192
193 if status.show_gui:
194 dialog.Show()
195
196
197 - def about_relax(self, event):
198 """The about message for relax.
199
200 @param event: The wx event.
201 @type event: wx event
202 """
203
204
205 dialog = About_relax(None, -1)
206
207
208 if status.show_gui:
209 dialog.Show()
210
211
212 - def action_state_save(self, event):
213 """Save the program state.
214
215 @param event: The wx event.
216 @type event: wx event
217 """
218
219
220 if not self.save_file:
221 self.action_state_save_as(event)
222 return
223
224
225 self.state_save()
226
227
228 - def action_state_save_as(self, event):
229 """Save the program state with file name selection.
230
231 @param event: The wx event.
232 @type event: wx event
233 """
234
235
236 dialog = RelaxFileDialog(parent=self, message='Select the relax save state file', defaultFile='state.bz2', wildcard='relax save file (*.bz2)|*.bz2', style=wx.FD_SAVE)
237
238
239 if status.show_gui and dialog.ShowModal() != wx.ID_OK:
240
241 return
242
243
244 file_name = dialog.get_file()
245
246
247 self.save_file = file_name
248
249
250 self.state_save()
251
252
254 """Create a start screen for the main window when no analyses exist."""
255
256
257 sizer = wx.BoxSizer(wx.VERTICAL)
258 self.SetSizer(sizer)
259
260
261 image = wx.StaticBitmap(self, -1, wx.Bitmap(paths.IMAGE_PATH+'ulysses_shadowless_400x168.png', wx.BITMAP_TYPE_ANY))
262
263
264 sizer.AddStretchSpacer()
265 sizer.Add(image, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
266 sizer.AddStretchSpacer()
267
268
269 self.Layout()
270 self.Refresh()
271
272
273 - def close_windows(self):
274 """Throw a warning to close all of the non-essential windows when execution is locked.
275
276 This is to speed up the calculations by avoiding window updates.
277 """
278
279
280 win_list = []
281
282
283 if hasattr(self, 'spin_viewer') and self.spin_viewer.IsShown():
284 win_list.append('The spin viewer window')
285
286
287 if hasattr(self, 'pipe_editor') and self.pipe_editor.IsShown():
288 win_list.append('The data pipe editor window')
289
290
291 if hasattr(self, 'results_viewer') and self.results_viewer.IsShown():
292 win_list.append('The results viewer window')
293
294
295 if not len(win_list):
296 return
297
298
299 text = "The following windows are currently open:\n\n"
300 for win in win_list:
301 text = "%s\t%s.\n" % (text, win)
302 text = text + "\nClosing these will significantly speed up the calculations."
303
304
305 dlg = wx.MessageDialog(self, text, caption="Close windows", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP)
306 if status.show_gui:
307 dlg.ShowModal()
308
309
310 else:
311 sys.stderr.write(text)
312
313
315 """Write an email to the relax mailing-list using the standard mailing program."""
316 webbrowser.open_new('mailto:relax-users@gna.org')
317
318
320 """Enable and disable certain parts of the main window with the execution lock."""
321
322
323 enable = False
324 if not status.exec_lock.locked():
325 enable = True
326
327
328 wx.CallAfter(self.toolbar.EnableTool, self.TB_FILE_NEW, enable)
329 wx.CallAfter(self.toolbar.EnableTool, self.TB_FILE_CLOSE, enable)
330 wx.CallAfter(self.toolbar.EnableTool, self.TB_FILE_CLOSE_ALL, enable)
331 wx.CallAfter(self.toolbar.EnableTool, self.TB_FILE_OPEN, enable)
332 wx.CallAfter(self.toolbar.EnableTool, self.TB_FILE_SAVE, enable)
333 wx.CallAfter(self.toolbar.EnableTool, self.TB_FILE_SAVE_AS, enable)
334
335
336 - def exit_gui(self, event=None):
337 """Catch the main window closure and perform the exit procedure.
338
339 @param event: The wx event.
340 @type event: wx event
341 """
342
343
344 doexit = wx.ID_YES
345 if status.show_gui and not ds.is_empty():
346 doexit = Question('Are you sure you would like to quit relax? All unsaved data will be lost.', title='Exit relax', default=True).ShowModal()
347
348
349 if doexit == wx.ID_YES:
350
351 io_streams_restore(verbosity=0)
352
353
354 info = Info_box()
355
356
357 if platform.uname()[0] in ['Windows', 'Microsoft']:
358 width = 80
359 else:
360 width = 100
361
362
363 text = "\n\nThank you for citing:\n"
364 text = text + "\n\nrelaxGUI\n========\n\n"
365 for line in wrap(info.bib['Bieri11'].cite_short(), width):
366 text = text + line + '\n'
367 text = text + "\n\n\nrelax\n=====\n\n"
368 for line in wrap(info.bib['dAuvergneGooley08a'].cite_short(), width):
369 text = text + line + '\n'
370 text = text + '\n'
371 for line in wrap(info.bib['dAuvergneGooley08b'].cite_short(), width):
372 text = text + line + '\n'
373 text = text + '\n'
374 sys.stdout.write(text)
375
376
377 if hasattr(self, 'taskbar_icon'):
378 self.taskbar_icon.Destroy()
379
380
381 wx.Exit()
382
383
384 - def init_data(self):
385 """Initialise the data used by the GUI interface."""
386
387
388 self.save_file = None
389
390
391 ds.relax_gui = Gui()
392
393
395 """Open the free file format settings window.
396
397 @param event: The wx event.
398 @type event: wx event
399 """
400
401
402 win = Free_file_format()
403
404
405 if status.show_gui:
406 win.Show()
407
408
409 - def references(self, event):
410 """Display the references relevant for relax.
411
412 @param event: The wx event.
413 @type event: wx event
414 """
415
416
417 self.references = References(self)
418 if status.show_gui:
419 self.references.Show()
420
421
422 - def relax_manual(self, event):
423 """Display the relax manual.
424
425 @param event: The wx event.
426 @type event: wx event
427 """
428
429
430 file = status.install_path + sep+"docs"+sep+"relax.pdf"
431
432
433 if not access(file, F_OK):
434 error_message("The relax manual '%s' cannot be found. Please compile using the scons program." % file)
435 return
436
437
438 open_file(file)
439
440
441 - def run_test_suite(self, event, categories=['system', 'unit', 'gui']):
442 """Execute the full test suite.
443
444 @param event: The wx event.
445 @type event: wx event
446 @keyword categories: The list of test categories to run, for example ['system', 'unit', 'gui'] for all tests.
447 @type categories: list of str
448 """
449
450
451 msg = "In running the test suite, relax will be reset and all data lost. Are you sure you would like to run the test suite?"
452 if Question(msg, parent=self, default=False).ShowModal() == wx.ID_NO:
453 return
454
455
456 self.test_suite_flag = True
457
458
459 wx.BeginBusyCursor()
460
461
462 orig_style = self.controller.GetWindowStyle()
463 self.controller.SetWindowStyle(orig_style | wx.STAY_ON_TOP)
464 self.controller.Refresh()
465
466
467 self.controller.MakeModal(True)
468
469
470 if hasattr(self, 'spin_viewer'):
471 self.spin_viewer.Close()
472 if hasattr(self, 'pipe_editor'):
473 self.pipe_editor.Close()
474 if hasattr(self, 'results_viewer'):
475 self.results_viewer.Close()
476 if hasattr(self, 'relax_prompt'):
477 self.relax_prompt.Close()
478
479
480 reset()
481
482
483 self.show_controller(event)
484
485
486 wx.GetApp().Yield(True)
487
488
489 status.show_gui = False
490
491
492 runner = test_suite.test_suite_runner.Test_suite_runner([], from_gui=True, categories=categories)
493 runner.run_all_tests()
494
495
496 status.show_gui = True
497
498
499 if wx.IsBusy():
500 wx.EndBusyCursor()
501
502
503 self.controller.SetWindowStyle(orig_style)
504 self.controller.MakeModal(False)
505 self.controller.Refresh()
506
507
508 self.test_suite_flag = False
509
510
511 - def run_test_suite_gui(self, event):
512 """Execute the GUI tests.
513
514 @param event: The wx event.
515 @type event: wx event
516 """
517
518
519 self.run_test_suite(event, categories=['gui'])
520
521
522 - def run_test_suite_sys(self, event):
523 """Execute the system tests.
524
525 @param event: The wx event.
526 @type event: wx event
527 """
528
529
530 self.run_test_suite(event, categories=['system'])
531
532
533 - def run_test_suite_unit(self, event):
534 """Execute the unit tests.
535
536 @param event: The wx event.
537 @type event: wx event
538 """
539
540
541 self.run_test_suite(event, categories=['unit'])
542
543
544 - def show_controller(self, event):
545 """Display the relax controller window.
546
547 @param event: The wx event.
548 @type event: wx event
549 """
550
551
552 if self.controller.IsShown():
553 self.controller.Raise()
554 return
555
556
557 if status.show_gui:
558 self.controller.Show()
559
560
561 - def show_pipe_editor(self, event):
562 """Display the data pipe editor window.
563
564 @param event: The wx event.
565 @type event: wx event
566 """
567
568
569 if status.exec_lock.locked():
570 dlg = wx.MessageDialog(self, "Leaving the pipe editor window open will slow down the calculations.", caption="Warning", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP)
571 if status.show_gui:
572 dlg.ShowModal()
573
574
575 if not hasattr(self, 'pipe_editor'):
576 self.pipe_editor = Pipe_editor(gui=self)
577
578
579 if self.pipe_editor.IsShown():
580 self.pipe_editor.Raise()
581 return
582
583
584 if status.show_gui and not self.pipe_editor.IsShown():
585 self.pipe_editor.Show()
586
587
588 - def show_prompt(self, event):
589 """Display the relax prompt window.
590
591 @param event: The wx event.
592 @type event: wx event
593 """
594
595
596 if not hasattr(self, 'relax_prompt'):
597 self.relax_prompt = Prompt(None, -1, "", parent=self)
598
599
600 if self.relax_prompt.IsShown():
601 self.relax_prompt.Raise()
602 return
603
604
605 if status.show_gui:
606 self.relax_prompt.Show()
607
608
609 - def show_results_viewer(self, event=None):
610 """Display the analysis results.
611
612 @param event: The wx event.
613 @type event: wx event
614 """
615
616
617 wx.CallAfter(self.show_results_viewer_safe, warn=True)
618
619
620 - def show_results_viewer_safe(self, warn=False):
621 """Display the analysis results in a thread safe wx.CallAfter call.
622
623 @keyword warn: A flag which if True will cause a message dialog to appear warning about keeping the window open with the execution lock.
624 @type warn: bool
625 """
626
627
628 if warn and status.exec_lock.locked():
629 dlg = wx.MessageDialog(self, "Leaving the results viewer window open will slow down the calculations.", caption="Warning", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP)
630 if status.show_gui:
631 wx.CallAfter(dlg.ShowModal)
632
633
634 if not hasattr(self, 'results_viewer'):
635 self.results_viewer = Results_viewer(self)
636
637
638 if self.results_viewer.IsShown():
639 self.results_viewer.Raise()
640 return
641
642
643 if status.show_gui and not self.results_viewer.IsShown():
644 self.results_viewer.Show()
645
646
648 """Display the analysis results."""
649
650
651 wx.CallAfter(self.show_results_viewer_safe, warn=False)
652
653
654 - def show_tree(self, event):
655 """Display the molecule, residue, and spin tree window.
656
657 @param event: The wx event.
658 @type event: wx event
659 """
660
661
662 if status.exec_lock.locked():
663 dlg = wx.MessageDialog(self, "Leaving the spin viewer window open will slow down the calculations.", caption="Warning", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP)
664 if status.show_gui:
665 dlg.ShowModal()
666
667
668 if not hasattr(self, 'spin_viewer'):
669 self.spin_viewer = Spin_view_window(None, -1, "", parent=self)
670
671
672 if self.spin_viewer.IsShown():
673 self.spin_viewer.Raise()
674 return
675
676
677 if status.show_gui and not self.spin_viewer.IsShown():
678 self.spin_viewer.Show()
679
680
681 - def state_load(self, event=None, file_name=None):
682 """Load the program state.
683
684 @param event: The wx event.
685 @type event: wx event
686 @keyword file_name: The name of the file to load (for dialogless operation).
687 @type file_name: str
688 """
689
690
691 if status.exec_lock.locked():
692 return
693
694
695 if not self.analysis.init_state:
696
697 msg = "Loading a saved relax state file will cause all unsaved data to be lost. Are you sure you would to open a save file?"
698
699
700 if status.show_gui and Question(msg, default=True, size=(400, 150)).ShowModal() == wx.ID_NO:
701 return
702
703
704 if not file_name:
705 dialog = RelaxFileDialog(parent=self, message='Select the relax save state file', defaultFile='state.bz2', wildcard='relax save files (*.bz2;*.gz)|*.bz2;*.gz|All files (*)|*', style=wx.FD_OPEN)
706
707
708 if status.show_gui and dialog.ShowModal() != wx.ID_OK:
709
710 return
711
712
713 file_name = gui_to_str(dialog.get_file())
714
715
716 wx.Yield()
717
718
719 wx.BeginBusyCursor()
720 self.Freeze()
721
722
723 try:
724
725 self.analysis.delete_all()
726
727
728 reset()
729
730
731 self.save_file = file_name
732
733
734 if protected_exec(state.load_state, file_name, verbosity=0):
735
736 self.analysis.load_from_store()
737
738
739 self.sync_ds(upload=False)
740
741
742 else:
743
744 self.init_data()
745
746
747 finally:
748 self.Thaw()
749
750
751 if wx.IsBusy():
752 wx.EndBusyCursor()
753
754
755 - def state_save(self):
756 """Save the program state."""
757
758
759 self.sync_ds(upload=True)
760
761
762 try:
763 wx.BeginBusyCursor()
764 state.save_state(self.save_file, verbosity=0, force=True)
765
766
767 sleep(1)
768
769
770 finally:
771 if wx.IsBusy():
772 wx.EndBusyCursor()
773
774
775 - def sync_ds(self, upload=False):
776 """Synchronise the GUI and the relax data store, both ways.
777
778 This method allows the GUI information to be uploaded into the relax data store, or for the information in the relax data store to be downloaded by the GUI.
779
780 @keyword upload: A flag which if True will cause the GUI to send data to the relax data store. If False, data will be downloaded from the relax data store to update the GUI.
781 @type upload: bool
782 """
783
784
785 for page in self.analysis.analysis_loop():
786
787 if hasattr(page, 'sync_ds'):
788 page.sync_ds(upload)
789
790
792 """Create the toolbar."""
793
794
795 self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL|wx.TB_FLAT)
796
797
798 self.TB_FILE_NEW = wx.NewId()
799 self.toolbar.AddLabelTool(self.TB_FILE_NEW, "New analysis", wx.Bitmap(paths.icon_22x22.new, wx.BITMAP_TYPE_ANY), shortHelp="New analysis")
800 self.Bind(wx.EVT_TOOL, self.analysis.menu_new, id=self.TB_FILE_NEW)
801
802
803 self.TB_FILE_CLOSE = wx.NewId()
804 self.toolbar.AddLabelTool(self.TB_FILE_CLOSE, "Close analysis", wx.Bitmap(paths.icon_22x22.document_close, wx.BITMAP_TYPE_ANY), shortHelp="Close analysis")
805 self.Bind(wx.EVT_TOOL, self.analysis.menu_close, id=self.TB_FILE_CLOSE)
806
807
808 self.TB_FILE_CLOSE_ALL = wx.NewId()
809 self.toolbar.AddLabelTool(self.TB_FILE_CLOSE_ALL, "Close all analyses", wx.Bitmap(paths.icon_22x22.dialog_close, wx.BITMAP_TYPE_ANY), shortHelp="Close all analyses")
810 self.Bind(wx.EVT_TOOL, self.analysis.menu_close_all, id=self.TB_FILE_CLOSE_ALL)
811
812
813 self.toolbar.AddSeparator()
814
815
816 self.TB_FILE_OPEN = wx.NewId()
817 self.toolbar.AddLabelTool(self.TB_FILE_OPEN, "Open relax state", wx.Bitmap(paths.icon_22x22.document_open, wx.BITMAP_TYPE_ANY), shortHelp="Open relax state")
818 self.Bind(wx.EVT_TOOL, self.state_load, id=self.TB_FILE_OPEN)
819
820
821 self.TB_FILE_SAVE = wx.NewId()
822 self.toolbar.AddLabelTool(self.TB_FILE_SAVE, "Save relax state", wx.Bitmap(paths.icon_22x22.document_save, wx.BITMAP_TYPE_ANY), shortHelp="Save relax state")
823 self.Bind(wx.EVT_TOOL, self.action_state_save, id=self.TB_FILE_SAVE)
824
825
826 self.TB_FILE_SAVE_AS = wx.NewId()
827 self.toolbar.AddLabelTool(self.TB_FILE_SAVE_AS, "Save as", wx.Bitmap(paths.icon_22x22.document_save_as, wx.BITMAP_TYPE_ANY), shortHelp="Save as")
828 self.Bind(wx.EVT_TOOL, self.action_state_save_as, id=self.TB_FILE_SAVE_AS)
829
830
831 self.toolbar.AddSeparator()
832
833
834 self.TB_VIEW_CONTROLLER = wx.NewId()
835 self.toolbar.AddLabelTool(self.TB_VIEW_CONTROLLER, "Controller", wx.Bitmap(paths.icon_22x22.preferences_system_performance, wx.BITMAP_TYPE_ANY), shortHelp="relax controller")
836 self.Bind(wx.EVT_TOOL, self.show_controller, id=self.TB_VIEW_CONTROLLER)
837
838
839 self.TB_VIEW_SPIN_VIEW = wx.NewId()
840 self.toolbar.AddLabelTool(self.TB_VIEW_SPIN_VIEW, "Spin viewer", wx.Bitmap(paths.icon_22x22.spin, wx.BITMAP_TYPE_ANY), shortHelp="Spin viewer window")
841 self.Bind(wx.EVT_TOOL, self.show_tree, id=self.TB_VIEW_SPIN_VIEW)
842
843
844 self.TB_VIEW_RESULTS = wx.NewId()
845 self.toolbar.AddLabelTool(self.TB_VIEW_RESULTS, "Results viewer", wx.Bitmap(paths.icon_22x22.view_statistics, wx.BITMAP_TYPE_ANY), shortHelp="Results viewer window")
846 self.Bind(wx.EVT_TOOL, self.show_results_viewer, id=self.TB_VIEW_RESULTS)
847
848
849 self.TB_VIEW_PIPE_EDIT = wx.NewId()
850 self.toolbar.AddLabelTool(self.TB_VIEW_PIPE_EDIT, "Data pipe editor", wx.Bitmap(paths.icon_22x22.pipe, wx.BITMAP_TYPE_ANY), shortHelp="Data pipe editor")
851 self.Bind(wx.EVT_TOOL, self.show_pipe_editor, id=self.TB_VIEW_PIPE_EDIT)
852
853
854 self.TB_VIEW_PROMPT = wx.NewId()
855 self.toolbar.AddLabelTool(self.TB_VIEW_PROMPT, "relax prompt", wx.Bitmap(paths.icon_22x22.relax_prompt, wx.BITMAP_TYPE_ANY), shortHelp="The relax prompt GUI window")
856 self.Bind(wx.EVT_TOOL, self.show_prompt, id=self.TB_VIEW_PROMPT)
857
858
859 self.toolbar.Realize()
860
861
863 """Update the status bar info."""
864
865
866 pipe = cdp_name()
867
868
869 if pipe == None:
870 pipe = ''
871
872
873 wx.CallAfter(self.status_bar.SetStatusText, "(C) 2001-2012 the relax development team", 0)
874 wx.CallAfter(self.status_bar.SetStatusText, "Current data pipe:", 1)
875 wx.CallAfter(self.status_bar.SetStatusText, pipe, 2)
876