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