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