1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """Log window of relax GUI controlling all calculations."""
25
26
27 import sys
28 import wx
29 import wx.stc
30
31
32 from graphics import IMAGE_PATH, fetch_icon
33 from gui.components.menu import build_menu_item
34 from gui.fonts import font
35 from gui.icons import relax_icons
36 from gui.misc import add_border, bitmap_setup
37 from gui.string_conv import str_to_gui
38 from info import Info_box
39 from lib.compat import Queue
40 from lib.io import SplitIO
41 from pipe_control.pipes import cdp_name
42 from status import Status; status = Status()
43
44
45
46 MENU_ID_FIND = wx.NewId()
47 MENU_ID_COPY = wx.NewId()
48 MENU_ID_SELECT_ALL = wx.NewId()
49 MENU_ID_ZOOM_IN = wx.NewId()
50 MENU_ID_ZOOM_OUT = wx.NewId()
51 MENU_ID_ZOOM_ORIG = wx.NewId()
52 MENU_ID_GOTO_START = wx.NewId()
53 MENU_ID_GOTO_END = wx.NewId()
54
55
56
58 """The relax controller window."""
59
61 """Set up the relax controller frame.
62
63 @param gui: The GUI object.
64 @type gui: wx.Frame instance
65 """
66
67
68 self.gui = gui
69
70
71 super(Controller, self).__init__(self.gui, -1, style=wx.DEFAULT_FRAME_STYLE)
72
73
74 self.size_x = 800
75 self.size_y = 700
76 self.border = 5
77 self.spacer = 10
78
79
80 sizer = self.setup_frame()
81
82
83 self.add_relax_logo(sizer)
84
85
86 sizer.AddSpacer(20)
87
88
89 self.name = self.add_text(self.main_panel, sizer, "Current GUI analysis:")
90
91
92 self.cdp = self.add_text(self.main_panel, sizer, "Current data pipe:")
93
94
95 self.create_rx(sizer)
96
97
98 self.create_mf(sizer)
99
100
101 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.")
102
103
104 self.log_queue = Queue()
105
106
107 self.log_panel = LogCtrl(self.main_panel, self, log_queue=self.log_queue, id=-1)
108 sizer.Add(self.log_panel, 1, wx.EXPAND|wx.ALL, 0)
109
110
111 out = Redirect_text(self.log_panel, self.log_queue, orig_io=sys.stdout, stream=0)
112 if sys.stdout == sys.__stdout__ or status.relax_mode in ['test suite', 'system tests', 'unit tests', 'GUI tests']:
113 sys.stdout = out
114 else:
115 split_stdout = SplitIO()
116 split_stdout.split(sys.stdout, out)
117 sys.stdout = split_stdout
118
119
120 err = Redirect_text(self.log_panel, self.log_queue, orig_io=sys.stderr, stream=1)
121 if sys.stderr == sys.__stderr__ or status.relax_mode in ['test suite', 'system tests', 'unit tests', 'GUI tests']:
122 sys.stderr = err
123 else:
124 split_stderr = SplitIO()
125 split_stderr.split(sys.stderr, err)
126 sys.stderr = split_stderr
127
128
129 self.update_controller()
130
131
132 self.timer = wx.Timer(self)
133 self.Bind(wx.EVT_TIMER, self.handler_timer, self.timer)
134
135
136 if not status.test_mode:
137 info = Info_box()
138 print(info.intro_text())
139
140
141 self.log_panel.SetFocus()
142
143
144 status.observers.pipe_alteration.register('controller', self.update_controller, method_name='update_controller')
145 status.observers.auto_analyses.register('controller', self.update_controller, method_name='update_controller')
146 status.observers.gui_analysis.register('controller', self.update_controller, method_name='update_controller')
147 status.observers.exec_lock.register('controller', self.update_gauge, method_name='update_gauge')
148
149
150 - def add_gauge(self, parent, sizer, desc, tooltip=None):
151 """Add a gauge to the sizer and return it.
152
153 @param parent: The parent GUI element.
154 @type parent: wx object
155 @param sizer: The sizer element to pack the element into.
156 @type sizer: wx.Sizer instance
157 @param desc: The description to display.
158 @type desc: str
159 @keyword tooltip: The tooltip which appears on hovering over the text and the gauge.
160 @type tooltip: str
161 @return: The gauge element.
162 @rtype: wx.Gauge instance
163 """
164
165
166 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
167
168
169 text = wx.StaticText(parent, -1, desc, style=wx.ALIGN_LEFT)
170 text.SetFont(font.normal)
171 sub_sizer.Add(text, 1, wx.ALIGN_CENTER_VERTICAL, 0)
172
173
174 gauge = wx.Gauge(parent, id=-1, range=100, style=wx.GA_SMOOTH)
175 gauge.SetSize((-1, 20))
176 sub_sizer.Add(gauge, 3, wx.EXPAND|wx.ALL, 0)
177
178
179 sizer.Add(sub_sizer, 0, wx.ALL|wx.EXPAND, 0)
180
181
182 sizer.AddSpacer(self.spacer)
183
184
185 if tooltip:
186 text.SetToolTipString(tooltip)
187 gauge.SetToolTipString(tooltip)
188
189
190 return gauge
191
192
194 """Add the relax logo to the sizer.
195
196 @param sizer: The sizer element to pack the relax logo into.
197 @type sizer: wx.Sizer instance
198 """
199
200
201 logo = wx.StaticBitmap(self.main_panel, -1, bitmap_setup(IMAGE_PATH+'relax.gif'))
202
203
204 sizer.Add(logo, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 0)
205
206
207 sizer.AddSpacer(self.spacer)
208
209
210 - def add_text(self, parent, sizer, desc, tooltip=None):
211 """Add the current data pipe element.
212
213 @param parent: The parent GUI element.
214 @type parent: wx object
215 @param sizer: The sizer element to pack the element into.
216 @type sizer: wx.Sizer instance
217 @param desc: The description to display.
218 @type desc: str
219 @keyword tooltip: The tooltip which appears on hovering over the text and field.
220 @type tooltip: str
221 @return: The text control.
222 @rtype: wx.TextCtrl instance
223 """
224
225
226 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
227
228
229 text = wx.StaticText(parent, -1, desc, style=wx.ALIGN_LEFT)
230 text.SetFont(font.normal)
231 sub_sizer.Add(text, 1, wx.ALIGN_CENTER_VERTICAL, 0)
232
233
234 field = wx.TextCtrl(parent, -1, '', style=wx.ALIGN_LEFT)
235 field.SetEditable(False)
236 field.SetFont(font.normal)
237 colour = self.main_panel.GetBackgroundColour()
238 field.SetOwnBackgroundColour(colour)
239 sub_sizer.Add(field, 3, wx.ALIGN_CENTER_VERTICAL, 0)
240
241
242 sizer.Add(sub_sizer, 0, wx.ALL|wx.EXPAND, 0)
243
244
245 sizer.AddSpacer(self.spacer)
246
247
248 if tooltip:
249 text.SetToolTipString(tooltip)
250 field.SetToolTipString(tooltip)
251
252
253 field.Bind(wx.EVT_KEY_DOWN, self.handler_key_down)
254
255
256 return field
257
258
260 """Return the key for the current analysis' status object.
261
262 @return: The current analysis' status object key.
263 @rtype: str or None
264 """
265
266
267 data = self.gui.analysis.current_data()
268 if data == None:
269 return
270
271
272 if hasattr(data, 'pipe_bundle'):
273 return data.pipe_bundle
274
275
277 """Create the model-free specific panel.
278
279 @param sizer: The sizer element to pack the element into.
280 @type sizer: wx.Sizer instance
281 """
282
283
284 self.panel_mf = wx.Panel(self.main_panel, -1)
285 sizer.Add(self.panel_mf, 0, wx.ALL|wx.EXPAND, 0)
286
287
288 panel_sizer = wx.BoxSizer(wx.VERTICAL)
289 self.panel_mf.SetSizer(panel_sizer)
290
291
292 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'.")
293
294
295 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%")
296
297
298 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.")
299
300
302 """Create the relaxation curve-fitting specific panel.
303
304 @param sizer: The sizer element to pack the element into.
305 @type sizer: wx.Sizer instance
306 """
307
308
309 self.panel_rx = wx.Panel(self.main_panel, -1)
310 sizer.Add(self.panel_rx, 0, wx.ALL|wx.EXPAND, 0)
311
312
313 panel_sizer = wx.BoxSizer(wx.VERTICAL)
314 self.panel_rx.SetSizer(panel_sizer)
315
316
317 self.mc_gauge_rx = self.add_gauge(self.panel_rx, panel_sizer, "Monte Carlo simulations:", tooltip="The Monte Carlo simulation number.")
318
319
321 """Event handler for the close window action.
322
323 @param event: The wx event.
324 @type event: wx event
325 """
326
327
328 if self.gui.test_suite_flag:
329 return
330
331
332 self.Hide()
333
334
336 """Event handler for key strokes.
337
338 @keyword event: The wx event.
339 @type event: wx event
340 """
341
342
343 if event.GetKeyCode() == wx.WXK_ESCAPE:
344 self.handler_close(event)
345
346
348 """Event handler for the timer.
349
350 @param event: The wx event.
351 @type event: wx event
352 """
353
354
355 wx.CallAfter(self.main_gauge.Pulse)
356
357
358 if not status.exec_lock.locked() and self.timer.IsRunning():
359 self.timer.Stop()
360 self.update_gauge()
361
362
364 """Reset the relax controller to its initial state."""
365
366
367 if self.timer.IsRunning():
368 self.timer.Stop()
369
370
371 if hasattr(self, 'mc_gauge_rx'):
372 wx.CallAfter(self.mc_gauge_rx.SetValue, 0)
373
374
375 if hasattr(self, 'mc_gauge_mf'):
376 wx.CallAfter(self.mc_gauge_mf.SetValue, 0)
377 if hasattr(self, 'progress_gauge_mf'):
378 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
379
380
381 wx.CallAfter(self.main_gauge.SetValue, 0)
382
383
385 """Set up the relax controller frame.
386 @return: The sizer object.
387 @rtype: wx.Sizer instance
388 """
389
390
391 self.SetTitle("The relax controller")
392
393
394 self.SetIcons(relax_icons)
395
396
397 self.main_panel = wx.Panel(self, -1)
398
399
400 main_sizer = wx.BoxSizer(wx.VERTICAL)
401 self.main_panel.SetSizer(main_sizer)
402
403
404 sizer = add_border(main_sizer, border=self.border, packing=wx.VERTICAL)
405
406
407 self.Bind(wx.EVT_CLOSE, self.handler_close)
408
409
410 self.SetSize((self.size_x, self.size_y))
411
412
413 self.Centre()
414
415
416 return sizer
417
418
460
461
463 """Update the main execution gauge."""
464
465
466 if status.exec_lock.locked():
467
468 if not self.timer.IsRunning():
469 wx.CallAfter(self.timer.Start, 100)
470
471
472 return
473
474
475 key = self.analysis_key()
476 if key and key in status.auto_analysis and status.auto_analysis[key].fin:
477
478 if self.timer.IsRunning():
479 self.timer.Stop()
480
481
482 if hasattr(self, 'mc_gauge_rx'):
483 wx.CallAfter(self.mc_gauge_rx.SetValue, 100)
484
485
486 if hasattr(self, 'mc_gauge_mf'):
487 wx.CallAfter(self.mc_gauge_mf.SetValue, 100)
488 if hasattr(self, 'progress_gauge_mf'):
489 wx.CallAfter(self.progress_gauge_mf.SetValue, 100)
490
491
492 wx.CallAfter(self.main_gauge.SetValue, 100)
493
494
495 if not self.main_gauge.GetValue():
496 return
497
498
499 if not key or not key in status.auto_analysis:
500 wx.CallAfter(self.main_gauge.SetValue, 0)
501
502
503 if key and key in status.auto_analysis and not status.auto_analysis[key].fin:
504
505 if hasattr(self, 'mc_gauge_rx'):
506 wx.CallAfter(self.mc_gauge_rx.SetValue, 0)
507
508
509 if hasattr(self, 'mc_gauge_mf'):
510 wx.CallAfter(self.mc_gauge_mf.SetValue, 0)
511 if hasattr(self, 'progress_gauge_mf'):
512 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
513
514
515 wx.CallAfter(self.main_gauge.SetValue, 0)
516
517
519 """Update the model-free specific elements."""
520
521
522 key = self.analysis_key()
523 if not key:
524 return
525
526
527 elif not key in status.auto_analysis and cdp_name() == 'final':
528 wx.CallAfter(self.mc_gauge_mf.SetValue, 100)
529 wx.CallAfter(self.progress_gauge_mf.SetValue, 100)
530 wx.CallAfter(self.main_gauge.SetValue, 100)
531 return
532
533
534 if not key in status.auto_analysis:
535 wx.CallAfter(self.mc_gauge_mf.SetValue, 0)
536 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
537 wx.CallAfter(self.main_gauge.SetValue, 0)
538 return
539
540
541 wx.CallAfter(self.global_model_mf.SetValue, str_to_gui(status.auto_analysis[key].diff_model))
542
543
544 if status.auto_analysis[key].diff_model == 'local_tm':
545 if status.auto_analysis[key].current_model:
546
547 no = int(status.auto_analysis[key].current_model[2:])
548
549
550 total_models = len(status.auto_analysis[key].local_tm_models)
551
552
553 percent = int(100 * no / float(total_models))
554 wx.CallAfter(self.progress_gauge_mf.SetValue, percent)
555
556
557 elif status.auto_analysis[key].diff_model in ['sphere', 'prolate', 'oblate', 'ellipsoid']:
558
559 if status.auto_analysis[key].round == None:
560 wx.CallAfter(self.progress_gauge_mf.SetValue, 0)
561 else:
562
563 percent = int(100 * (status.auto_analysis[key].round + 1) / (status.auto_analysis[key].max_iter + 1))
564
565
566 wx.CallAfter(self.progress_gauge_mf.SetValue, percent)
567
568
569 if status.auto_analysis[key].mc_number:
570
571 percent = int(100 * (status.auto_analysis[key].mc_number + 1) / cdp.sim_number)
572
573
574 wx.CallAfter(self.mc_gauge_mf.SetValue, percent)
575
576
578 """Update the Rx specific elements."""
579
580
581 key = self.analysis_key()
582 if not key:
583 return
584
585
586 if not key in status.auto_analysis:
587 wx.CallAfter(self.mc_gauge_rx.SetValue, 0)
588 wx.CallAfter(self.main_gauge.SetValue, 0)
589 return
590
591
592 if status.auto_analysis[key].mc_number:
593
594 percent = int(100 * (status.auto_analysis[key].mc_number + 1) / cdp.sim_number)
595
596
597 wx.CallAfter(self.mc_gauge_rx.SetValue, percent)
598
599
600
601 -class LogCtrl(wx.stc.StyledTextCtrl):
602 """A special control designed to display relax output messages."""
603
604 - 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):
605 """Set up the log control.
606
607 @param parent: The parent wx window object.
608 @type parent: Window
609 @param controller: The controller window.
610 @type controller: wx.Frame instance
611 @keyword log_queue: The queue of log messages.
612 @type log_queue: Queue.Queue instance
613 @keyword id: The wx ID.
614 @type id: int
615 @keyword pos: The window position.
616 @type pos: Point
617 @keyword size: The window size.
618 @type size: Size
619 @keyword style: The StyledTextCtrl to apply.
620 @type style: long
621 @keyword name: The window name.
622 @type name: str
623 """
624
625
626 self.controller = controller
627 self.log_queue = log_queue
628
629
630 super(LogCtrl, self).__init__(parent, id=id, pos=pos, size=size, style=style, name=name)
631
632
633 self.at_end = True
634
635
636 self.SetWrapMode(wx.stc.STC_WRAP_WORD)
637
638
639 self.StyleSetFont(0, font.modern_small)
640
641
642 self.StyleSetForeground(1, wx.NamedColour('red'))
643 self.StyleSetFont(1, font.modern_small)
644
645
646 self.StyleSetForeground(2, wx.NamedColour('blue'))
647 self.StyleSetFont(2, font.modern_small_bold)
648
649
650 self.StyleSetForeground(3, wx.NamedColour('orange red'))
651 self.StyleSetFont(3, font.modern_small)
652
653
654 self.StyleSetForeground(4, wx.NamedColour('dark green'))
655 self.StyleSetFont(4, font.modern_small)
656
657
658 self.find_dlg = None
659
660
661 self.find_data = wx.FindReplaceData()
662 self.find_data.SetFlags(wx.FR_DOWN)
663
664
665 self.UsePopUp(0)
666
667
668 self.SetReadOnly(True)
669
670
671 self.orig_zoom = self.GetZoom()
672
673
674 self.Bind(wx.EVT_KEY_DOWN, self.capture_keys)
675 self.Bind(wx.EVT_MOUSE_EVENTS, self.capture_mouse)
676 self.Bind(wx.EVT_MOUSEWHEEL, self.capture_mouse_wheel)
677 self.Bind(wx.EVT_RIGHT_DOWN, self.pop_up_menu)
678 self.Bind(wx.EVT_SCROLLWIN_THUMBTRACK, self.capture_scroll)
679 self.Bind(wx.EVT_MENU, self.find_open, id=MENU_ID_FIND)
680 self.Bind(wx.EVT_MENU, self.on_copy, id=MENU_ID_COPY)
681 self.Bind(wx.EVT_MENU, self.on_select_all, id=MENU_ID_SELECT_ALL)
682 self.Bind(wx.EVT_MENU, self.on_zoom_in, id=MENU_ID_ZOOM_IN)
683 self.Bind(wx.EVT_MENU, self.on_zoom_out, id=MENU_ID_ZOOM_OUT)
684 self.Bind(wx.EVT_MENU, self.on_zoom_orig, id=MENU_ID_ZOOM_ORIG)
685 self.Bind(wx.EVT_MENU, self.on_goto_start, id=MENU_ID_GOTO_START)
686 self.Bind(wx.EVT_MENU, self.on_goto_end, id=MENU_ID_GOTO_END)
687
688
690 """Control which key events are active, preventing text insertion and deletion.
691
692 @param event: The wx event.
693 @type event: wx event
694 """
695
696
697 if event.ControlDown() and event.GetKeyCode() == 67:
698 event.Skip()
699
700
701 if event.ControlDown() and event.GetKeyCode() == 70:
702 self.find_open(event)
703
704
705 if event.ControlDown() and event.GetKeyCode() == 65:
706 self.on_select_all(event)
707
708
709 if 'darwin' in sys.platform and event.ControlDown() and event.GetKeyCode() == 71:
710 self.find_next(event)
711 elif 'darwin' not in sys.platform and event.GetKeyCode() == wx.WXK_F3:
712 self.find_next(event)
713
714
715 if event.GetKeyCode() in [wx.WXK_END, wx.WXK_HOME, wx.WXK_LEFT, wx.WXK_UP, wx.WXK_RIGHT, wx.WXK_DOWN]:
716 self.at_end = False
717 event.Skip()
718
719
720 if event.GetKeyCode() in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]:
721 self.at_end = False
722 event.Skip()
723
724
725 if event.ControlDown() and event.GetKeyCode() == 48:
726 self.on_zoom_orig(event)
727 if event.ControlDown() and event.GetKeyCode() == 45:
728 self.on_zoom_out(event)
729 if event.ControlDown() and event.GetKeyCode() == 61:
730 self.on_zoom_in(event)
731
732
733 if event.ControlDown() and event.GetKeyCode() == wx.WXK_HOME:
734 self.on_goto_start(event)
735 elif event.ControlDown() and event.GetKeyCode() == wx.WXK_END:
736 self.on_goto_end(event)
737
738
739 if event.GetKeyCode() == wx.WXK_ESCAPE:
740 self.controller.handler_close(event)
741
742
744 """Control the mouse events.
745
746 @param event: The wx event.
747 @type event: wx event
748 """
749
750
751 if event.ButtonDown():
752 self.at_end = False
753
754
755 event.Skip()
756
757
759 """Control the mouse wheel events.
760
761 @param event: The wx event.
762 @type event: wx event
763 """
764
765
766 self.at_end = False
767
768
769 scroll = event.GetLinesPerAction()
770 if event.GetWheelRotation() > 0.0:
771 scroll *= -1
772 self.GotoLine(self.GetCurrentLine() + scroll)
773
774
775 event.Skip()
776
777
793
794
796 """Remove all text from the log."""
797
798
799 self.SetReadOnly(False)
800
801
802 self.ClearAll()
803
804
805 self.SetReadOnly(True)
806
807
808 - def find(self, event):
809 """Find the text in the log control.
810
811 @param event: The wx event.
812 @type event: wx event
813 """
814
815
816 sel = self.find_data.GetFindString()
817
818
819 flags = self.find_data.GetFlags()
820
821
822 pos = self.GetCurrentPos()
823 if pos != self.GetLength():
824 self.SetCurrentPos(pos+1)
825 self.SearchAnchor()
826
827
828 forwards = wx.FR_DOWN & flags
829
830
831 if forwards:
832 pos = self.SearchNext(flags, sel)
833
834
835 else:
836 pos = self.SearchPrev(flags, sel)
837
838
839 if pos == -1:
840
841 if forwards:
842 self.GotoPos(self.GetLength())
843 else:
844 self.GotoPos(pos)
845
846
847 text = "The string '%s' could not be found." % sel
848 nothing = wx.MessageDialog(self, text, caption="Not found", style=wx.ICON_INFORMATION|wx.OK)
849 nothing.SetSize((300, 200))
850 if status.show_gui:
851 nothing.ShowModal()
852 nothing.Destroy()
853
854
855 else:
856
857 self.EnsureCaretVisible()
858
859
860 self.at_end = False
861
862
864 """Close the find dialog.
865
866 @param event: The wx event.
867 @type event: wx event
868 """
869
870
871 self.find_dlg.Destroy()
872
873
874 self.find_dlg = None
875
876
878 """Display the text finding dialog.
879
880 @param event: The wx event.
881 @type event: wx event
882 """
883
884
885 self.at_end = False
886
887
888 if self.find_dlg == None:
889
890 self.find_dlg = wx.FindReplaceDialog(self, self.find_data, "Find")
891
892
893 self.find_dlg.Bind(wx.EVT_FIND, self.find)
894 self.find_dlg.Bind(wx.EVT_FIND_NEXT, self.find)
895 self.find_dlg.Bind(wx.EVT_FIND_CLOSE, self.find_close)
896
897
898 if status.show_gui:
899 self.find_dlg.Show(True)
900
901
902 else:
903 self.find_dlg.Show()
904
905
907 """Find the next instance of the text.
908
909 @param event: The wx event.
910 @type event: wx event
911 """
912
913
914 self.at_end = False
915
916
917 if self.find_data.GetFindString():
918 self.find(event)
919
920
921 else:
922 self.find_open(event)
923
924
925 - def get_text(self):
926 """Concatenate all of the text from the log queue and return it as a string.
927
928 @return: A list of the text from the log queue and a list of the streams these correspond to.
929 @rtype: list of str, list of int
930 """
931
932
933 string_list = ['']
934 stream_list = [0]
935
936
937 while True:
938
939 if self.log_queue.empty():
940 break
941
942
943 msg, stream = self.log_queue.get()
944
945
946 if msg[1:7] == 'relax>':
947
948 string_list[-1] += '\n'
949
950
951 string_list.append('relax>')
952 stream_list.append(2)
953
954
955 msg = msg[7:]
956
957
958 string_list.append('')
959 stream_list.append(stream)
960
961
962 elif msg[0:13] == 'RelaxWarning:':
963
964 string_list.append(msg)
965 stream_list.append(3)
966 continue
967
968
969 elif msg[0:6] == 'debug>':
970
971 string_list.append(msg)
972 stream_list.append(4)
973 continue
974
975
976 if stream_list[-1] != stream:
977 string_list.append('')
978 stream_list.append(stream)
979
980
981 string_list[-1] = string_list[-1] + msg
982
983
984 return string_list, stream_list
985
986
1017
1018
1020 """Copy the selected text.
1021
1022 @param event: The wx event.
1023 @type event: wx event
1024 """
1025
1026
1027 self.Copy()
1028
1029
1031 """Move to the end of the text.
1032
1033 @param event: The wx event.
1034 @type event: wx event
1035 """
1036
1037
1038 self.at_end = True
1039
1040
1041 self.GotoPos(self.GetLength())
1042
1043
1045 """Move to the start of the text.
1046
1047 @param event: The wx event.
1048 @type event: wx event
1049 """
1050
1051
1052 self.at_end = False
1053
1054
1055 self.GotoPos(-1)
1056
1057
1059 """Select all text in the control.
1060
1061 @param event: The wx event.
1062 @type event: wx event
1063 """
1064
1065
1066 self.at_end = False
1067
1068
1069 self.GotoPos(1)
1070
1071
1072 self.SelectAll()
1073
1074
1076 """Zoom in by increase the font by 1 point size.
1077
1078 @param event: The wx event.
1079 @type event: wx event
1080 """
1081
1082
1083 self.ZoomIn()
1084
1085
1087 """Zoom to the original zoom level.
1088
1089 @param event: The wx event.
1090 @type event: wx event
1091 """
1092
1093
1094 self.SetZoom(self.orig_zoom)
1095
1096
1098 """Zoom out by decreasing the font by 1 point size.
1099
1100 @param event: The wx event.
1101 @type event: wx event
1102 """
1103
1104
1105 self.ZoomOut()
1106
1107
1109 """Override the StyledTextCtrl pop up menu.
1110
1111 @param event: The wx event.
1112 @type event: wx event
1113 """
1114
1115
1116 menu = wx.Menu()
1117
1118
1119 menu.AppendItem(build_menu_item(menu, id=MENU_ID_FIND, text="&Find", icon=fetch_icon('oxygen.actions.edit-find', "16x16")))
1120 menu.AppendSeparator()
1121 menu.AppendItem(build_menu_item(menu, id=MENU_ID_COPY, text="&Copy", icon=fetch_icon('oxygen.actions.edit-copy', "16x16")))
1122 menu.AppendItem(build_menu_item(menu, id=MENU_ID_SELECT_ALL, text="&Select all", icon=fetch_icon('oxygen.actions.edit-select-all', "16x16")))
1123 menu.AppendSeparator()
1124 menu.AppendItem(build_menu_item(menu, id=MENU_ID_ZOOM_IN, text="Zoom &in", icon=fetch_icon('oxygen.actions.zoom-in', "16x16")))
1125 menu.AppendItem(build_menu_item(menu, id=MENU_ID_ZOOM_OUT, text="Zoom &out", icon=fetch_icon('oxygen.actions.zoom-out', "16x16")))
1126 menu.AppendItem(build_menu_item(menu, id=MENU_ID_ZOOM_ORIG, text="Original &zoom", icon=fetch_icon('oxygen.actions.zoom-original', "16x16")))
1127 menu.AppendSeparator()
1128 menu.AppendItem(build_menu_item(menu, id=MENU_ID_GOTO_START, text="&Go to start", icon=fetch_icon('oxygen.actions.go-top', "16x16")))
1129 menu.AppendItem(build_menu_item(menu, id=MENU_ID_GOTO_END, text="&Go to end", icon=fetch_icon('oxygen.actions.go-bottom', "16x16")))
1130
1131
1132 if status.show_gui:
1133 self.PopupMenu(menu)
1134
1135
1136 menu.Destroy()
1137
1138
1140 """Write the text in the log queue to the log control."""
1141
1142
1143 if self.GetScrollRange(wx.VERTICAL) - self.GetCurrentLine() <= 1:
1144 self.at_end = True
1145
1146
1147 string_list, stream_list = self.get_text()
1148
1149
1150 if len(string_list) == 1 and string_list[0] == '':
1151 return
1152
1153
1154 self.SetReadOnly(False)
1155
1156
1157 for i in range(len(string_list)):
1158
1159 self.AppendText(string_list[i])
1160
1161
1162 if stream_list[i] != 0:
1163
1164 len_string = len(string_list[i].encode('utf8'))
1165 end = self.GetLength()
1166
1167
1168 self.StartStyling(end - len_string, 31)
1169 self.SetStyling(len_string, stream_list[i])
1170
1171
1172 if stream_list[i] in [1, 3] and status.show_gui:
1173
1174 if self.controller.IsShown():
1175 self.controller.Raise()
1176
1177
1178 else:
1179
1180 self.controller.Show()
1181 self.GotoPos(self.GetLength())
1182
1183
1184 self.limit_scrollback()
1185
1186
1187 if self.at_end:
1188 self.DocumentEnd()
1189
1190
1191 self.SetReadOnly(True)
1192
1193
1194
1195 -class Redirect_text(object):
1196 """The IO redirection to text control object."""
1197
1198 - def __init__(self, control, log_queue, orig_io, stream=0):
1199 """Set up the text redirection object.
1200
1201 @param control: The text control object to redirect IO to.
1202 @type control: wx.TextCtrl instance
1203 @param log_queue: The queue of log messages.
1204 @type log_queue: Queue.Queue instance
1205 @param orig_io: The original IO stream, used for debugging and the test suite.
1206 @type orig_io: file
1207 @keyword stream: The type of steam (0 for STDOUT and 1 for STDERR).
1208 @type stream: int
1209 """
1210
1211
1212 self.control = control
1213 self.log_queue = log_queue
1214 self.orig_io = orig_io
1215 self.stream = stream
1216
1217
1219 """Simulate the file object flush method."""
1220
1221
1222 wx.CallAfter(self.control.write)
1223
1224
1226 """Answer that this is not a TTY.
1227
1228 @return: False, as this is not a TTY.
1229 @rtype: bool
1230 """
1231
1232 return False
1233
1234
1235 - def write(self, string):
1236 """Simulate the file object write method.
1237
1238 @param string: The text to write.
1239 @type string: str
1240 """
1241
1242
1243 if status.debug or status.test_mode:
1244 self.orig_io.write(string)
1245
1246
1247 self.log_queue.put([string, self.stream])
1248
1249
1250 wx.CallAfter(self.control.write)
1251