"Fossies" - the Fresh Open Source Software Archive

Member "relax-5.0.0/gui/wizards/wiz_objects.py" (2 Dec 2019, 34998 Bytes) of package /linux/privat/relax-5.0.0.src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "wiz_objects.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.1.3_vs_5.0.0.

    1 ###############################################################################
    2 #                                                                             #
    3 # Copyright (C) 2010-2015,2019 Edward d'Auvergne                              #
    4 #                                                                             #
    5 # This file is part of the program relax (http://www.nmr-relax.com).          #
    6 #                                                                             #
    7 # This program is free software: you can redistribute it and/or modify        #
    8 # it under the terms of the GNU General Public License as published by        #
    9 # the Free Software Foundation, either version 3 of the License, or           #
   10 # (at your option) any later version.                                         #
   11 #                                                                             #
   12 # This program is distributed in the hope that it will be useful,             #
   13 # but WITHOUT ANY WARRANTY; without even the implied warranty of              #
   14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
   15 # GNU General Public License for more details.                                #
   16 #                                                                             #
   17 # You should have received a copy of the GNU General Public License           #
   18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
   19 #                                                                             #
   20 ###############################################################################
   21 
   22 # Module docstring.
   23 """Base class module for the wizard GUI elements."""
   24 
   25 # Python module imports.
   26 import wx
   27 from wx.lib import buttons, scrolledpanel
   28 
   29 # relax module imports.
   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 # The wx ID for the special accelerator table that allows the ESC button to close relax wizards.
   43 ESC_ID = wx.NewId()
   44 
   45 
   46 
   47 class Wiz_page(wx.Panel):
   48     """The wizard pages to be placed inside the wizard.
   49 
   50     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:
   51 
   52         - add_artwork(), this builds the left hand artwork section of the page.
   53         - add_contents(), this builds the right hand section of the page.
   54         - on_display(), this is executed when the page is displayed.
   55         - on_display_post(), this is executed when the page is displayed, directly after the on_display method.
   56         - on_execute(), this is executed when the wizard is terminated or the apply button is hit.
   57         - on_next(), this is executed when moving to the next wizard page.
   58 
   59     The following methods can be used by add_contents() to create standard GUI elements:
   60 
   61         - chooser()
   62         - combo_box()
   63         - file_selection()
   64         - input_field()
   65         - text()
   66 
   67     These are described in full detail in their docstrings.
   68     """
   69 
   70     # Some class variables.
   71     art_spacing = 20
   72     divider = None
   73     height_element = 27
   74     image_path = IMAGE_PATH + "relax.gif"
   75     main_text = ''
   76     setup_fail = False
   77     size_button = (100, 33)
   78     size_square_button = (33, 33)
   79     title = ''
   80 
   81     def __init__(self, parent, height_desc=220):
   82         """Set up the window.
   83 
   84         @param parent:          The parent GUI element.
   85         @type parent:           wx.object instance
   86         @keyword height_desc:   The height in pixels of the description part of the wizard.
   87         @type height_desc:      int or None
   88         """
   89 
   90         # Store the args.
   91         self.parent = parent
   92         self.height_desc = height_desc
   93 
   94         # Execute the base class method.
   95         wx.Panel.__init__(self, parent, id=-1)
   96 
   97         # Initilise some variables.
   98         self.exec_status = False
   99 
  100         # Pack a sizer into the panel.
  101         box_main = wx.BoxSizer(wx.HORIZONTAL)
  102         self.SetSizer(box_main)
  103 
  104         # Add the artwork.
  105         self.add_artwork(box_main)
  106 
  107         # The size of the image.
  108         image_x, image_y = self.image.GetSize()
  109 
  110         # Calculate the size of the main section, and the subdivisions.
  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         # Add the main sizer.
  119         main_sizer = self._build_main_section(box_main)
  120 
  121         # Add the title.
  122         self._add_title(main_sizer)
  123 
  124         # Add the description.
  125         self.add_desc(main_sizer, max_y=self.height_desc)
  126 
  127         # Add the specific GUI elements (bounded by spacers).
  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         # Spacing.
  140         sizer.AddSpacer(10)
  141 
  142         # The text.
  143         title = wx.StaticText(self, -1, self.title)
  144 
  145         # Font.
  146         title.SetFont(font.title)
  147 
  148         # Add the title.
  149         sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 0)
  150 
  151         # Spacing.
  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         # A bit of user feedback.
  163         wx.BeginBusyCursor()
  164 
  165         # Execute.
  166         self.exec_status = self.on_execute()
  167 
  168         # Execution failure.
  169         if not self.exec_status:
  170             if wx.IsBusy():
  171                 wx.EndBusyCursor()
  172             return
  173 
  174         # Finished.
  175         self.on_completion()
  176 
  177         # Execute the on_apply() method.
  178         self.on_apply()
  179 
  180         # Turn off the busy cursor if needed.
  181         if wx.IsBusy():
  182             wx.EndBusyCursor()
  183 
  184 
  185     def _build_main_section(self, sizer):
  186         """Add the main part of the dialog.
  187 
  188         @param sizer:   A sizer object.
  189         @type sizer:    wx.Sizer instance
  190         @return:        The sizer object for the main part of the dialog.
  191         @rtype:         wx.Sizer instance
  192         """
  193 
  194         # Use a grid sizer for packing the elements.
  195         main_sizer = wx.BoxSizer(wx.VERTICAL)
  196 
  197         # Pack the sizer.
  198         sizer.Add(main_sizer, 1, wx.EXPAND|wx.ALL, 0)
  199 
  200         # Return the sizer.
  201         return main_sizer
  202 
  203 
  204     def add_artwork(self, sizer):
  205         """Add the artwork to the dialog.
  206 
  207         @param sizer:   A sizer object.
  208         @type sizer:    wx.Sizer instance
  209         """
  210 
  211         # Add the graphics.
  212         if self.image_path:
  213             self.image = wx.StaticBitmap(self, -1, bitmap_setup(self.image_path))
  214             sizer.Add(self.image, 0, wx.TOP|wx.ALIGN_CENTER_HORIZONTAL, 0)
  215 
  216         # A spacer.
  217         sizer.AddSpacer(self.art_spacing)
  218 
  219 
  220     def add_contents(self, sizer):
  221         """Add the specific GUI elements (dummy method).
  222 
  223         @param sizer:   A sizer object.
  224         @type sizer:    wx.Sizer instance
  225         """
  226 
  227         # This must be supplied.
  228         raise RelaxImplementError
  229 
  230 
  231     def add_desc(self, sizer, max_y=220):
  232         """Add the description to the dialog.
  233 
  234         @param sizer:   A sizer object.
  235         @type sizer:    wx.Sizer instance
  236         @keyword max_y: The maximum height, in number of pixels, for the description.
  237         @type max_y:    int
  238         """
  239 
  240         # A line with spacing.
  241         sizer.AddSpacer(5)
  242         sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
  243         sizer.AddSpacer(5)
  244 
  245         # Create a scrolled panel.
  246         panel = scrolledpanel.ScrolledPanel(self, -1, name="desc")
  247 
  248         # A sizer for the panel.
  249         panel_sizer = wx.BoxSizer(wx.VERTICAL)
  250 
  251         # The text.
  252         text = wx.StaticText(panel, -1, self.main_text, style=wx.TE_MULTILINE)
  253         text.SetFont(font.normal)
  254 
  255         # Wrap the text.
  256         text.Wrap(self._main_size - 20)
  257 
  258         # The text size.
  259         x, y = text.GetSize()
  260 
  261         # Scrolling needed.
  262         if y > max_y-10:
  263             # Set the panel size.
  264             panel.SetInitialSize((self._main_size, max_y))
  265 
  266         # No scrolling.
  267         else:
  268             # Rewrap the text.
  269             text.Wrap(self._main_size)
  270 
  271             # Set the panel size.
  272             panel.SetInitialSize(text.GetSize())
  273 
  274         # Add the text.
  275         panel_sizer.Add(text, 0, wx.ALIGN_LEFT, 0)
  276 
  277         # Set up and add the panel to the sizer.
  278         panel.SetSizer(panel_sizer)
  279         panel.SetAutoLayout(1)
  280         panel.SetupScrolling(scroll_x=False, scroll_y=True)
  281         sizer.Add(panel, 0, wx.ALL|wx.EXPAND)
  282 
  283         # A line with spacing.
  284         sizer.AddSpacer(5)
  285         sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
  286         sizer.AddSpacer(5)
  287 
  288 
  289     def on_apply(self):
  290         """To be over-ridden if an action is to be performed on hitting the apply button.
  291 
  292         This method will be called when clicking on the apply button.
  293         """
  294 
  295 
  296     def on_back(self):
  297         """To be over-ridden if an action is to be performed just before moving back to the previous page.
  298 
  299         This method is called when moving back to the previous page of the wizard.
  300         """
  301 
  302 
  303     def on_completion(self):
  304         """To be over-ridden if an action is to be performed just after executing self.on_execute().
  305 
  306         This method is called just after self.on_execute() has been called
  307         """
  308 
  309 
  310     def on_display(self):
  311         """To be over-ridden if an action is to be performed prior to displaying the page.
  312 
  313         This method will be called by the wizard class method _display_page() just after hiding all other pages but prior to displaying this page.
  314         """
  315 
  316 
  317     def on_display_post(self):
  318         """To be over-ridden if an action is to be performed after the execution of the on_display() method.
  319 
  320         This method will be called by the wizard class method _display_page() just after hiding all other pages but prior to displaying this page.
  321         """
  322 
  323 
  324     def on_execute(self):
  325         """To be over-ridden if an action is to be performed just before exiting the page.
  326 
  327         This method is called when terminating the wizard or hitting the apply button.
  328         """
  329 
  330         return True
  331 
  332 
  333     def on_init(self):
  334         """To be over-ridden if an action is to be performed when a page is newly displayed.
  335 
  336         This method will be called by the wizard class method _display_page() at the very end.
  337         """
  338 
  339 
  340     def on_next(self):
  341         """To be over-ridden if an action is to be performed just before moving to the next page.
  342 
  343         This method is called when moving to the next page of the wizard.
  344         """
  345 
  346 
  347 
  348 class Wiz_window(wx.Dialog):
  349     """The wizard."""
  350 
  351     # Some class variables.
  352     _size_button = (100, 33)
  353     ICON_APPLY = fetch_icon('oxygen.actions.dialog-ok-apply', "22x22")
  354     ICON_BACK = fetch_icon('oxygen.actions.go-previous-view', "22x22")
  355     ICON_CANCEL = fetch_icon('oxygen.actions.dialog-cancel', "22x22")
  356     ICON_FINISH = fetch_icon('oxygen.actions.dialog-ok', "22x22")
  357     ICON_NEXT = fetch_icon('oxygen.actions.go-next-view', "22x22")
  358     ICON_OK = fetch_icon('oxygen.actions.dialog-ok', "22x22")
  359     ICON_SKIP = fetch_icon('oxygen.actions.arrow-right-double-relax-blue', "22x22")
  360     TEXT_APPLY = " Apply"
  361     TEXT_BACK = " Back"
  362     TEXT_CANCEL = " Cancel"
  363     TEXT_FINISH = " Finish"
  364     TEXT_NEXT = " Next"
  365     TEXT_OK = " OK"
  366     TEXT_SKIP = " Skip"
  367 
  368 
  369     def __init__(self, parent=None, size_x=400, size_y=400, title='', border=10, style=wx.DEFAULT_DIALOG_STYLE):
  370         """Set up the window.
  371 
  372         @keyword parent:    The parent window.
  373         @type parent:       wx.Window instance
  374         @keyword size_x:    The width of the wizard.
  375         @type size_x:       int
  376         @keyword size_y:    The height of the wizard.
  377         @type size_y:       int
  378         @keyword title:     The title of the wizard dialog.
  379         @type title:        str
  380         @keyword border:    The size of the border inside the wizard.
  381         @type border:       int
  382         @keyword style:     The dialog style.
  383         @type style:        wx style
  384         """
  385 
  386         # Store the args.
  387         self._size_x = size_x
  388         self._size_y = size_y
  389         self._border = border
  390         self.title = title
  391 
  392         # Execute the base class method.
  393         wx.Dialog.__init__(self, parent, id=-1, title=title, style=style)
  394 
  395         # Set up the window icon.
  396         self.SetIcons(Relax_icons())
  397 
  398         # The sizer for the dialog.
  399         sizer = wx.BoxSizer(wx.VERTICAL)
  400         self.SetSizer(sizer)
  401 
  402         # Build the central sizer, with borders.
  403         self._main_sizer = add_border(sizer, border=border, packing=wx.VERTICAL)
  404 
  405         # Set the default size of the dialog.
  406         self.SetSize((size_x, size_y))
  407 
  408         # Centre the dialog.
  409         self.Centre()
  410 
  411         # Initialise the page storage.
  412         self._current_page = 0
  413         self._num_pages = 0
  414         self._pages = []
  415         self._page_sizers = []
  416         self._button_sizers = []
  417         self._top_sizers = []
  418         self._button_apply_flag = []
  419         self._button_skip_flag = []
  420         self._buttons = []
  421         self._button_ids = []
  422         self._exec_on_next = []
  423         self._exec_count = []
  424         self._proceed_on_error = []
  425         self._uf_flush = []
  426         self._seq_fn_list = []
  427         self._seq_next = []
  428         self._seq_prev = []
  429         self._skip_flag = []
  430 
  431         # Flag to suppress later button addition.
  432         self._buttons_built = False
  433 
  434         # Bind some events.
  435         self.Bind(wx.EVT_CLOSE, self._handler_close)
  436 
  437         # ESC to exit, via an accelerator table which creates menu events.
  438         self.acc_list = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, ESC_ID)]
  439         self.acc_table = wx.AcceleratorTable(self.acc_list)
  440         self.SetAcceleratorTable(self.acc_table)
  441         self.Bind(wx.EVT_MENU, self._handler_escape, id=ESC_ID)
  442 
  443 
  444     def _apply(self, event=None):
  445         """Execute the current page's 'Apply' method.
  446 
  447         @keyword event: The wx event.
  448         @type event:    wx event
  449         """
  450 
  451         # Execute the current page's apply() method.
  452         self._pages[self._current_page]._apply()
  453 
  454 
  455     def _build_buttons(self):
  456         """Construct the buttons for all pages of the wizard."""
  457 
  458         # Single page or a wizard?
  459         single_page = True
  460         if self._num_pages > 1:
  461             single_page = False
  462 
  463         # Loop over each page.
  464         for i in range(self._num_pages):
  465             # The back button (only for multi-pages, after the first).
  466             if not single_page and i > 0:
  467                 # Create the button.
  468                 button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_BACK)
  469                 button.SetBitmapLabel(wx.Bitmap(self.ICON_BACK, wx.BITMAP_TYPE_ANY))
  470                 button.SetFont(font.normal)
  471                 button.SetToolTip(wx.ToolTip("Return to the previous page."))
  472                 button.SetMinSize(self._size_button)
  473                 self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  474                 self.Bind(wx.EVT_BUTTON, self._go_back, button)
  475                 self._buttons[i]['back'] = button
  476 
  477                 # Spacer.
  478                 self._button_sizers[i].AddSpacer(5)
  479 
  480             # The apply button.
  481             if self._button_apply_flag[i]:
  482                 # Create the button.
  483                 button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_APPLY)
  484                 button.SetBitmapLabel(wx.Bitmap(self.ICON_APPLY, wx.BITMAP_TYPE_ANY))
  485                 button.SetFont(font.normal)
  486                 if single_page:
  487                     button.SetToolTip(wx.ToolTip("Apply the operation and leave the window open."))
  488                 else:
  489                     button.SetToolTip(wx.ToolTip("Apply the operation and stay on the current page."))
  490                 button.SetMinSize(self._size_button)
  491                 self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  492                 self.Bind(wx.EVT_BUTTON, self._pages[i]._apply, button)
  493                 self._buttons[i]['apply'] = button
  494 
  495                 # Spacer.
  496                 self._button_sizers[i].AddSpacer(5)
  497 
  498             # The skip button.
  499             if self._button_skip_flag[i]:
  500                 # Create the button.
  501                 button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_SKIP)
  502                 button.SetBitmapLabel(wx.Bitmap(self.ICON_SKIP, wx.BITMAP_TYPE_ANY))
  503                 button.SetFont(font.normal)
  504                 button.SetToolTip(wx.ToolTip("Skip the operation and move to the next page."))
  505                 button.SetMinSize(self._size_button)
  506                 self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  507                 self.Bind(wx.EVT_BUTTON, self._skip, button)
  508                 self._buttons[i]['skip'] = button
  509 
  510                 # Spacer.
  511                 self._button_sizers[i].AddSpacer(5)
  512 
  513             # The next button (only for multi-pages, excluding the last).
  514             if not single_page and i < self._num_pages - 1:
  515                 # Create the button.
  516                 button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_NEXT)
  517                 button.SetBitmapLabel(wx.Bitmap(self.ICON_NEXT, wx.BITMAP_TYPE_ANY))
  518                 button.SetFont(font.normal)
  519                 button.SetToolTip(wx.ToolTip("Apply the operation and move to the next page."))
  520                 button.SetMinSize(self._size_button)
  521                 self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  522                 self.Bind(wx.EVT_BUTTON, self._go_next, button)
  523                 self._buttons[i]['next'] = button
  524 
  525             # The OK button (only for single pages).
  526             if single_page:
  527                 button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_OK)
  528                 button.SetBitmapLabel(wx.Bitmap(self.ICON_OK, wx.BITMAP_TYPE_ANY))
  529                 button.SetFont(font.normal)
  530                 button.SetToolTip(wx.ToolTip("Apply the operation and close the window."))
  531                 button.SetMinSize(self._size_button)
  532                 self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  533                 self.Bind(wx.EVT_BUTTON, self._ok, button)
  534                 self._buttons[i]['ok'] = button
  535 
  536             # The finish button (only for the last page with multi-pages).
  537             if not single_page and i == self._num_pages - 1:
  538                 button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_FINISH)
  539                 button.SetBitmapLabel(wx.Bitmap(self.ICON_FINISH, wx.BITMAP_TYPE_ANY))
  540                 button.SetFont(font.normal)
  541                 button.SetToolTip(wx.ToolTip("Apply the operation and close the wizard."))
  542                 button.SetMinSize(self._size_button)
  543                 self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  544                 self.Bind(wx.EVT_BUTTON, self._ok, button)
  545                 self._buttons[i]['finish'] = button
  546 
  547             # Spacer.
  548             self._button_sizers[i].AddSpacer(15)
  549 
  550             # The cancel button.
  551             button = buttons.ThemedGenBitmapTextButton(self, -1, None, self.TEXT_CANCEL)
  552             button.SetBitmapLabel(wx.Bitmap(self.ICON_CANCEL, wx.BITMAP_TYPE_ANY))
  553             button.SetFont(font.normal)
  554             if single_page:
  555                 button.SetToolTip(wx.ToolTip("Abort the operation and close the window."))
  556             else:
  557                 button.SetToolTip(wx.ToolTip("Abort the operation and close the wizard."))
  558             button.SetMinSize(self._size_button)
  559             self._button_sizers[i].Add(button, 0, wx.ADJUST_MINSIZE, 0)
  560             self.Bind(wx.EVT_BUTTON, self._cancel, button)
  561             self._buttons[i]['cancel'] = button
  562 
  563         # Flag to suppress later button addition.
  564         self._buttons_built = True
  565 
  566 
  567     def _cancel(self, event=None):
  568         """Cancel the operation.
  569 
  570         @keyword event: The wx event.
  571         @type event:    wx event
  572         """
  573 
  574         # Execute the page's on_next() method to allow the page to clean itself up.
  575         self._pages[self._current_page].on_next()
  576 
  577         # Close the window.
  578         self.Close()
  579 
  580 
  581     def _display_page(self, i):
  582         """Display the given page.
  583 
  584         @param i:   The index of the page to display.
  585         @type i:    int
  586         """
  587 
  588         # Hide all of the original contents.
  589         for j in range(self._num_pages):
  590             if self._main_sizer.IsShown(self._page_sizers[j]):
  591                 self._main_sizer.Hide(self._page_sizers[j])
  592 
  593         # Show the desired page.
  594         if status.show_gui:
  595             self._main_sizer.Show(self._page_sizers[i])
  596 
  597         # Execute the page's on_display() method.
  598         self._pages[i].on_display()
  599         self._pages[i].on_display_post()
  600 
  601         # Re-perform the window layout.
  602         self.Layout()
  603         self.Refresh()
  604 
  605         # Execute the page's on_init() method.
  606         self._pages[i].on_init()
  607 
  608         # Set the focus to this page to allow the keyboard to be functional without a mouse click.
  609         self._pages[i].SetFocus()
  610 
  611 
  612     def _go_back(self, event=None):
  613         """Return to the previous page.
  614 
  615         @keyword event: The wx event.
  616         @type event:    wx event
  617         """
  618 
  619         # Execute the page's on_next() method.
  620         self._pages[self._current_page].on_back()
  621 
  622         # Work back in the sequence.
  623         self._current_page = self._seq_prev[self._current_page]
  624 
  625         # Display the previous page.
  626         self._display_page(self._current_page)
  627 
  628 
  629     def _go_next(self, event=None):
  630         """Move to the next page.
  631 
  632         @keyword event: The wx event.
  633         @type event:    wx event
  634         """
  635 
  636         # Execute the page's on_next() method.
  637         self._pages[self._current_page].on_next()
  638 
  639         # Operations for non-skipped pages.
  640         if not self._skip_flag[self._current_page]:
  641             # Execute the page's on_execute() method (via the _apply() method).
  642             if self._exec_on_next[self._current_page]:
  643                 self._pages[self._current_page]._apply(event)
  644 
  645                 # UF flush.
  646                 if self._uf_flush[self._current_page]:
  647                     interpreter.flush()
  648 
  649                 # Check for execution errors.
  650                 if not self._pages[self._current_page].exec_status:
  651                     # Do not proceed.
  652                     if not self._proceed_on_error[self._current_page]:
  653                         return
  654 
  655                 # Increment the execution counter.
  656                 self._exec_count[self._current_page] += 1
  657 
  658         # Determine the next page.
  659         next_page = self._seq_fn_list[self._current_page]()
  660 
  661         # No next page, so terminate.
  662         if next_page >= len(self._pages):
  663             self._ok(None)
  664             return
  665 
  666         # Update the sequence lists.
  667         self._seq_next[self._current_page] = next_page
  668         self._seq_prev[next_page] = self._current_page
  669 
  670         # Change the current page.
  671         self._current_page = next_page
  672 
  673         # Display the next page.
  674         self._display_page(self._current_page)
  675 
  676 
  677     def _handler_close(self, event=None):
  678         """Event handler for the close window action.
  679 
  680         @keyword event: The wx event.
  681         @type event:    wx event
  682         """
  683 
  684         # Execute the page's on_next() method to allow the page to clean itself up.
  685         self._pages[self._current_page].on_next()
  686 
  687         # Continue with the window closing.
  688         event.Skip()
  689 
  690 
  691     def _handler_escape(self, event=None):
  692         """Event handler for key strokes.
  693 
  694         @keyword event: The wx event.
  695         @type event:    wx event
  696         """
  697 
  698         # Close the window.
  699         self.Close()
  700 
  701 
  702     def _next_fn(self):
  703         """Standard function for setting the next page to the one directly next in the sequence.
  704 
  705         @return:    The index of the next page, which is the current page index plus one.
  706         @rtype:     int
  707         """
  708 
  709         # Return the next page.
  710         return self._current_page + 1
  711 
  712 
  713     def _ok(self, event=None):
  714         """Accept the operation.
  715 
  716         @keyword event: The wx event.
  717         @type event:    wx event
  718         """
  719 
  720         # Loop over the pages in the sequence and execute their _apply() methods, if not already done and not skipped.
  721         for i in self._seq_loop():
  722             if not self._exec_count[i] and not self._skip_flag[i]:
  723                 # Execute the _apply method.
  724                 self._pages[i]._apply(event)
  725 
  726                 # UF flush.
  727                 if self._uf_flush[i]:
  728                     interpreter.flush()
  729 
  730                 # Check for execution errors.
  731                 if not self._pages[self._current_page].exec_status:
  732                     # Do not proceed.
  733                     if not self._proceed_on_error[self._current_page]:
  734                         return
  735 
  736                 # Increment the execution counter.
  737                 self._exec_count[i] += 1
  738 
  739         # Execute the current page's on_next() method to allow the page to clean itself up.
  740         self._pages[self._current_page].on_next()
  741 
  742         # Then close the dialog.
  743         if self.IsModal():
  744             self.EndModal(wx.ID_OK)
  745         else:
  746             self.Close()
  747 
  748 
  749     def _seq_loop(self):
  750         """Loop over the sequence in the forwards direction."""
  751 
  752         # Initialise.
  753         current = 0
  754 
  755         # First yield the initial element (always zero!).
  756         yield current
  757 
  758         # Loop over the sequence.
  759         while True:
  760             # Update.
  761             next = self._seq_next[current]
  762             current = next
  763 
  764             # End of the sequence.
  765             if next == None:
  766                 break
  767 
  768             # Yield the next index.
  769             yield next
  770 
  771 
  772     def _skip(self, event=None):
  773         """Skip the page.
  774 
  775         @keyword event: The wx event.
  776         @type event:    wx event
  777         """
  778 
  779         # Set the skip flag.
  780         self._skip_flag[self._current_page] = True
  781 
  782         # Go to the next page.
  783         self._go_next(None)
  784 
  785 
  786     def Destroy(self):
  787         """Override the default wx.Dialog.Destroy() method."""
  788 
  789         # Call the parent method to close the dialog.
  790         self.Close()
  791 
  792         # Loop over each page, destroying it and all its elements to avoid memory leaks.
  793         for i in range(len(self._buttons)):
  794             # Destroy the buttons.
  795             for name in self._buttons[i]:
  796                 if hasattr(self._buttons[i][name], 'Destroy'):
  797                     self._buttons[i][name].Destroy()
  798                     self._buttons[i][name] = None
  799 
  800             # Destroy each page.
  801             if hasattr(self._pages[i], 'Destroy'):
  802                 self._pages[i].Destroy()
  803                 self._pages[i] = None
  804 
  805         # Call the parent method to destroy the dialog.
  806         super(Wiz_window, self).DestroyChildren()
  807         super(Wiz_window, self).Destroy()
  808 
  809 
  810     def add_page(self, panel, apply_button=True, skip_button=False, exec_on_next=True, proceed_on_error=True, uf_flush=False):
  811         """Add a new page to the wizard.
  812 
  813         @param panel:               The page to add to the wizard.
  814         @type panel:                wx.Panel instance
  815         @keyword apply_button:      A flag which if true will show the apply button for that page.
  816         @type apply_button:         bool
  817         @keyword skip_button:       A flag which if true will show the skip button for that page.
  818         @type skip_button:          bool
  819         @keyword exec_on_next:      A flag which if true will run the on_execute() method when clicking on the next button.
  820         @type exec_on_next:         bool
  821         @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).
  822         @type proceed_on_error:     bool
  823         @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.
  824         @type uf_flush:             bool
  825         @return:                    The index of the page in the wizard.
  826         @rtype:                     int
  827         """
  828 
  829         # Store the page.
  830         index = self._num_pages
  831         self._num_pages += 1
  832         self._pages.append(panel)
  833 
  834         # Initialise all box sizers for the wizard page, and store them.
  835         self._page_sizers.append(wx.BoxSizer(wx.VERTICAL))
  836         self._main_sizer.Add(self._page_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
  837 
  838         # Add the sizer for the top half.
  839         self._top_sizers.append(wx.BoxSizer(wx.VERTICAL))
  840         self._page_sizers[index].Add(self._top_sizers[index], 1, wx.ALL|wx.EXPAND, 0)
  841 
  842         # Add the page to the top sizer.
  843         self._top_sizers[index].Add(panel, 1, wx.ALL|wx.EXPAND, 0)
  844 
  845         # Initialise all box sizers for the buttons, and store them.
  846         self._button_sizers.append(wx.BoxSizer(wx.HORIZONTAL))
  847 
  848         # Add the sizer for the wizard buttons.
  849         self._page_sizers[index].Add(self._button_sizers[index], 0, wx.ALIGN_RIGHT|wx.ALL, 0)
  850 
  851         # Store all button flags.
  852         self._button_apply_flag.append(apply_button)
  853         self._button_skip_flag.append(skip_button)
  854 
  855         # Initialise the button storage.
  856         self._buttons.append({'back': None,
  857                               'apply': None,
  858                               'next': None,
  859                               'ok': None,
  860                               'finish': None,
  861                               'cancel': None})
  862 
  863         # Initialise a set of unique button IDs.
  864         self._button_ids.append({'back': -1,
  865                                  'apply': -1,
  866                                  'next': -1,
  867                                  'ok': -1,
  868                                  'finish': -1,
  869                                  'cancel': -1})
  870 
  871         # Execute on next by default.
  872         self._exec_on_next.append(exec_on_next)
  873 
  874         # Execution count.
  875         self._exec_count.append(0)
  876 
  877         # Proceed to next page on errors by default.
  878         self._proceed_on_error.append(proceed_on_error)
  879 
  880         # User function flushing of the GUI interpreter thread prior to proceeding.
  881         if not proceed_on_error or uf_flush:
  882             self._uf_flush.append(True)
  883         else:
  884             self._uf_flush.append(False)
  885 
  886         # Page sequence initialisation.
  887         self._seq_fn_list.append(self._next_fn)
  888         self._seq_next.append(None)
  889         self._seq_prev.append(None)
  890 
  891         # Page skipping.
  892         self._skip_flag.append(False)
  893 
  894         # Store the index of the page.
  895         panel.page_index = index
  896 
  897         # Return the index of the page.
  898         return index
  899 
  900 
  901     def block_next(self, block=True):
  902         """Prevent moving forwards (or unblock).
  903 
  904         @keyword block: A flag which if True will block forwards movement and if False will unblock.
  905         @type block:    bool
  906         """
  907 
  908         # The buttons to disable.
  909         buttons = ['next', 'ok', 'finish']
  910 
  911         # Disable or enable the buttons.
  912         for i in range(len(buttons)):
  913             # The button.
  914             button = self._buttons[self._current_page][buttons[i]]
  915             if button == None:
  916                 continue
  917 
  918             # Block.
  919             if block:
  920                 button.Disable()
  921 
  922             # Unblock.
  923             else:
  924                 button.Enable()
  925 
  926 
  927     def get_page(self, index):
  928         """Get a page from the wizard.
  929 
  930         @param index:   The index of the page.
  931         @type index:    int
  932         @return:        The page object.
  933         @rtype:         Wiz_page instance.
  934         """
  935 
  936         # Return the page.
  937         return self._pages[index]
  938 
  939 
  940     def reset(self):
  941         """Reset the wizard."""
  942 
  943         # Clear the execution counts.
  944         for i in range(len(self._exec_count)):
  945             self._exec_count[i] = 0
  946 
  947 
  948     def run(self, modal=False):
  949         """Execute the wizard.
  950 
  951         @keyword modal: A flag which if True will cause the wizard to be run as a modal dialog.
  952         @type modal:    bool
  953         @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.
  954         @rtype:         bool or None
  955         """
  956 
  957         # Check that all pages have been set up correctly, returning without doing anything if not.
  958         for i in range(self._num_pages):
  959             if self._pages[i].setup_fail:
  960                 return
  961 
  962         # Build the buttons for the entire wizard.
  963         if not self._buttons_built:
  964             self._build_buttons()
  965 
  966         # Display the first page.
  967         self._display_page(0)
  968 
  969         # Display failure.
  970         if self._pages[0].setup_fail:
  971             return
  972 
  973         # No GUI.
  974         if not status.show_gui:
  975             return
  976 
  977         # Modal operation.
  978         if modal:
  979             # Show the wizard (it should be closed by the _cancel() or _ok() methods).
  980             wiz_status = self.ShowModal()
  981 
  982             # Return the status.
  983             return wiz_status
  984 
  985         # Modeless operation.
  986         else:
  987             # Show the wizard.
  988             self.Show()
  989 
  990 
  991     def set_seq_next_fn(self, index, fn):
  992         """A user specified function for non-linear page changing.
  993 
  994         @param index:   The index of the page the function should be associated with.
  995         @type index:    int
  996         @param fn:      The function for determining the page after the current.  This function should return the index of the next page.
  997         @type fn:       func or method.
  998         """
  999 
 1000         # Store the function.
 1001         self._seq_fn_list[index] = fn
 1002 
 1003 
 1004     def setup_page(self, page=None, **kargs):
 1005         """Allow a specified user function page to be remotely set up.
 1006 
 1007         @keyword page:  The page to setup.  This is the page index key.
 1008         @type page:     str
 1009         """
 1010 
 1011         # Get the page.
 1012         page = self.get_page(self.page_indices[page])
 1013 
 1014         # Loop over the keyword arguments and set them.
 1015         for arg in kargs:
 1016             # The value.
 1017             value = kargs[arg]
 1018             if isinstance(value, str):
 1019                 value = str_to_gui(value)
 1020             elif is_float(value):
 1021                 value = float_to_gui(value)
 1022 
 1023             # Set the argument.
 1024             page.SetValue(arg, value)