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