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