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 """Log window of relax GUI controlling all calculations."""
26
27
28 from Queue import Queue
29 import sys
30 import wx
31 import wx.stc
32
33
34 from generic_fns.pipes import cdp_name
35 from relax_io import SplitIO
36 from status import Status; status = Status()
37
38
39 from gui.components.menu import build_menu_item
40 from gui.fonts import font
41 from gui.icons import relax_icons
42 from gui.misc import add_border, str_to_gui
43 from gui.paths import IMAGE_PATH, icon_16x16
44 from info import Info_box
45
46
48 """The relax controller window."""
49
51 """Set up the relax controller frame.
52
53 @param gui: The GUI object.
54 @type gui: wx.Frame instance
55 """
56
57
58 self.gui = gui
59
60
61 super(Controller, self).__init__(self.gui, -1, style=wx.DEFAULT_FRAME_STYLE)
62
63
64 self.size_x = 800
65 self.size_y = 700
66 self.border = 5
67 self.spacer = 10
68
69
70 sizer = self.setup_frame()
71
72
73 self.add_relax_logo(sizer)
74
75
76 sizer.AddSpacer(20)
77
78
79 self.name = self.add_text(self.main_panel, sizer, "Current GUI analysis:")
80
81
82 self.cdp = self.add_text(self.main_panel, sizer, "Current data pipe:")
83
84
85 self.create_rx(sizer)
86
87
88 self.create_mf(sizer)
89
90
91 self.main_gauge = self.add_gauge(self.main_panel, sizer, "Execution progress:", tooltip="This gauge will pulse while relax is executing an auto-analysis (when the execution lock is turned on) and will be set to 100% once the analysis is complete.")
92
93
94 self.log_queue = Queue()
95
96
97 self.log_panel = LogCtrl(self.main_panel, self, log_queue=self.log_queue, id=-1)
98 sizer.Add(self.log_panel, 1, wx.EXPAND|wx.ALL, 0)
99
100
101 out = Redirect_text(self.log_panel, self.log_queue, orig_io=sys.stdout, stream=0)
102 if sys.stdout == sys.__stdout__:
103 sys.stdout = out
104 else:
105 split_stdout = SplitIO()
106 split_stdout.split(sys.stdout, out)
107 sys.stdout = split_stdout
108
109
110 err = Redirect_text(self.log_panel, self.log_queue, orig_io=sys.stderr, stream=1)
111 if sys.stderr == sys.__stderr__:
112 sys.stderr = err
113 else:
114 split_stderr = SplitIO()
115 split_stderr.split(sys.stderr, err)
116 sys.stderr = split_stderr
117
118
119 self.update_controller()
120
121
122 self.timer = wx.Timer(self)
123 self.Bind(wx.EVT_TIMER, self.handler_timer, self.timer)
124
125
126 info = Info_box()
127 print(info.intro_text())
128
129
130 status.observers.pipe_alteration.register('controller', self.update_controller)
131 status.observers.auto_analyses.register('controller', self.update_controller)
132 status.observers.gui_analysis.register('controller', self.update_controller)
133 status.observers.exec_lock.register('controller', self.update_gauge)
134
135
136 - def add_gauge(self, parent, sizer, desc, tooltip=None):
137 """Add a gauge to the sizer and return it.
138
139 @param parent: The parent GUI element.
140 @type parent: wx object
141 @param sizer: The sizer element to pack the element into.
142 @type sizer: wx.Sizer instance
143 @param desc: The description to display.
144 @type desc: str
145 @keyword tooltip: The tooltip which appears on hovering over the text and the gauge.
146 @type tooltip: str
147 @return: The gauge element.
148 @rtype: wx.Gauge instance
149 """
150
151
152 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
153
154
155 text = wx.StaticText(parent, -1, desc, style=wx.ALIGN_LEFT)
156 text.SetFont(font.normal)
157 sub_sizer.Add(text, 1, wx.ALIGN_CENTER_VERTICAL, 0)
158
159
160 gauge = wx.Gauge(parent, id=-1, range=100, style=wx.GA_SMOOTH)
161 gauge.SetSize((-1, 20))
162 sub_sizer.Add(gauge, 3, wx.EXPAND|wx.ALL, 0)
163
164
165 sizer.Add(sub_sizer, 0, wx.ALL|wx.EXPAND, 0)
166
167
168 sizer.AddSpacer(self.spacer)
169
170
171 if tooltip:
172 text.SetToolTipString(tooltip)
173 gauge.SetToolTipString(tooltip)
174
175
176 return gauge
177
178
180 """Add the relax logo to the sizer.
181
182 @param sizer: The sizer element to pack the relax logo into.
183 @type sizer: wx.Sizer instance
184 """
185
186
187 logo = wx.StaticBitmap(self.main_panel, -1, wx.Bitmap(IMAGE_PATH+'relax.gif', wx.BITMAP_TYPE_ANY))
188
189
190 sizer.Add(logo, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 0)
191
192
193 sizer.AddSpacer(self.spacer)
194
195
196 - def add_text(self, parent, sizer, desc, tooltip=None):
197 """Add the current data pipe element.
198
199 @param parent: The parent GUI element.
200 @type parent: wx object
201 @param sizer: The sizer element to pack the element into.
202 @type sizer: wx.Sizer instance
203 @param desc: The description to display.
204 @type desc: str
205 @keyword tooltip: The tooltip which appears on hovering over the text and field.
206 @type tooltip: str
207 @return: The text control.
208 @rtype: wx.TextCtrl instance
209 """
210
211
212 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
213
214
215 text = wx.StaticText(parent, -1, desc, style=wx.ALIGN_LEFT)
216 text.SetFont(font.normal)
217 sub_sizer.Add(text, 1, wx.ALIGN_CENTER_VERTICAL, 0)
218
219
220 field = wx.TextCtrl(parent, -1, '', style=wx.ALIGN_LEFT)
221 field.SetEditable(False)
222 field.SetFont(font.normal)
223 colour = self.main_panel.GetBackgroundColour()
224 field.SetOwnBackgroundColour(colour)
225 sub_sizer.Add(field, 3, wx.ALIGN_CENTER_VERTICAL, 0)
226
227
228 sizer.Add(sub_sizer, 0, wx.ALL|wx.EXPAND, 0)
229
230
231 sizer.AddSpacer(self.spacer)
232
233
234 if tooltip:
235 text.SetToolTipString(tooltip)
236 field.SetToolTipString(tooltip)
237
238
239 return field
240
241
243 """Return the key for the current analysis' status object.
244
245 @return: The current analysis' status object key.
246 @rtype: str or None
247 """
248
249
250 data = self.gui.analysis.current_data()
251 if data == None:
252 return
253
254
255 if hasattr(data, 'pipe_name'):
256 return data.pipe_name
257
258
260 """Create the model-free specific panel.
261
262 @param sizer: The sizer element to pack the element into.
263 @type sizer: wx.Sizer instance
264 """
265
266
267 self.panel_mf = wx.Panel(self.main_panel, -1)
268 sizer.Add(self.panel_mf, 0, wx.ALL|wx.EXPAND, 0)
269
270
271 panel_sizer = wx.BoxSizer(wx.VERTICAL)
272 self.panel_mf.SetSizer(panel_sizer)
273
274
275 self.global_model_mf = self.add_text(self.panel_mf, panel_sizer, "Global model:", tooltip="This shows the global diffusion model of the dauvergne_protocol auto-analysis currently being optimised. It will be one of 'local_tm', 'sphere', 'prolate', 'oblate', 'ellipsoid' or 'final'.")
276
277
278 self.progress_gauge_mf = self.add_gauge(self.panel_mf, panel_sizer, "Incremental progress:", tooltip="This shows the global iteration round of the dauvergne_protocol auto-analysis. Optimisation of the global model may require between 5 to 15 iterations. The maximum number of iterations should not be reached. Once the global diffusion model has converged, this gauge will be set to 100%")
279
280
281 self.mc_gauge_mf = self.add_gauge(self.panel_mf, panel_sizer, "Monte Carlo simulations:", tooltip="The Monte Carlo simulation number. Simulations are only performed at the very end of the analysis in the 'final' global model.")
282
283
285 """Create the relaxation curve-fitting specific panel.
286
287 @param sizer: The sizer element to pack the element into.
288 @type sizer: wx.Sizer instance
289 """
290
291
292 self.panel_rx = wx.Panel(self.main_panel, -1)
293 sizer.Add(self.panel_rx, 0, wx.ALL|wx.EXPAND, 0)
294
295
296 panel_sizer = wx.BoxSizer(wx.VERTICAL)
297 self.panel_rx.SetSizer(panel_sizer)
298
299
300 self.mc_gauge_rx = self.add_gauge(self.panel_rx, panel_sizer, "Monte Carlo simulations:", tooltip="The Monte Carlo simulation number.")
301
302
304 """Event handler for the close window action.
305
306 @param event: The wx event.
307 @type event: wx event
308 """
309
310
311 if self.gui.test_suite_flag:
312 return
313
314
315 self.Hide()
316
317
319 """Event handler for the timer.
320
321 @param event: The wx event.
322 @type event: wx event
323 """
324
325
326 wx.CallAfter(self.main_gauge.Pulse)
327
328
329 if not status.exec_lock.locked() and self.timer.IsRunning():
330 self.timer.Stop()
331 self.update_gauge()
332
333
335 """Set up the relax controller frame.
336 @return: The sizer object.
337 @rtype: wx.Sizer instance
338 """
339
340
341 self.SetTitle("The relax controller")
342
343
344 self.SetIcons(relax_icons)
345
346
347 self.main_panel = wx.Panel(self, -1)
348
349
350 main_sizer = wx.BoxSizer(wx.VERTICAL)
351 self.main_panel.SetSizer(main_sizer)
352
353
354 sizer = add_border(main_sizer, border=self.border, packing=wx.VERTICAL)
355
356
357 self.Bind(wx.EVT_CLOSE, self.handler_close)
358
359
360 self.SetSize((self.size_x, self.size_y))
361
362
363 self.Centre()
364
365
366 return sizer
367
368
410
411
413 """Update the main execution gauge."""
414
415
416 if status.exec_lock.locked():
417
418 if not self.timer.IsRunning():
419 wx.CallAfter(self.timer.Start, 100)
420
421
422 return
423
424
425 key = self.analysis_key()
426 if key and status.auto_analysis.has_key(key) and status.auto_analysis[key].fin:
427
428 if self.timer.IsRunning():
429 self.timer.Stop()
430
431
432 if hasattr(self, 'mc_gauge_rx'):
433 wx.CallAfter(self.mc_gauge_rx.SetValue, 100)
434
435
436 if hasattr(self, 'mc_gauge_mf'):
437 wx.CallAfter(self.mc_gauge_mf.SetValue, 100)
438 if hasattr(self, 'progress_gauge_mf'):
439 wx.CallAfter(self.progress_gauge_mf.SetValue, 100)
440
441
442 wx.CallAfter(self.main_gauge.SetValue, 100)
443
444
445 if not self.main_gauge.GetValue():
446 return
447
448
449 if not key or not status.auto_analysis.has_key(key):
450 wx.CallAfter(self.main_gauge.SetValue, 0)
451
452
453 if key and status.auto_analysis.has_key(key) and not status.auto_analysis[key].fin:
454
455 if hasattr(self, 'mc_gauge_rx'):
456 wx.CallAfter(self.mc_gauge_rx.SetValue, 0)
457
458
459 if hasattr(self, 'mc_gauge_mf'):
460 wx.CallAfter(self.mc_gauge_mf.SetValue, 0)
461 if hasattr(self, 'progress_gauge_mf'):
462 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
463
464
465 wx.CallAfter(self.main_gauge.SetValue, 0)
466
467
469 """Update the model-free specific elements."""
470
471
472 key = self.analysis_key()
473 if not key:
474 return
475
476
477 elif not status.auto_analysis.has_key(key) and cdp_name() == 'final':
478 wx.CallAfter(self.mc_gauge_mf.SetValue, 100)
479 wx.CallAfter(self.progress_gauge_mf.SetValue, 100)
480 wx.CallAfter(self.main_gauge.SetValue, 100)
481 return
482
483
484 if not status.auto_analysis.has_key(key):
485 wx.CallAfter(self.mc_gauge_mf.SetValue, 0)
486 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
487 wx.CallAfter(self.main_gauge.SetValue, 0)
488 return
489
490
491 wx.CallAfter(self.global_model_mf.SetValue, str_to_gui(status.auto_analysis[key].diff_model))
492
493
494 if status.auto_analysis[key].diff_model == 'local_tm':
495 if status.auto_analysis[key].current_model:
496
497 no = int(status.auto_analysis[key].current_model[2:])
498
499
500 total_models = len(status.auto_analysis[key].local_tm_models)
501
502
503 percent = int(100 * no / float(total_models))
504 wx.CallAfter(self.progress_gauge_mf.SetValue, percent)
505
506
507 elif status.auto_analysis[key].diff_model in ['sphere', 'prolate', 'oblate', 'ellipsoid']:
508
509 if status.auto_analysis[key].round == None:
510 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
511 else:
512
513 percent = int(100 * (status.auto_analysis[key].round + 1) / (status.auto_analysis[key].max_iter + 1))
514
515
516 wx.CallAfter(self.progress_gauge_mf.SetValue, percent)
517
518
519 if status.auto_analysis[key].mc_number:
520
521 percent = int(100 * (status.auto_analysis[key].mc_number + 1) / cdp.sim_number)
522
523
524 wx.CallAfter(self.mc_gauge_mf.SetValue, percent)
525
526
528 """Update the Rx specific elements."""
529
530
531 key = self.analysis_key()
532 if not key:
533 return
534
535
536 if not status.auto_analysis.has_key(key):
537 wx.CallAfter(self.mc_gauge_rx.SetValue, 0)
538 wx.CallAfter(self.main_gauge.SetValue, 0)
539 return
540
541
542 if status.auto_analysis[key].mc_number:
543
544 percent = int(100 * (status.auto_analysis[key].mc_number + 1) / cdp.sim_number)
545
546
547 wx.CallAfter(self.mc_gauge_rx.SetValue, percent)
548
549
550
551 -class LogCtrl(wx.stc.StyledTextCtrl):
552 """A special control designed to display relax output messages."""
553
554 - def __init__(self, parent, controller, log_queue=None, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.BORDER_SUNKEN, name=wx.stc.STCNameStr):
555 """Set up the log control.
556
557 @param parent: The parent wx window object.
558 @type parent: Window
559 @param controller: The controller window.
560 @type controller: wx.Frame instance
561 @keyword log_queue: The queue of log messages.
562 @type log_queue: Queue.Queue instance
563 @keyword id: The wx ID.
564 @type id: int
565 @keyword pos: The window position.
566 @type pos: Point
567 @keyword size: The window size.
568 @type size: Size
569 @keyword style: The StyledTextCtrl to apply.
570 @type style: long
571 @keyword name: The window name.
572 @type name: str
573 """
574
575
576 self.controller = controller
577 self.log_queue = log_queue
578
579
580 super(LogCtrl, self).__init__(parent, id=id, pos=pos, size=size, style=style, name=name)
581
582
583 self.StyleSetFont(0, font.modern_small)
584
585
586 self.StyleSetForeground(1, wx.NamedColour('red'))
587 self.StyleSetFont(1, font.modern_small)
588
589
590 self.StyleSetForeground(2, wx.NamedColour('blue'))
591 self.StyleSetFont(2, font.modern_small_bold)
592
593
594 self.StyleSetForeground(3, wx.NamedColour('orange red'))
595 self.StyleSetFont(3, font.modern_small)
596
597
598 self.StyleSetForeground(4, wx.NamedColour('dark green'))
599 self.StyleSetFont(4, font.modern_small)
600
601
602 self.find_dlg = None
603
604
605 self.find_data = wx.FindReplaceData()
606 self.find_data.SetFlags(wx.FR_DOWN)
607
608
609 self.UsePopUp(0)
610
611
612 self.menu_id_find = wx.NewId()
613 self.menu_id_copy = wx.NewId()
614 self.menu_id_select_all = wx.NewId()
615 self.menu_id_zoom_in = wx.NewId()
616 self.menu_id_zoom_out = wx.NewId()
617 self.menu_id_zoom_orig = wx.NewId()
618 self.menu_id_goto_start = wx.NewId()
619 self.menu_id_goto_end = wx.NewId()
620
621
622 self.SetReadOnly(True)
623
624
625 self.orig_zoom = self.GetZoom()
626
627
628 self.Bind(wx.EVT_FIND, self.find)
629 self.Bind(wx.EVT_FIND_NEXT, self.find)
630 self.Bind(wx.EVT_FIND_CLOSE, self.find_close)
631 self.Bind(wx.EVT_KEY_DOWN, self.capture_keys)
632 self.Bind(wx.EVT_RIGHT_DOWN, self.pop_up_menu)
633 self.Bind(wx.EVT_MENU, self.find_open, id=self.menu_id_find)
634 self.Bind(wx.EVT_MENU, self.on_copy, id=self.menu_id_copy)
635 self.Bind(wx.EVT_MENU, self.on_select_all, id=self.menu_id_select_all)
636 self.Bind(wx.EVT_MENU, self.on_zoom_in, id=self.menu_id_zoom_in)
637 self.Bind(wx.EVT_MENU, self.on_zoom_out, id=self.menu_id_zoom_out)
638 self.Bind(wx.EVT_MENU, self.on_zoom_orig, id=self.menu_id_zoom_orig)
639 self.Bind(wx.EVT_MENU, self.on_goto_start, id=self.menu_id_goto_start)
640 self.Bind(wx.EVT_MENU, self.on_goto_end, id=self.menu_id_goto_end)
641
642
644 """Control which key events are active, preventing text insertion and deletion.
645
646 @param event: The wx event.
647 @type event: wx event
648 """
649
650
651 if event.ControlDown() and event.GetKeyCode() == 67:
652 event.Skip()
653
654
655 if event.ControlDown() and event.GetKeyCode() == 70:
656 self.find_open(event)
657
658
659 if event.ControlDown() and event.GetKeyCode() == 65:
660 event.Skip()
661
662
663 if 'darwin' in sys.platform and event.ControlDown() and event.GetKeyCode() == 71:
664 self.find_next(event)
665 elif 'darwin' not in sys.platform and event.GetKeyCode() == 342:
666 self.find_next(event)
667
668
669 if event.GetKeyCode() in [312, 313, 314, 315, 316, 317]:
670 event.Skip()
671
672
673 if event.GetKeyCode() in [366, 367]:
674 event.Skip()
675
676
677 if event.ControlDown() and event.GetKeyCode() == 48:
678 self.on_zoom_orig(event)
679 if event.ControlDown() and event.GetKeyCode() == 45:
680 self.on_zoom_out(event)
681 if event.ControlDown() and event.GetKeyCode() == 61:
682 self.on_zoom_in(event)
683
684
685 if event.ControlDown() and event.GetKeyCode() == 316:
686 self.on_goto_start(event)
687 elif event.ControlDown() and event.GetKeyCode() == 317:
688 self.on_goto_end(event)
689
690
691 - def find(self, event):
692 """Find the text in the log control.
693
694 @param event: The wx event.
695 @type event: wx event
696 """
697
698
699 sel = self.find_data.GetFindString()
700
701
702 flags = event.GetFlags()
703
704
705 pos = self.GetCurrentPos()
706 if pos != self.GetLength():
707 self.SetCurrentPos(pos+1)
708 self.SearchAnchor()
709
710
711 forwards = wx.FR_DOWN & flags
712
713
714 if forwards:
715 pos = self.SearchNext(flags, sel)
716
717
718 else:
719 pos = self.SearchPrev(flags, sel)
720
721
722 if pos == -1:
723
724 if forwards:
725 self.GotoPos(self.GetLength())
726 else:
727 self.GotoPos(pos)
728
729
730 text = "The string '%s' could not be found." % sel
731 nothing = wx.MessageDialog(self, text, caption="Not found", style=wx.ICON_INFORMATION|wx.OK)
732 nothing.SetSize((300, 200))
733 if status.show_gui:
734 nothing.ShowModal()
735 nothing.Destroy()
736
737
738 else:
739
740 self.EnsureCaretVisible()
741
742
744 """Close the find dialog.
745
746 @param event: The wx event.
747 @type event: wx event
748 """
749
750
751 self.find_dlg.Destroy()
752
753
754 self.find_dlg = None
755
756
758 """Display the text finding dialog.
759
760 @param event: The wx event.
761 @type event: wx event
762 """
763
764
765 if self.find_dlg == None:
766 self.find_dlg = wx.FindReplaceDialog(self, self.find_data, "Find")
767 if status.show_gui:
768 self.find_dlg.Show(True)
769
770
771 else:
772 self.find_dlg.Show()
773
774
776 """Find the next instance of the text.
777
778 @param event: The wx event.
779 @type event: wx event
780 """
781
782
783 if self.find_data.GetFindString():
784 self.find(event)
785
786
787 else:
788 self.find_open(event)
789
790
791 - def get_text(self):
792 """Concatenate all of the text from the log queue and return it as a string.
793
794 @return: A list of the text from the log queue and a list of the streams these correspond to.
795 @rtype: list of str, list of int
796 """
797
798
799 string_list = ['']
800 stream_list = [0]
801
802
803 while True:
804
805 if self.log_queue.empty():
806 break
807
808
809 msg, stream = self.log_queue.get()
810
811
812 if msg[1:7] == 'relax>':
813
814 string_list[-1] += '\n'
815
816
817 string_list.append('relax>')
818 stream_list.append(2)
819
820
821 msg = msg[7:]
822
823
824 string_list.append('')
825 stream_list.append(stream)
826
827
828 elif msg[0:13] == 'RelaxWarning:':
829
830 string_list.append(msg)
831 stream_list.append(3)
832 continue
833
834
835 elif msg[0:6] == 'debug>':
836
837 string_list.append(msg)
838 stream_list.append(4)
839 continue
840
841
842 if stream_list[-1] != stream:
843 string_list.append('')
844 stream_list.append(stream)
845
846
847 string_list[-1] = string_list[-1] + msg
848
849
850 return string_list, stream_list
851
852
883
884
886 """Copy the selected text.
887
888 @param event: The wx event.
889 @type event: wx event
890 """
891
892
893 self.Copy()
894
895
897 """Move to the end of the text.
898
899 @param event: The wx event.
900 @type event: wx event
901 """
902
903
904 self.GotoPos(self.GetLength())
905
906
908 """Move to the start of the text.
909
910 @param event: The wx event.
911 @type event: wx event
912 """
913
914
915 self.GotoPos(-1)
916
917
919 """Select all text in the control.
920
921 @param event: The wx event.
922 @type event: wx event
923 """
924
925
926 self.SelectAll()
927
928
930 """Zoom in by increase the font by 1 point size.
931
932 @param event: The wx event.
933 @type event: wx event
934 """
935
936
937 self.ZoomIn()
938
939
941 """Zoom to the original zoom level.
942
943 @param event: The wx event.
944 @type event: wx event
945 """
946
947
948 self.SetZoom(self.orig_zoom)
949
950
952 """Zoom out by decreasing the font by 1 point size.
953
954 @param event: The wx event.
955 @type event: wx event
956 """
957
958
959 self.ZoomOut()
960
961
963 """Override the StyledTextCtrl pop up menu.
964
965 @param event: The wx event.
966 @type event: wx event
967 """
968
969
970 menu = wx.Menu()
971
972
973 menu.AppendItem(build_menu_item(menu, id=self.menu_id_find, text="&Find", icon=icon_16x16.edit_find))
974 menu.AppendSeparator()
975 menu.AppendItem(build_menu_item(menu, id=self.menu_id_copy, text="&Copy", icon=icon_16x16.edit_copy))
976 menu.AppendItem(build_menu_item(menu, id=self.menu_id_select_all, text="&Select all", icon=icon_16x16.edit_select_all))
977 menu.AppendSeparator()
978 menu.AppendItem(build_menu_item(menu, id=self.menu_id_zoom_in, text="Zoom &in", icon=icon_16x16.zoom_in))
979 menu.AppendItem(build_menu_item(menu, id=self.menu_id_zoom_out, text="Zoom &out", icon=icon_16x16.zoom_out))
980 menu.AppendItem(build_menu_item(menu, id=self.menu_id_zoom_orig, text="Original &zoom", icon=icon_16x16.zoom_original))
981 menu.AppendSeparator()
982 menu.AppendItem(build_menu_item(menu, id=self.menu_id_goto_start, text="&Go to start", icon=icon_16x16.go_top))
983 menu.AppendItem(build_menu_item(menu, id=self.menu_id_goto_end, text="&Go to end", icon=icon_16x16.go_bottom))
984
985
986 if status.show_gui:
987 self.PopupMenu(menu)
988 menu.Destroy()
989
990
992 """Write the text in the log queue to the log control."""
993
994
995 string_list, stream_list = self.get_text()
996
997
998 if len(string_list) == 1 and string_list[0] == '':
999 return
1000
1001
1002 at_end = False
1003 if self.GetScrollPos(wx.VERTICAL) == self.GetScrollRange(wx.VERTICAL) - self.LinesOnScreen():
1004 at_end = True
1005
1006
1007 self.SetReadOnly(False)
1008
1009
1010 for i in range(len(string_list)):
1011
1012 self.AppendText(string_list[i])
1013
1014
1015 if stream_list[i] != 0:
1016
1017 len_string = len(string_list[i].encode('utf8'))
1018 end = self.GetLength()
1019
1020
1021 self.StartStyling(end - len_string, 31)
1022 self.SetStyling(len_string, stream_list[i])
1023
1024
1025 if stream_list[i] in [1, 3] and status.show_gui:
1026
1027 if self.controller.IsShown():
1028 self.controller.Raise()
1029
1030
1031 else:
1032
1033 self.controller.Show()
1034 self.GotoPos(self.GetLength())
1035
1036
1037 self.limit_scrollback()
1038
1039
1040 if at_end:
1041 self.ScrollToLine(self.GetLineCount())
1042
1043
1044 self.SetReadOnly(True)
1045
1046
1047
1048 -class Redirect_text(object):
1049 """The IO redirection to text control object."""
1050
1051 - def __init__(self, control, log_queue, orig_io, stream=0):
1052 """Set up the text redirection object.
1053
1054 @param control: The text control object to redirect IO to.
1055 @type control: wx.TextCtrl instance
1056 @param log_queue: The queue of log messages.
1057 @type log_queue: Queue.Queue instance
1058 @param orig_io: The original IO stream, used for debugging and the test suite.
1059 @type orig_io: file
1060 @keyword stream: The type of steam (0 for STDOUT and 1 for STDERR).
1061 @type stream: int
1062 """
1063
1064
1065 self.control = control
1066 self.log_queue = log_queue
1067 self.orig_io = orig_io
1068 self.stream = stream
1069
1070
1072 """Simulate the file object flush method."""
1073
1074
1075 wx.CallAfter(self.control.write)
1076
1077
1079 """Answer that this is not a TTY.
1080
1081 @return: False, as this is not a TTY.
1082 @rtype: bool
1083 """
1084
1085 return False
1086
1087
1088 - def write(self, string):
1089 """Simulate the file object write method.
1090
1091 @param string: The text to write.
1092 @type string: str
1093 """
1094
1095
1096 if status.debug or status.test_mode:
1097 self.orig_io.write(string)
1098
1099
1100 self.log_queue.put([string, self.stream])
1101
1102
1103 wx.CallAfter(self.control.write)
1104