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