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 import Relax_data_store; ds = Relax_data_store()
31 from relax_errors import RelaxImplementError
32 from status import Status; status = Status()
33
34
35 from gui.interpreter import Interpreter; interpreter = Interpreter()
36 from gui.filedialog import RelaxFileDialog
37 from gui.fonts import font
38 from gui.icons import relax_icons
39 from gui.misc import add_border, bitmap_setup, open_file, protected_exec
40 from gui import paths
41 from gui.string_conv import bool_to_gui, gui_to_int, gui_to_str, int_to_gui, str_to_gui
42
43
44 -class Wiz_page(wx.Panel):
45 """The wizard pages to be placed inside the wizard.
46
47 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:
48
49 - add_artwork(), this builds the left hand artwork section of the page.
50 - add_contents(), this builds the right hand section of the page.
51 - on_display(), this is executed when the page is displayed.
52 - on_display_post(), this is executed when the page is displayed, directly after the on_display method.
53 - on_execute(), this is executed when the wizard is terminated or the apply button is hit.
54 - on_next(), this is executed when moving to the next wizard page.
55
56 The following methods can be used by add_contents() to create standard GUI elements:
57
58 - chooser()
59 - combo_box()
60 - file_selection()
61 - input_field()
62 - text()
63
64 These are described in full detail in their docstrings.
65 """
66
67
68 art_spacing = 20
69 divider = None
70 height_element = 27
71 image_path = paths.IMAGE_PATH + "relax.gif"
72 main_text = ''
73 setup_fail = False
74 size_button = (100, 33)
75 size_square_button = (33, 33)
76 title = ''
77
78 - def __init__(self, parent, height_desc=220):
79 """Set up the window.
80
81 @param parent: The parent GUI element.
82 @type parent: wx.object instance
83 @keyword height_desc: The height in pixels of the description part of the wizard.
84 @type height_desc: int or None
85 """
86
87
88 self.parent = parent
89 self.height_desc = height_desc
90
91
92 wx.Panel.__init__(self, parent, id=-1)
93
94
95 self.exec_status = False
96
97
98 self._elements = {}
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 self.exec_status = self.on_execute()
164
165
166 if not self.exec_status:
167 return
168
169
170 self.on_completion()
171
172
173 self.on_apply()
174
175
176 - def _build_main_section(self, sizer):
177 """Add the main part of the dialog.
178
179 @param sizer: A sizer object.
180 @type sizer: wx.Sizer instance
181 @return: The sizer object for the main part of the dialog.
182 @rtype: wx.Sizer instance
183 """
184
185
186 main_sizer = wx.BoxSizer(wx.VERTICAL)
187
188
189 sizer.Add(main_sizer, 1, wx.EXPAND|wx.ALL, 0)
190
191
192 return main_sizer
193
194
195 - def add_artwork(self, sizer):
196 """Add the artwork to the dialog.
197
198 @param sizer: A sizer object.
199 @type sizer: wx.Sizer instance
200 """
201
202
203 if self.image_path:
204 self.image = wx.StaticBitmap(self, -1, bitmap_setup(self.image_path))
205 sizer.Add(self.image, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 0)
206
207
208 sizer.AddSpacer(self.art_spacing)
209
210
211 - def add_contents(self, sizer):
212 """Add the specific GUI elements (dummy method).
213
214 @param sizer: A sizer object.
215 @type sizer: wx.Sizer instance
216 """
217
218
219 raise RelaxImplementError
220
221
222 - def add_desc(self, sizer, max_y=220):
223 """Add the description to the dialog.
224
225 @param sizer: A sizer object.
226 @type sizer: wx.Sizer instance
227 @keyword max_y: The maximum height, in number of pixels, for the description.
228 @type max_y: int
229 """
230
231
232 sizer.AddSpacer(5)
233 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
234 sizer.AddSpacer(5)
235
236
237 panel = scrolledpanel.ScrolledPanel(self, -1, name="desc")
238
239
240 panel_sizer = wx.BoxSizer(wx.VERTICAL)
241
242
243 text = wx.StaticText(panel, -1, self.main_text, style=wx.TE_MULTILINE)
244 text.SetFont(font.normal)
245
246
247 text.Wrap(self._main_size - 20)
248
249
250 x, y = text.GetSizeTuple()
251
252
253 if y > max_y-10:
254
255 panel.SetInitialSize((self._main_size, max_y))
256
257
258 else:
259
260 text.Wrap(self._main_size)
261
262
263 panel.SetInitialSize(text.GetSize())
264
265
266 panel_sizer.Add(text, 0, wx.ALIGN_LEFT, 0)
267
268
269 panel.SetSizer(panel_sizer)
270 panel.SetAutoLayout(1)
271 panel.SetupScrolling(scroll_x=False, scroll_y=True)
272 sizer.Add(panel, 0, wx.ALL|wx.EXPAND)
273
274
275 sizer.AddSpacer(5)
276 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
277 sizer.AddSpacer(5)
278
279
280 - def on_apply(self):
281 """To be over-ridden if an action is to be performed on hitting the apply button.
282
283 This method will be called when clicking on the apply button.
284 """
285
286
288 """To be over-ridden if an action is to be performed just before moving back to the previous page.
289
290 This method is called when moving back to the previous page of the wizard.
291 """
292
293
294 - def on_completion(self):
295 """To be over-ridden if an action is to be performed just after executing self.on_execute().
296
297 This method is called just after self.on_execute() has been called
298 """
299
300
301 - def on_display(self):
302 """To be over-ridden if an action is to be performed prior to displaying the page.
303
304 This method will be called by the wizard class method _display_page() just after hiding all other pages but prior to displaying this page.
305 """
306
307
308 - def on_display_post(self):
309 """To be over-ridden if an action is to be performed after the execution of the on_display() method.
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_execute(self):
316 """To be over-ridden if an action is to be performed just before exiting the page.
317
318 This method is called when terminating the wizard or hitting the apply button.
319 """
320
321 return True
322
323
325 """To be over-ridden if an action is to be performed when a page is newly displayed.
326
327 This method will be called by the wizard class method _display_page() at the very end.
328 """
329
330
332 """To be over-ridden if an action is to be performed just before moving to the next page.
333
334 This method is called when moving to the next page of the wizard.
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
478 - def _apply(self, event=None):
479 """Execute the current page's 'Apply' method.
480
481 @keyword event: The wx event.
482 @type event: wx event
483 """
484
485
486 self._pages[self._current_page]._apply()
487
488
588
589
591 """Cancel the operation.
592
593 @keyword event: The wx event.
594 @type event: wx event
595 """
596
597
598 self._pages[self._current_page].on_next()
599
600
601 self.Close()
602
603
604 - def _display_page(self, i):
605 """Display the given page.
606
607 @param i: The index of the page to display.
608 @type i: int
609 """
610
611
612 for j in range(self._num_pages):
613 if self._main_sizer.IsShown(self._page_sizers[j]):
614 self._main_sizer.Hide(self._page_sizers[j])
615
616
617 if status.show_gui:
618 self._main_sizer.Show(self._page_sizers[i])
619
620
621 self._pages[i].on_display()
622 self._pages[i].on_display_post()
623
624
625 self.Layout()
626 self.Refresh()
627
628
629 self._pages[i].on_init()
630
631
633 """Return to the previous page.
634
635 @keyword event: The wx event.
636 @type event: wx event
637 """
638
639
640 self._pages[self._current_page].on_back()
641
642
643 self._current_page = self._seq_prev[self._current_page]
644
645
646 self._display_page(self._current_page)
647
648
650 """Move to the next page.
651
652 @keyword event: The wx event.
653 @type event: wx event
654 """
655
656
657 self._pages[self._current_page].on_next()
658
659
660 if not self._skip_flag[self._current_page]:
661
662 if self._exec_on_next[self._current_page]:
663 self._pages[self._current_page]._apply(event)
664
665
666 if self._uf_flush[self._current_page]:
667 interpreter.flush()
668
669
670 if not self._pages[self._current_page].exec_status:
671
672 if not self._proceed_on_error[self._current_page]:
673 return
674
675
676 self._exec_count[self._current_page] += 1
677
678
679 next_page = self._seq_fn_list[self._current_page]()
680
681
682 if self._pages[next_page] == None:
683 self._ok(None)
684 return
685
686
687 self._seq_next[self._current_page] = next_page
688 self._seq_prev[next_page] = self._current_page
689
690
691 self._current_page = next_page
692
693
694 self._display_page(self._current_page)
695
696
698 """Event handler for the close window action.
699
700 @keyword event: The wx event.
701 @type event: wx event
702 """
703
704
705 self._pages[self._current_page].on_next()
706
707
708 event.Skip()
709
710
712 """Standard function for setting the next page to the one directly next in the sequence.
713
714 @return: The index of the next page, which is the current page index plus one.
715 @rtype: int
716 """
717
718
719 return self._current_page + 1
720
721
722 - def _ok(self, event=None):
723 """Accept the operation.
724
725 @keyword event: The wx event.
726 @type event: wx event
727 """
728
729
730 for i in self._seq_loop():
731 if not self._exec_count[i] and not self._skip_flag[i]:
732
733 self._pages[i]._apply(event)
734
735
736 if self._uf_flush[i]:
737 interpreter.flush()
738
739
740 if not self._pages[self._current_page].exec_status:
741
742 if not self._proceed_on_error[self._current_page]:
743 return
744
745
746 self._exec_count[i] += 1
747
748
749 self._pages[self._current_page].on_next()
750
751
752 if self.IsModal():
753 self.EndModal(wx.ID_OK)
754 else:
755 self.Close()
756
757
759 """Loop over the sequence in the forwards direction."""
760
761
762 current = 0
763
764
765 yield current
766
767
768 while True:
769
770 next = self._seq_next[current]
771 current = next
772
773
774 if next == None:
775 break
776
777
778 yield next
779
780
781 - def _skip(self, event=None):
782 """Skip the page.
783
784 @keyword event: The wx event.
785 @type event: wx event
786 """
787
788
789 self._skip_flag[self._current_page] = True
790
791
792 self._go_next(None)
793
794
795 - def add_page(self, panel, apply_button=True, skip_button=False, exec_on_next=True, proceed_on_error=True, uf_flush=False):
796 """Add a new page to the wizard.
797
798 @param panel: The page to add to the wizard.
799 @type panel: wx.Panel instance
800 @keyword apply_button: A flag which if true will show the apply button for that page.
801 @type apply_button: bool
802 @keyword skip_button: A flag which if true will show the skip button for that page.
803 @type skip_button: bool
804 @keyword exec_on_next: A flag which if true will run the on_execute() method when clicking on the next button.
805 @type exec_on_next: bool
806 @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).
807 @type proceed_on_error: bool
808 @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.
809 @type uf_flush: bool
810 @return: The index of the page in the wizard.
811 @rtype: int
812 """
813
814
815 index = self._num_pages
816 self._num_pages += 1
817 self._pages[index] = panel
818
819
820 self._main_sizer.Add(self._page_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
821
822
823 top_sizer = wx.BoxSizer(wx.VERTICAL)
824 self._page_sizers[index].Add(top_sizer, 1, wx.ALL|wx.EXPAND, 0)
825
826
827 top_sizer.Add(panel, 1, wx.ALL|wx.EXPAND, 0)
828
829
830 self._page_sizers[index].Add(self._button_sizers[index], 0, wx.ALIGN_RIGHT|wx.ALL, 0)
831
832
833 self._button_apply_flag[index] = apply_button
834 self._button_skip_flag[index] = skip_button
835 self._exec_on_next[index] = exec_on_next
836 self._proceed_on_error[index] = proceed_on_error
837 if not proceed_on_error or uf_flush:
838 self._uf_flush[index] = True
839
840
841 panel.page_index = self._num_pages - 1
842
843
844 return panel.page_index
845
846
848 """Prevent moving forwards (or unblock).
849
850 @keyword block: A flag which if True will block forwards movement and if False will unblock.
851 @type block: bool
852 """
853
854
855 buttons = ['next', 'ok', 'finish']
856
857
858 for i in range(len(buttons)):
859
860 button = self._buttons[self._current_page][buttons[i]]
861 if button == None:
862 continue
863
864
865 if block:
866 button.Disable()
867
868
869 else:
870 button.Enable()
871
872
873 - def get_page(self, index):
874 """Get a page from the wizard.
875
876 @param index: The index of the page.
877 @type index: int
878 @return: The page object.
879 @rtype: Wiz_page instance.
880 """
881
882
883 return self._pages[index]
884
885
887 """Reset the wizard."""
888
889
890 for i in range(len(self._exec_count)):
891 self._exec_count[i] = 0
892
893
894 - def run(self, modal=False):
895 """Execute the wizard.
896
897 @keyword modal: A flag which if True will cause the wizard to be run as a modal dialog.
898 @type modal: bool
899 @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.
900 @rtype: bool or None
901 """
902
903
904 for i in range(self._num_pages):
905 if self._pages[i].setup_fail:
906 return
907
908
909 if not self._buttons_built:
910 self._build_buttons()
911
912
913 self._display_page(0)
914
915
916 if self._pages[0].setup_fail:
917 return
918
919
920 if not status.show_gui:
921 return
922
923
924 if modal:
925
926 wiz_status = self.ShowModal()
927
928
929 return wiz_status
930
931
932 else:
933
934 self.Show()
935
936
938 """A user specified function for non-linear page changing.
939
940 @param index: The index of the page the function should be associated with.
941 @type index: int
942 @param fn: The function for determining the page after the current. This function should return the index of the next page.
943 @type fn: func or method.
944 """
945
946
947 self._seq_fn_list[index] = fn
948