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