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
554
555
557 """Cancel the operation.
558
559 @keyword event: The wx event.
560 @type event: wx event
561 """
562
563
564 self._pages[self._current_page].on_next()
565
566
567 self.Close()
568
569
570 - def _display_page(self, i):
571 """Display the given page.
572
573 @param i: The index of the page to display.
574 @type i: int
575 """
576
577
578 for j in range(self._num_pages):
579 if self._main_sizer.IsShown(self._page_sizers[j]):
580 self._main_sizer.Hide(self._page_sizers[j])
581
582
583 if status.show_gui:
584 self._main_sizer.Show(self._page_sizers[i])
585
586
587 self._pages[i].on_display()
588 self._pages[i].on_display_post()
589
590
591 self.Layout()
592 self.Refresh()
593
594
595 self._pages[i].on_init()
596
597
598 self._pages[i].SetFocus()
599
600
602 """Return to the previous page.
603
604 @keyword event: The wx event.
605 @type event: wx event
606 """
607
608
609 self._pages[self._current_page].on_back()
610
611
612 self._current_page = self._seq_prev[self._current_page]
613
614
615 self._display_page(self._current_page)
616
617
619 """Move to the next page.
620
621 @keyword event: The wx event.
622 @type event: wx event
623 """
624
625
626 self._pages[self._current_page].on_next()
627
628
629 if not self._skip_flag[self._current_page]:
630
631 if self._exec_on_next[self._current_page]:
632 self._pages[self._current_page]._apply(event)
633
634
635 if self._uf_flush[self._current_page]:
636 interpreter.flush()
637
638
639 if not self._pages[self._current_page].exec_status:
640
641 if not self._proceed_on_error[self._current_page]:
642 return
643
644
645 self._exec_count[self._current_page] += 1
646
647
648 next_page = self._seq_fn_list[self._current_page]()
649
650
651 if next_page >= len(self._pages):
652 self._ok(None)
653 return
654
655
656 self._seq_next[self._current_page] = next_page
657 self._seq_prev[next_page] = self._current_page
658
659
660 self._current_page = next_page
661
662
663 self._display_page(self._current_page)
664
665
667 """Event handler for the close window action.
668
669 @keyword event: The wx event.
670 @type event: wx event
671 """
672
673
674 self._pages[self._current_page].on_next()
675
676
677 event.Skip()
678
679
681 """Event handler for key strokes.
682
683 @keyword event: The wx event.
684 @type event: wx event
685 """
686
687
688 self.Close()
689
690
692 """Standard function for setting the next page to the one directly next in the sequence.
693
694 @return: The index of the next page, which is the current page index plus one.
695 @rtype: int
696 """
697
698
699 return self._current_page + 1
700
701
702 - def _ok(self, event=None):
703 """Accept the operation.
704
705 @keyword event: The wx event.
706 @type event: wx event
707 """
708
709
710 for i in self._seq_loop():
711 if not self._exec_count[i] and not self._skip_flag[i]:
712
713 self._pages[i]._apply(event)
714
715
716 if self._uf_flush[i]:
717 interpreter.flush()
718
719
720 if not self._pages[self._current_page].exec_status:
721
722 if not self._proceed_on_error[self._current_page]:
723 return
724
725
726 self._exec_count[i] += 1
727
728
729 self._pages[self._current_page].on_next()
730
731
732 if self.IsModal():
733 self.EndModal(wx.ID_OK)
734 else:
735 self.Close()
736
737
739 """Loop over the sequence in the forwards direction."""
740
741
742 current = 0
743
744
745 yield current
746
747
748 while True:
749
750 next = self._seq_next[current]
751 current = next
752
753
754 if next == None:
755 break
756
757
758 yield next
759
760
761 - def _skip(self, event=None):
762 """Skip the page.
763
764 @keyword event: The wx event.
765 @type event: wx event
766 """
767
768
769 self._skip_flag[self._current_page] = True
770
771
772 self._go_next(None)
773
774
776 """Override the default wx.Dialog.Destroy() method."""
777
778
779 self.Close()
780
781
782 for i in range(len(self._buttons)):
783
784 for name in self._buttons[i]:
785 if hasattr(self._buttons[i][name], 'Destroy'):
786 self._buttons[i][name].Destroy()
787 self._buttons[i][name] = None
788
789
790 if hasattr(self._pages[i], 'Destroy'):
791 self._pages[i].Destroy()
792 self._pages[i] = None
793
794
795 super(Wiz_window, self).DestroyChildren()
796 super(Wiz_window, self).Destroy()
797
798
799 - def add_page(self, panel, apply_button=True, skip_button=False, exec_on_next=True, proceed_on_error=True, uf_flush=False):
800 """Add a new page to the wizard.
801
802 @param panel: The page to add to the wizard.
803 @type panel: wx.Panel instance
804 @keyword apply_button: A flag which if true will show the apply button for that page.
805 @type apply_button: bool
806 @keyword skip_button: A flag which if true will show the skip button for that page.
807 @type skip_button: bool
808 @keyword exec_on_next: A flag which if true will run the on_execute() method when clicking on the next button.
809 @type exec_on_next: bool
810 @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).
811 @type proceed_on_error: bool
812 @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.
813 @type uf_flush: bool
814 @return: The index of the page in the wizard.
815 @rtype: int
816 """
817
818
819 index = self._num_pages
820 self._num_pages += 1
821 self._pages.append(panel)
822
823
824 self._page_sizers.append(wx.BoxSizer(wx.VERTICAL))
825 self._main_sizer.Add(self._page_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
826
827
828 self._top_sizers.append(wx.BoxSizer(wx.VERTICAL))
829 self._page_sizers[index].Add(self._top_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
830
831
832 self._top_sizers[index].Add(panel, 1, wx.ALL|wx.EXPAND, 0)
833
834
835 self._button_sizers.append(wx.BoxSizer(wx.HORIZONTAL))
836
837
838 self._page_sizers[index].Add(self._button_sizers[index], 0, wx.ALIGN_RIGHT|wx.ALL, 0)
839
840
841 self._button_apply_flag.append(apply_button)
842 self._button_skip_flag.append(skip_button)
843
844
845 self._buttons.append({'back': None,
846 'apply': None,
847 'next': None,
848 'ok': None,
849 'finish': None,
850 'cancel': None})
851
852
853 self._button_ids.append({'back': -1,
854 'apply': -1,
855 'next': -1,
856 'ok': -1,
857 'finish': -1,
858 'cancel': -1})
859
860
861 self._exec_on_next.append(exec_on_next)
862
863
864 self._exec_count.append(0)
865
866
867 self._proceed_on_error.append(proceed_on_error)
868
869
870 if not proceed_on_error or uf_flush:
871 self._uf_flush.append(True)
872 else:
873 self._uf_flush.append(False)
874
875
876 self._seq_fn_list.append(self._next_fn)
877 self._seq_next.append(None)
878 self._seq_prev.append(None)
879
880
881 self._skip_flag.append(False)
882
883
884 panel.page_index = index
885
886
887 return index
888
889
891 """Prevent moving forwards (or unblock).
892
893 @keyword block: A flag which if True will block forwards movement and if False will unblock.
894 @type block: bool
895 """
896
897
898 buttons = ['next', 'ok', 'finish']
899
900
901 for i in range(len(buttons)):
902
903 button = self._buttons[self._current_page][buttons[i]]
904 if button == None:
905 continue
906
907
908 if block:
909 button.Disable()
910
911
912 else:
913 button.Enable()
914
915
916 - def get_page(self, index):
917 """Get a page from the wizard.
918
919 @param index: The index of the page.
920 @type index: int
921 @return: The page object.
922 @rtype: Wiz_page instance.
923 """
924
925
926 return self._pages[index]
927
928
930 """Reset the wizard."""
931
932
933 for i in range(len(self._exec_count)):
934 self._exec_count[i] = 0
935
936
937 - def run(self, modal=False):
938 """Execute the wizard.
939
940 @keyword modal: A flag which if True will cause the wizard to be run as a modal dialog.
941 @type modal: bool
942 @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.
943 @rtype: bool or None
944 """
945
946
947 for i in range(self._num_pages):
948 if self._pages[i].setup_fail:
949 return
950
951
952 if not self._buttons_built:
953 self._build_buttons()
954
955
956 self._display_page(0)
957
958
959 if self._pages[0].setup_fail:
960 return
961
962
963 if not status.show_gui:
964 return
965
966
967 if modal:
968
969 wiz_status = self.ShowModal()
970
971
972 return wiz_status
973
974
975 else:
976
977 self.Show()
978
979
981 """A user specified function for non-linear page changing.
982
983 @param index: The index of the page the function should be associated with.
984 @type index: int
985 @param fn: The function for determining the page after the current. This function should return the index of the next page.
986 @type fn: func or method.
987 """
988
989
990 self._seq_fn_list[index] = fn
991
992
993 - def setup_page(self, page=None, **kargs):
994 """Allow a specified user function page to be remotely set up.
995
996 @keyword page: The page to setup. This is the page index key.
997 @type page: str
998 """
999
1000
1001 page = self.get_page(self.page_indices[page])
1002
1003
1004 for arg in kargs:
1005
1006 value = kargs[arg]
1007 if isinstance(value, str):
1008 value = str_to_gui(value)
1009 elif is_float(value):
1010 value = float_to_gui(value)
1011
1012
1013 page.SetValue(arg, value)
1014