1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """Base class module for the wizard GUI elements."""
24
25
26 import wx
27 from wx.lib import buttons, scrolledpanel
28
29
30 from data_store import Relax_data_store; ds = Relax_data_store()
31 from graphics import IMAGE_PATH, fetch_icon
32 from gui.fonts import font
33 from gui.icons import Relax_icons
34 from gui.interpreter import Interpreter; interpreter = Interpreter()
35 from gui.misc import add_border, bitmap_setup
36 from gui.string_conv import float_to_gui, str_to_gui
37 from lib.check_types import is_float
38 from lib.errors import RelaxImplementError
39 from status import Status; status = Status()
40
41
42
43 ESC_ID = wx.NewId()
44
45
46
47 -class Wiz_page(wx.Panel):
48 """The wizard pages to be placed inside the wizard.
49
50 To inherit from this class, you must minimally supply the add_contents() method. This method should build the specific GUI elements. The following methods are also designed to be overwritten:
51
52 - add_artwork(), this builds the left hand artwork section of the page.
53 - add_contents(), this builds the right hand section of the page.
54 - on_display(), this is executed when the page is displayed.
55 - on_display_post(), this is executed when the page is displayed, directly after the on_display method.
56 - on_execute(), this is executed when the wizard is terminated or the apply button is hit.
57 - on_next(), this is executed when moving to the next wizard page.
58
59 The following methods can be used by add_contents() to create standard GUI elements:
60
61 - chooser()
62 - combo_box()
63 - file_selection()
64 - input_field()
65 - text()
66
67 These are described in full detail in their docstrings.
68 """
69
70
71 art_spacing = 20
72 divider = None
73 height_element = 27
74 image_path = IMAGE_PATH + "relax.gif"
75 main_text = ''
76 setup_fail = False
77 size_button = (100, 33)
78 size_square_button = (33, 33)
79 title = ''
80
81 - def __init__(self, parent, height_desc=220):
82 """Set up the window.
83
84 @param parent: The parent GUI element.
85 @type parent: wx.object instance
86 @keyword height_desc: The height in pixels of the description part of the wizard.
87 @type height_desc: int or None
88 """
89
90
91 self.parent = parent
92 self.height_desc = height_desc
93
94
95 wx.Panel.__init__(self, parent, id=-1)
96
97
98 self.exec_status = False
99
100
101 box_main = wx.BoxSizer(wx.HORIZONTAL)
102 self.SetSizer(box_main)
103
104
105 self.add_artwork(box_main)
106
107
108 image_x, image_y = self.image.GetSize()
109
110
111 self._main_size = parent._size_x - image_x - self.art_spacing - 2*parent._border
112 if self.divider:
113 self._div_left = self.divider
114 self._div_right = self._main_size - self.divider
115 else:
116 self._div_left = self._div_right = self._main_size / 2
117
118
119 main_sizer = self._build_main_section(box_main)
120
121
122 self._add_title(main_sizer)
123
124
125 self.add_desc(main_sizer, max_y=self.height_desc)
126
127
128 main_sizer.AddStretchSpacer()
129 self.add_contents(main_sizer)
130
131
132 - def _add_title(self, sizer):
133 """Add the title to the dialog.
134
135 @param sizer: A sizer object.
136 @type sizer: wx.Sizer instance
137 """
138
139
140 sizer.AddSpacer(10)
141
142
143 dc = wx.ScreenDC()
144 dc.SetFont(font.title)
145 size = dc.GetTextExtent(self.title)
146 title = wx.StaticText(self, -1, self.title, size=size)
147 title.SetFont(font.title)
148
149
150 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 0)
151
152
153 sizer.AddSpacer(10)
154
155
156 - def _apply(self, event=None):
157 """Apply the operation.
158
159 @keyword event: The wx event.
160 @type event: wx event
161 """
162
163
164 wx.BeginBusyCursor()
165
166
167 self.exec_status = self.on_execute()
168
169
170 if not self.exec_status:
171 if wx.IsBusy():
172 wx.EndBusyCursor()
173 return
174
175
176 self.on_completion()
177
178
179 self.on_apply()
180
181
182 if wx.IsBusy():
183 wx.EndBusyCursor()
184
185
186 - def _build_main_section(self, sizer):
187 """Add the main part of the dialog.
188
189 @param sizer: A sizer object.
190 @type sizer: wx.Sizer instance
191 @return: The sizer object for the main part of the dialog.
192 @rtype: wx.Sizer instance
193 """
194
195
196 main_sizer = wx.BoxSizer(wx.VERTICAL)
197
198
199 sizer.Add(main_sizer, 1, wx.EXPAND|wx.ALL, 0)
200
201
202 return main_sizer
203
204
205 - def add_artwork(self, sizer):
206 """Add the artwork to the dialog.
207
208 @param sizer: A sizer object.
209 @type sizer: wx.Sizer instance
210 """
211
212
213 if self.image_path:
214 self.image = wx.StaticBitmap(self, -1, bitmap_setup(self.image_path))
215 sizer.Add(self.image, 0, wx.TOP, 0)
216
217
218 sizer.AddSpacer(self.art_spacing)
219
220
221 - def add_contents(self, sizer):
222 """Add the specific GUI elements (dummy method).
223
224 @param sizer: A sizer object.
225 @type sizer: wx.Sizer instance
226 """
227
228
229 raise RelaxImplementError
230
231
232 - def add_desc(self, sizer, max_y=220):
233 """Add the description to the dialog.
234
235 @param sizer: A sizer object.
236 @type sizer: wx.Sizer instance
237 @keyword max_y: The maximum height, in number of pixels, for the description.
238 @type max_y: int
239 """
240
241
242 sizer.AddSpacer(5)
243 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
244 sizer.AddSpacer(5)
245
246
247 panel = scrolledpanel.ScrolledPanel(self, -1, name="desc")
248
249
250 panel_sizer = wx.BoxSizer(wx.VERTICAL)
251
252
253 text = wx.StaticText(panel, -1, self.main_text, style=wx.TE_MULTILINE)
254 text.SetFont(font.normal)
255
256
257 text.Wrap(self._main_size - 20)
258
259
260 x, y = text.GetSize()
261
262
263 if y > max_y-10:
264
265 panel.SetInitialSize((self._main_size, max_y))
266
267
268 else:
269
270 text.Wrap(self._main_size)
271
272
273 panel.SetInitialSize(text.GetSize())
274
275
276 panel_sizer.Add(text, 1, wx.EXPAND, 0)
277
278
279 panel.SetSizer(panel_sizer)
280 panel.SetAutoLayout(1)
281 panel.SetupScrolling(scroll_x=False, scroll_y=True, scrollToTop=True)
282 sizer.Add(panel, 0, wx.ALL|wx.EXPAND)
283
284
285 sizer.AddSpacer(5)
286 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
287 sizer.AddSpacer(5)
288
289
290 - def on_apply(self):
291 """To be over-ridden if an action is to be performed on hitting the apply button.
292
293 This method will be called when clicking on the apply button.
294 """
295
296
298 """To be over-ridden if an action is to be performed just before moving back to the previous page.
299
300 This method is called when moving back to the previous page of the wizard.
301 """
302
303
304 - def on_completion(self):
305 """To be over-ridden if an action is to be performed just after executing self.on_execute().
306
307 This method is called just after self.on_execute() has been called
308 """
309
310
311 - def on_display(self):
312 """To be over-ridden if an action is to be performed prior to displaying the page.
313
314 This method will be called by the wizard class method _display_page() just after hiding all other pages but prior to displaying this page.
315 """
316
317
318 - def on_display_post(self):
319 """To be over-ridden if an action is to be performed after the execution of the on_display() method.
320
321 This method will be called by the wizard class method _display_page() just after hiding all other pages but prior to displaying this page.
322 """
323
324
325 - def on_execute(self):
326 """To be over-ridden if an action is to be performed just before exiting the page.
327
328 This method is called when terminating the wizard or hitting the apply button.
329 """
330
331 return True
332
333
335 """To be over-ridden if an action is to be performed when a page is newly displayed.
336
337 This method will be called by the wizard class method _display_page() at the very end.
338 """
339
340
342 """To be over-ridden if an action is to be performed just before moving to the next page.
343
344 This method is called when moving to the next page of the wizard.
345 """
346
347
348
350 """The wizard."""
351
352
353 _size_button = (100, 33)
354 ICON_APPLY = fetch_icon('oxygen.actions.dialog-ok-apply', "22x22")
355 ICON_BACK = fetch_icon('oxygen.actions.go-previous-view', "22x22")
356 ICON_CANCEL = fetch_icon('oxygen.actions.dialog-cancel', "22x22")
357 ICON_FINISH = fetch_icon('oxygen.actions.dialog-ok', "22x22")
358 ICON_NEXT = fetch_icon('oxygen.actions.go-next-view', "22x22")
359 ICON_OK = fetch_icon('oxygen.actions.dialog-ok', "22x22")
360 ICON_SKIP = fetch_icon('oxygen.actions.arrow-right-double-relax-blue', "22x22")
361 TEXT_APPLY = " Apply"
362 TEXT_BACK = " Back"
363 TEXT_CANCEL = " Cancel"
364 TEXT_FINISH = " Finish"
365 TEXT_NEXT = " Next"
366 TEXT_OK = " OK"
367 TEXT_SKIP = " Skip"
368
369
370 - def __init__(self, parent=None, size_x=400, size_y=400, title='', border=10, style=wx.DEFAULT_DIALOG_STYLE):
371 """Set up the window.
372
373 @keyword parent: The parent window.
374 @type parent: wx.Window instance
375 @keyword size_x: The width of the wizard.
376 @type size_x: int
377 @keyword size_y: The height of the wizard.
378 @type size_y: int
379 @keyword title: The title of the wizard dialog.
380 @type title: str
381 @keyword border: The size of the border inside the wizard.
382 @type border: int
383 @keyword style: The dialog style.
384 @type style: wx style
385 """
386
387
388 self._size_x = size_x
389 self._size_y = size_y
390 self._border = border
391 self.title = title
392
393
394 wx.Dialog.__init__(self, parent, id=-1, title=title, style=style)
395
396
397 self.SetIcons(Relax_icons())
398
399
400 sizer = wx.BoxSizer(wx.VERTICAL)
401 self.SetSizer(sizer)
402
403
404 self._main_sizer = add_border(sizer, border=border, packing=wx.VERTICAL)
405
406
407 self.SetSize((size_x, size_y))
408
409
410 self.Centre()
411
412
413 self._current_page = 0
414 self._num_pages = 0
415 self._pages = []
416 self._page_sizers = []
417 self._button_sizers = []
418 self._top_sizers = []
419 self._button_apply_flag = []
420 self._button_skip_flag = []
421 self._buttons = []
422 self._button_ids = []
423 self._exec_on_next = []
424 self._exec_count = []
425 self._proceed_on_error = []
426 self._uf_flush = []
427 self._seq_fn_list = []
428 self._seq_next = []
429 self._seq_prev = []
430 self._skip_flag = []
431
432
433 self._buttons_built = False
434
435
436 self.Bind(wx.EVT_CLOSE, self._handler_close)
437
438
439 self.acc_list = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, ESC_ID)]
440 self.acc_table = wx.AcceleratorTable(self.acc_list)
441 self.SetAcceleratorTable(self.acc_table)
442 self.Bind(wx.EVT_MENU, self._handler_escape, id=ESC_ID)
443
444
445 - def _apply(self, event=None):
446 """Execute the current page's 'Apply' method.
447
448 @keyword event: The wx event.
449 @type event: wx event
450 """
451
452
453 self._pages[self._current_page]._apply()
454
455
566
567
569 """Cancel the operation.
570
571 @keyword event: The wx event.
572 @type event: wx event
573 """
574
575
576 self._pages[self._current_page].on_next()
577
578
579 self.Close()
580
581
582 - def _display_page(self, i):
583 """Display the given page.
584
585 @param i: The index of the page to display.
586 @type i: int
587 """
588
589
590 for j in range(self._num_pages):
591 if self._main_sizer.IsShown(self._page_sizers[j]):
592 self._main_sizer.Hide(self._page_sizers[j])
593
594
595 if status.show_gui:
596 self._main_sizer.Show(self._page_sizers[i])
597
598
599 self._pages[i].on_display()
600 self._pages[i].on_display_post()
601
602
603 self.Layout()
604 self.Refresh()
605
606
607 self._pages[i].on_init()
608
609
610 self._pages[i].SetFocus()
611
612
614 """Return to the previous page.
615
616 @keyword event: The wx event.
617 @type event: wx event
618 """
619
620
621 self._pages[self._current_page].on_back()
622
623
624 self._current_page = self._seq_prev[self._current_page]
625
626
627 self._display_page(self._current_page)
628
629
631 """Move to the next page.
632
633 @keyword event: The wx event.
634 @type event: wx event
635 """
636
637
638 self._pages[self._current_page].on_next()
639
640
641 if not self._skip_flag[self._current_page]:
642
643 if self._exec_on_next[self._current_page]:
644 self._pages[self._current_page]._apply(event)
645
646
647 if self._uf_flush[self._current_page]:
648 interpreter.flush()
649
650
651 if not self._pages[self._current_page].exec_status:
652
653 if not self._proceed_on_error[self._current_page]:
654 return
655
656
657 self._exec_count[self._current_page] += 1
658
659
660 next_page = self._seq_fn_list[self._current_page]()
661
662
663 if next_page >= len(self._pages):
664 self._ok(None)
665 return
666
667
668 self._seq_next[self._current_page] = next_page
669 self._seq_prev[next_page] = self._current_page
670
671
672 self._current_page = next_page
673
674
675 self._display_page(self._current_page)
676
677
679 """Event handler for the close window action.
680
681 @keyword event: The wx event.
682 @type event: wx event
683 """
684
685
686 self._pages[self._current_page].on_next()
687
688
689 event.Skip()
690
691
693 """Event handler for key strokes.
694
695 @keyword event: The wx event.
696 @type event: wx event
697 """
698
699
700 self.Close()
701
702
704 """Standard function for setting the next page to the one directly next in the sequence.
705
706 @return: The index of the next page, which is the current page index plus one.
707 @rtype: int
708 """
709
710
711 return self._current_page + 1
712
713
714 - def _ok(self, event=None):
715 """Accept the operation.
716
717 @keyword event: The wx event.
718 @type event: wx event
719 """
720
721
722 for i in self._seq_loop():
723 if not self._exec_count[i] and not self._skip_flag[i]:
724
725 self._pages[i]._apply(event)
726
727
728 if self._uf_flush[i]:
729 interpreter.flush()
730
731
732 if not self._pages[self._current_page].exec_status:
733
734 if not self._proceed_on_error[self._current_page]:
735 return
736
737
738 self._exec_count[i] += 1
739
740
741 self._pages[self._current_page].on_next()
742
743
744 if self.IsModal():
745 self.EndModal(wx.ID_OK)
746 else:
747 self.Close()
748
749
751 """Loop over the sequence in the forwards direction."""
752
753
754 current = 0
755
756
757 yield current
758
759
760 while True:
761
762 next = self._seq_next[current]
763 current = next
764
765
766 if next == None:
767 break
768
769
770 yield next
771
772
773 - def _skip(self, event=None):
774 """Skip the page.
775
776 @keyword event: The wx event.
777 @type event: wx event
778 """
779
780
781 self._skip_flag[self._current_page] = True
782
783
784 self._go_next(None)
785
786
788 """Override the default wx.Dialog.Destroy() method."""
789
790
791 self.Close()
792
793
794 for i in range(len(self._buttons)):
795
796 for name in self._buttons[i]:
797 if hasattr(self._buttons[i][name], 'Destroy'):
798 self._buttons[i][name].Destroy()
799 self._buttons[i][name] = None
800
801
802 if hasattr(self._pages[i], 'Destroy'):
803 self._pages[i].Destroy()
804 self._pages[i] = None
805
806
807 super(Wiz_window, self).DestroyChildren()
808 super(Wiz_window, self).Destroy()
809
810
811 - def add_page(self, panel, apply_button=True, skip_button=False, exec_on_next=True, proceed_on_error=True, uf_flush=False):
812 """Add a new page to the wizard.
813
814 @param panel: The page to add to the wizard.
815 @type panel: wx.Panel instance
816 @keyword apply_button: A flag which if true will show the apply button for that page.
817 @type apply_button: bool
818 @keyword skip_button: A flag which if true will show the skip button for that page.
819 @type skip_button: bool
820 @keyword exec_on_next: A flag which if true will run the on_execute() method when clicking on the next button.
821 @type exec_on_next: bool
822 @keyword proceed_on_error: A flag which if True will proceed to the next page (or quit if there are no more pages) despite the occurrence of an error in execution. If False, the page will remain open (the GUI interpreter thread will be flushed first to synchronise).
823 @type proceed_on_error: bool
824 @keyword uf_flush: A flag which if True will cause the GUI interpreter thread to be flushed to clear out all user function call prior to proceeding.
825 @type uf_flush: bool
826 @return: The index of the page in the wizard.
827 @rtype: int
828 """
829
830
831 index = self._num_pages
832 self._num_pages += 1
833 self._pages.append(panel)
834
835
836 self._page_sizers.append(wx.BoxSizer(wx.VERTICAL))
837 self._main_sizer.Add(self._page_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
838
839
840 self._top_sizers.append(wx.BoxSizer(wx.VERTICAL))
841 self._page_sizers[index].Add(self._top_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
842
843
844 self._top_sizers[index].Add(panel, 1, wx.ALL|wx.EXPAND, 0)
845
846
847 self._button_sizers.append(wx.BoxSizer(wx.HORIZONTAL))
848
849
850 self._page_sizers[index].Add(self._button_sizers[index], 0, wx.ALIGN_RIGHT|wx.ALL, 0)
851
852
853 self._button_apply_flag.append(apply_button)
854 self._button_skip_flag.append(skip_button)
855
856
857 self._buttons.append({'back': None,
858 'apply': None,
859 'next': None,
860 'ok': None,
861 'finish': None,
862 'cancel': None})
863
864
865 self._button_ids.append({'back': -1,
866 'apply': -1,
867 'next': -1,
868 'ok': -1,
869 'finish': -1,
870 'cancel': -1})
871
872
873 self._exec_on_next.append(exec_on_next)
874
875
876 self._exec_count.append(0)
877
878
879 self._proceed_on_error.append(proceed_on_error)
880
881
882 if not proceed_on_error or uf_flush:
883 self._uf_flush.append(True)
884 else:
885 self._uf_flush.append(False)
886
887
888 self._seq_fn_list.append(self._next_fn)
889 self._seq_next.append(None)
890 self._seq_prev.append(None)
891
892
893 self._skip_flag.append(False)
894
895
896 panel.page_index = index
897
898
899 return index
900
901
903 """Prevent moving forwards (or unblock).
904
905 @keyword block: A flag which if True will block forwards movement and if False will unblock.
906 @type block: bool
907 """
908
909
910 buttons = ['next', 'ok', 'finish']
911
912
913 for i in range(len(buttons)):
914
915 button = self._buttons[self._current_page][buttons[i]]
916 if button == None:
917 continue
918
919
920 if block:
921 button.Disable()
922
923
924 else:
925 button.Enable()
926
927
928 - def get_page(self, index):
929 """Get a page from the wizard.
930
931 @param index: The index of the page.
932 @type index: int
933 @return: The page object.
934 @rtype: Wiz_page instance.
935 """
936
937
938 return self._pages[index]
939
940
942 """Reset the wizard."""
943
944
945 for i in range(len(self._exec_count)):
946 self._exec_count[i] = 0
947
948
949 - def run(self, modal=False):
950 """Execute the wizard.
951
952 @keyword modal: A flag which if True will cause the wizard to be run as a modal dialog.
953 @type modal: bool
954 @return: The status from the modal operation, i.e. True if the wizard is run, False if cancelled or other error occur. For modeless operation, this returns nothing.
955 @rtype: bool or None
956 """
957
958
959 for i in range(self._num_pages):
960 if self._pages[i].setup_fail:
961 return
962
963
964 if not self._buttons_built:
965 self._build_buttons()
966
967
968 self._display_page(0)
969
970
971 if self._pages[0].setup_fail:
972 return
973
974
975 if not status.show_gui:
976 return
977
978
979 if modal:
980
981 wiz_status = self.ShowModal()
982
983
984 return wiz_status
985
986
987 else:
988
989 self.Show()
990
991
993 """A user specified function for non-linear page changing.
994
995 @param index: The index of the page the function should be associated with.
996 @type index: int
997 @param fn: The function for determining the page after the current. This function should return the index of the next page.
998 @type fn: func or method.
999 """
1000
1001
1002 self._seq_fn_list[index] = fn
1003
1004
1005 - def setup_page(self, page=None, **kargs):
1006 """Allow a specified user function page to be remotely set up.
1007
1008 @keyword page: The page to setup. This is the page index key.
1009 @type page: str
1010 """
1011
1012
1013 page = self.get_page(self.page_indices[page])
1014
1015
1016 for arg in kargs:
1017
1018 value = kargs[arg]
1019 if isinstance(value, str):
1020 value = str_to_gui(value)
1021 elif is_float(value):
1022 value = float_to_gui(value)
1023
1024
1025 page.SetValue(arg, value)
1026