"Fossies" - the Fresh Open Source Software Archive

Member "relax-5.0.0/gui/uf_objects.py" (24 Aug 2020, 42160 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 "uf_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-2013,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 """Module containing the special objects for auto-generating the GUI user functions and classes."""
   24 
   25 # Python module imports.
   26 from re import search
   27 import wx
   28 from wx import FD_OPEN, FD_SAVE
   29 from wx.lib import scrolledpanel
   30 import sys
   31 
   32 # relax module imports.
   33 import dep_check
   34 import lib.arg_check
   35 from graphics import fetch_icon
   36 from gui.components.free_file_format import Free_file_format
   37 from gui.components.menu import build_menu_item
   38 from gui.errors import gui_raise
   39 from gui.fonts import font
   40 from gui.input_elements.bool import Selector_bool
   41 from gui.input_elements.dir import Selector_dir
   42 from gui.input_elements.file import Selector_file, Selector_file_multiple
   43 from gui.input_elements.sequence import Sequence
   44 from gui.input_elements.sequence_2D import Sequence_2D
   45 from gui.input_elements.spin_id import Spin_id
   46 from gui.input_elements.value import Value
   47 from gui.interpreter import Interpreter; interpreter = Interpreter()
   48 from gui.misc import format_table
   49 from gui.wizards.wiz_objects import Wiz_page, Wiz_window
   50 from lib.errors import AllRelaxErrors, RelaxError
   51 from lib.text.string import strip_lead
   52 from status import Status; status = Status()
   53 from user_functions.data import Uf_info; uf_info = Uf_info()
   54 from user_functions.data import Uf_tables; uf_tables = Uf_tables()
   55 
   56 
   57 def build_uf_menus(parent=None, menubar=None):
   58     """Auto-generate the user function sub-menu.
   59 
   60     @keyword parent:    The parent window to bind the events to.
   61     @type parent:       wx instance
   62     @keyword menubar:   The menubar to attach the user function menus to.
   63     @type menubar:      wx.MenuBar instance
   64     @return:            The menu ID.
   65     @rtype:             int
   66     """
   67 
   68     # The user function menus.
   69     menu1 = wx.Menu()
   70     menu2 = wx.Menu()
   71 
   72     # The menu splitting.
   73     pattern = '^[a-m]'
   74 
   75     # Initialise some variables.
   76     class_list = []
   77     uf_store = Uf_storage()
   78 
   79     # Loop over the user functions.
   80     class_item = None
   81     menu = menu1
   82     menu_index = 0
   83     for name, data in uf_info.uf_loop():
   84         # Split up the name.
   85         if search('\.', name):
   86             class_name, uf_name = name.split('.')
   87         else:
   88             class_name = None
   89 
   90         # Generate a sub menu.
   91         if class_name:
   92             if class_name not in class_list:
   93                 # Add the last sub menu.
   94                 if class_item != None:
   95                     if dep_check.wx_classic:
   96                         menu.AppendItem(class_item)
   97                     else:
   98                         menu.Append(class_item)
   99 
  100                 # Get the user function class data object.
  101                 class_data = uf_info.get_class(class_name)
  102 
  103                 # Create the menu entry.
  104                 class_item = build_menu_item(menu, id=-1, text=class_data.menu_text, icon=fetch_icon(class_data.gui_icon, size='16x16'), append=False)
  105 
  106                 # Initialise the sub menu.
  107                 sub_menu = wx.Menu()
  108                 class_item.SetSubMenu(sub_menu)
  109 
  110                 # Add the class name to the list to block further sub menu creation.
  111                 class_list.append(class_name)
  112 
  113             # Create the user function menu entry.
  114             build_menu_item(sub_menu, id=uf_store[name]._uf_id, text=data.menu_text, icon=fetch_icon(data.gui_icon, size='16x16'))
  115 
  116         # No sub menu.
  117         else:
  118             # Add the last sub menu.
  119             if class_item != None:
  120                 if dep_check.wx_classic:
  121                     menu.AppendItem(class_item)
  122                 else:
  123                     menu.Append(class_item)
  124                 class_item = None
  125 
  126             # The menu item.
  127             build_menu_item(menu, id=uf_store[name]._uf_id, text=data.menu_text, icon=fetch_icon(data.gui_icon, size='16x16'), append=True)
  128 
  129         # New menu.
  130         if menu_index == 0 and not search(pattern, name):
  131             menu = menu2
  132             menu_index = 1
  133 
  134         # Bind the menu item to the parent.
  135         parent.Bind(wx.EVT_MENU, parent.uf_call, id=uf_store[name]._uf_id)
  136 
  137     # Add the very last sub menu.
  138     if class_item != None:
  139         if dep_check.wx_classic:
  140             menu.AppendItem(class_item)
  141         else:
  142             menu.Append(class_item)
  143 
  144     # Add the user function menu to the menu bar.
  145     title1 = "&User functions (a-m)"
  146     title2 = "&User functions (n-z)"
  147     menubar.Append(menu1, title1)
  148     menubar.Append(menu2, title2)
  149 
  150     # Return the menu IDs.
  151     return [menubar.FindMenu(title1), menubar.FindMenu(title2)]
  152 
  153 
  154 
  155 class Force_true(object):
  156     """A special user function arg element which always returns True."""
  157 
  158     def __init__(self):
  159         """Initialise the object."""
  160 
  161         # Default to always being True.
  162         self._value = True
  163 
  164 
  165     def GetValue(self):
  166         """Simple method for returning the internal value."""
  167 
  168         # Return the stored value.
  169         return self._value
  170 
  171 
  172     def SetValue(self, value):
  173         """Internally store the value being set."""
  174 
  175         # Store the value.
  176         self._value = value
  177 
  178 
  179 
  180 class Uf_object(object):
  181     """The object for auto-generating the GUI user functions."""
  182 
  183     def __call__(self, event=None, wx_parent=None, wx_wizard_sync=None, wx_wizard_run=True, wx_wizard_modal=False, **kwds):
  184         """Make the GUI user function executable.
  185 
  186         All keyword args, apart from 'event', 'wx_parent' and 'wx_wizard_run' will be assumed to be user function arguments and the Uf_page.SetValue() method of the page will be used to set the GUI arg elements to the values supplied.
  187 
  188 
  189         @keyword event:             The wx event.
  190         @type event:                wx event or None
  191         @keyword wx_parent:         The parent wx object to associate the user function wizard to.
  192         @type wx_parent:            wx object
  193         @keyword wx_wizard_sync:    A flag which if given will switch between synchronous and asynchronous user function operation.
  194         @type wx_wizard_sync:       None or bool
  195         @keyword wx_wizard_run:     A flag which if True will call the wizard run() method.
  196         @type wx_wizard_run:        bool
  197         @keyword wx_wizard_modal:   A flag which if True will cause the wizard run() method to have the modal flag set so that the wizard is modal.
  198         @type wx_wizard_modal:      bool
  199         @return:                    The status of the call.  If the call failed, False will be returned.
  200         @rtype:                     bool
  201         """
  202 
  203         # Store the sync flag.
  204         if wx_wizard_sync != None:
  205             self._sync = wx_wizard_sync
  206 
  207         # Create a new wizard if needed (checking that the parent of an old wizard is not the same).
  208         if self.wizard == None or (wx_parent != None and wx_parent != self.wizard.GetParent()) or len(self.wizard._pages) == 0:
  209             status = self.create_wizard(wx_parent)
  210             if not status:
  211                 return False
  212 
  213         # Otherwise reset the wizard.
  214         else:
  215             self.wizard.reset()
  216 
  217         # Update all of the user function argument choices (ComboBoxes) to be current, returning if a failure occurred.
  218         if not self.page.update_args():
  219             return False
  220 
  221         # Loop over the keyword args, using the Uf_page.SetValue() method to set the user function argument GUI element values.
  222         for key in kwds:
  223             self.page.SetValue(key, kwds[key])
  224 
  225         # Execute the wizard when asked.
  226         if wx_wizard_run:
  227             self.wizard.run(modal=wx_wizard_modal)
  228 
  229 
  230     def __init__(self, name, title=None, size=None, height_desc=None, apply_button=True, sync=False):
  231         """Set up the object.
  232 
  233         @param name:            The name of the user function.
  234         @type name:             str
  235         @keyword title:         The long title of the user function to set as the window title.
  236         @type title:            str
  237         @keyword size:          The window size.
  238         @type size:             tuple of int
  239         @keyword height_desc:   The height in pixels of the description part of the wizard.
  240         @type height_desc:      int or None
  241         @keyword apply_button:  A flag specifying if the apply button should be shown or not.  This defaults to True.
  242         @type apply_button:     bool
  243         @keyword sync:          A flag which if True will call user functions via interpreter.apply and if False via interpreter.queue.
  244         @type sync:             bool
  245         """
  246 
  247         # Store the args.
  248         self._name = name
  249         self._title = title
  250         self._size = size
  251         self._height_desc = height_desc
  252         self._apply_button = apply_button
  253         self._sync = sync
  254 
  255         # Initialise the wizard storage.
  256         self.wizard = None
  257 
  258         # Create a unique wx ID for the user function.
  259         self._uf_id = wx.NewId()
  260 
  261 
  262     def Destroy(self):
  263         """Cleanly destroy the user function GUI elements."""
  264 
  265         # First flush all events.
  266         wx.Yield()
  267 
  268         # Destroy the user function page.
  269         if hasattr(self, 'page'):
  270             # Loop over the user function arguments.
  271             for key in self.page.uf_args:
  272                 # Destroy any selection windows.
  273                 if hasattr(self.page.uf_args[key], 'sel_win'):
  274                     self.page.uf_args[key].sel_win.Destroy()
  275 
  276             # Delete the page object.
  277             del self.page
  278 
  279         # Destroy the wizard, if it exists.
  280         if self.wizard != None:
  281             self.wizard.Destroy()
  282             self.wizard = None
  283 
  284 
  285     def create_page(self, wizard=None, sync=None, execute=True):
  286         """Create the user function wizard page GUI object.
  287 
  288         @keyword wizard:    The parent wizard.
  289         @type wizard:       Wiz_window instance
  290         @keyword sync:      A flag which if True will call user functions via interpreter.apply and if False via interpreter.queue.
  291         @type sync:         None or bool
  292         @keyword execute:   A flag which if True will prevent the user function from being executed when clicking on 'Next', 'Ok', or 'Apply'.  This can be useful for delaying the execution of the user function.
  293         @type execute:      bool
  294         @return:            The user function page object.
  295         @rtype:             Uf_page instance
  296         """
  297 
  298         # Overwrite (a)synchronous operation.
  299         if sync != None:
  300             self._sync = sync
  301 
  302         # Initialise and return the page.
  303         return Uf_page(self._name, parent=wizard, height_desc=self._height_desc, sync=self._sync, execute=execute)
  304 
  305 
  306     def create_wizard(self, parent=None):
  307         """Create the user function wizard GUI object, with embedded wizard page.
  308 
  309         @keyword parent:    The parent wx window.
  310         @type parent:       wx.Window instance
  311         @return:            True if the wizard was created, False if a problem was encountered.
  312         @rtype:             bool
  313         """
  314 
  315         # The parent object defaults to the main relax window.
  316         if parent == None:
  317             app = wx.GetApp()
  318             parent = app.gui
  319 
  320         # Create the wizard dialog.
  321         self.wizard = Wiz_window(parent=parent, size_x=self._size[0], size_y=self._size[1], title="The %s user function"%self._name)
  322 
  323         # Create the page.
  324         self.page = self.create_page(self.wizard, sync=self._sync)
  325 
  326         # For an update of the argument data.
  327         if not self.page.update_args():
  328             return False
  329 
  330         # Add the page to the wizard.
  331         self.wizard.add_page(self.page, apply_button=self._apply_button)
  332 
  333         # Success.
  334         return True
  335 
  336 
  337 
  338 class Uf_page(Wiz_page):
  339     """User function specific pages for the wizards."""
  340 
  341     def __init__(self, name, parent=None, height_desc=220, sync=False, execute=True):
  342         """Set up the window.
  343 
  344         @param name:            The name of the user function.
  345         @type name:             str
  346         @keyword parent:        The parent class containing the GUI.
  347         @type parent:           class instance
  348         @keyword height_desc:   The height in pixels of the description part of the wizard.
  349         @type height_desc:      int or None
  350         @keyword sync:          A flag which if True will call user functions via interpreter.apply and if False via interpreter.queue.
  351         @type sync:             bool
  352         @keyword execute:       A flag which if True will prevent the user function from being executed when clicking on 'Next', 'Ok', or 'Apply'.  This can be useful for delaying the execution of the user function.
  353         @type execute:          bool
  354         """
  355 
  356         # Store the args.
  357         self.name = name
  358         self.sync = sync
  359         self.execute_flag = execute
  360 
  361         # Storage of the user function argument elements.
  362         self.uf_args = {}
  363 
  364         # Yield to allow the cursor to be changed.
  365         wx.Yield()
  366 
  367         # Change the cursor to waiting.
  368         wx.BeginBusyCursor()
  369 
  370         # Get the user function data object.
  371         self.uf_data = uf_info.get_uf(name)
  372 
  373         # Set the wizard image.
  374         self.image_path = self.uf_data.wizard_image
  375 
  376         # Set the user function title.
  377         if self.uf_data.title_short != None:
  378             self.title = self.uf_data.title_short
  379         else:
  380             self.title = self.uf_data.title
  381 
  382         # Execute the base class method.
  383         super(Uf_page, self).__init__(parent, height_desc=height_desc)
  384 
  385         # Reset the cursor.
  386         if wx.IsBusy():
  387             wx.EndBusyCursor()
  388 
  389 
  390     def _format_text(self, text):
  391         """Format the text by stripping whitespace.
  392 
  393         @param text:    The text to strip.
  394         @type text:     str
  395         @return:        The stripped text.
  396         @rtype:         str
  397         """
  398 
  399         # First strip whitespace.
  400         stripped_text = strip_lead(text)
  401 
  402         # Remove the first characters if newlines.
  403         while True:
  404             if stripped_text[0] == "\n":
  405                 stripped_text = stripped_text[1:]
  406             else:
  407                 break
  408 
  409         # Remove the last character if a newline.
  410         while True:
  411             if stripped_text[-1] == "\n":
  412                 stripped_text = stripped_text[:-1]
  413             else:
  414                 break
  415 
  416         # Return the text.
  417         return stripped_text
  418 
  419 
  420     def _intro_text(self, keys, values, prompt=True):
  421         """Build and return the user function intro text.
  422 
  423         @param keys:        The user function keys.
  424         @type keys:         list of str
  425         @param values:      The values corresponding to the keys.
  426         @type values:       list
  427         @keyword prompt:    A flag which if True will cause the prompt text to be included.
  428         @type prompt:       bool
  429         @return:            The user function intro text.
  430         @rtype:             str
  431         """
  432 
  433         # Initialise.
  434         text = ""
  435 
  436         # The prompt.
  437         if prompt:
  438             text += status.ps3
  439 
  440         # The user function name.
  441         text += "%s(" % self.name
  442 
  443         # The keyword args.
  444         for i in range(len(keys)):
  445             # Comma separation.
  446             if i >= 1:
  447                 text += ", "
  448 
  449             # Add the arg.
  450             text += "%s=%s" % (keys[i], repr(values[i]))
  451 
  452         # The end.
  453         text += ")"
  454 
  455         # Return the text.
  456         return text
  457 
  458 
  459     def Clear(self, key):
  460         """Special wizard method for clearing the value of the GUI element corresponding to the key.
  461 
  462         @param key:     The key corresponding to the desired GUI element.
  463         @type key:      str
  464         """
  465 
  466         # Call the argument element's method.
  467         self.uf_args[key].Clear()
  468 
  469 
  470     def GetValue(self, key):
  471         """Special wizard method for getting the value of the GUI element corresponding to the key.
  472 
  473         @param key:     The key corresponding to the desired GUI element.
  474         @type key:      str
  475         @return:        The value that the specific GUI element's GetValue() method returns.
  476         @rtype:         unknown
  477         """
  478 
  479         # The key is not set, so assume this is a hidden argument.
  480         if key not in self.uf_args:
  481             return None
  482 
  483         # Call the argument element's method.
  484         return self.uf_args[key].GetValue()
  485 
  486 
  487     def SetValue(self, key, value):
  488         """Special wizard method for setting the value of the GUI element corresponding to the key.
  489 
  490         @param key:     The key corresponding to the desired GUI element.
  491         @type key:      str
  492         @param value:   The value that the specific GUI element's SetValue() method expects.
  493         @type value:    unknown
  494         """
  495 
  496         # Find the argument.
  497         arg = None
  498         for i in range(len(self.uf_data.kargs)):
  499             if self.uf_data.kargs[i]['name'] == key:
  500                 arg = self.uf_data.kargs[i]
  501 
  502         # No match.
  503         if arg == None:
  504             raise RelaxError("The key '%s' is unknown." % key)
  505 
  506         # Handle the free file format args (for external control, i.e. via the test suite).
  507         if 'free_file_format' in self.uf_args and key in ['spin_id_col', 'mol_name_col', 'res_num_col', 'res_name_col', 'spin_num_col', 'spin_name_col', 'data_col', 'error_col', 'sep']:
  508             self.uf_args['free_file_format'].SetValue(key, value)
  509 
  510         # Skip functions and function args, as these are not supported in the GUI.
  511         elif arg['arg_type'] in ['func', 'func args']:
  512             pass
  513 
  514         # Call the argument element's method.
  515         else:
  516             self.uf_args[key].SetValue(value)
  517 
  518 
  519     def UpdateChoices(self, key, combo_choices=None, combo_data=None, combo_default=None):
  520         """Special user function page method for updating the list of choices in a ComboBox type element.
  521 
  522         @param key:             The key corresponding to the desired GUI element.
  523         @type key:              str
  524         @keyword combo_choices: The list of choices to present to the user.  This is only used if the element_type is set to 'combo'.
  525         @type combo_choices:    list of str
  526         @keyword combo_data:    The data returned by a call to GetValue().  This is only used if the element_type is set to 'combo'.  If supplied, it should be the same length at the combo_choices list.  If not supplied, the combo_choices list will be used for the returned data.
  527         @type combo_data:       list
  528         @keyword combo_default: The default value of the ComboBox.  This is only used if the element_type is set to 'combo'.
  529         @type combo_default:    str or None
  530         """
  531 
  532         # Call the argument element's method.
  533         self.uf_args[key].UpdateChoices(combo_choices=combo_choices, combo_data=combo_data, combo_default=combo_default)
  534 
  535 
  536     def add_contents(self, sizer):
  537         """Add the specific GUI elements.
  538 
  539         @param sizer:   A sizer object.
  540         @type sizer:    wx.Sizer instance
  541         """
  542 
  543         # Initialise the free format file settings flag.
  544         free_format = False
  545         free_format_data = False
  546 
  547         # Loop over the arguments.
  548         for i in range(len(self.uf_data.kargs)):
  549             # Alias.
  550             arg = self.uf_data.kargs[i]
  551 
  552             # The arg description formatting.
  553             desc = "The %s:" % arg['desc_short']
  554 
  555             # Dimensions.
  556             dim = arg['dim']
  557             single_value = False
  558             if isinstance(dim, list):
  559                 dim = ()
  560                 for i in range(len(arg['dim'])):
  561                     if arg['dim'][i] == ():
  562                         single_value = True
  563                     if len(dim) == len(arg['dim']) and dim[0] < arg['dim']:
  564                         dim = arg['dim'][i]
  565                     elif len(dim) < len(arg['dim']):
  566                         dim = arg['dim'][i]
  567             if not dim and 'all' in arg['container_types']:
  568                 dim = ()
  569 
  570             # Special arg type:  file selection dialog.
  571             if arg['arg_type'] in ['file sel read', 'file sel write']:
  572                 if arg['arg_type'] == 'file sel read':
  573                     style = FD_OPEN
  574                 if arg['arg_type'] == 'file sel write':
  575                     style = FD_SAVE
  576                 self.uf_args[arg['name']] = Selector_file(name=arg['name'], parent=self, default=arg['default'], sizer=sizer, desc=desc, wildcard=arg['wiz_filesel_wildcard'], style=style, tooltip=arg['desc'], divider=self._div_left, height_element=self.height_element, preview=arg['wiz_filesel_preview'], read_only=arg['wiz_read_only'])
  577 
  578             # Special arg type:  multiple file selection dialog.
  579             elif arg['arg_type'] in ['file sel multi read', 'file sel multi write']:
  580                 if arg['arg_type'] == 'file sel multi read':
  581                     style = FD_OPEN
  582                 if arg['arg_type'] == 'file sel multi write':
  583                     style = FD_SAVE
  584                 self.uf_args[arg['name']] = Selector_file_multiple(name=arg['name'], parent=self, default=arg['default'], sizer=sizer, desc=desc, wildcard=arg['wiz_filesel_wildcard'], style=style, tooltip=arg['desc'], divider=self._div_left, height_element=self.height_element, preview=arg['wiz_filesel_preview'], read_only=arg['wiz_read_only'])
  585 
  586             # Special arg type:  dir arg.
  587             elif arg['arg_type'] == 'dir':
  588                 pass
  589 
  590             # Special arg type:  directory selection dialog.
  591             elif arg['arg_type'] == 'dir sel':
  592                 self.uf_args[arg['name']] = Selector_dir(name=arg['name'], parent=self, default=arg['default'], sizer=sizer, desc=desc, style=arg['wiz_dirsel_style'], tooltip=arg['desc'], divider=self._div_left, height_element=self.height_element, read_only=arg['wiz_read_only'])
  593 
  594             # Special arg type:  free format file settings.
  595             elif arg['arg_type'] == 'free format':
  596                 # Switch the flags.
  597                 free_format = True
  598                 if arg['name'] == 'data_col':
  599                     free_format_data = True
  600 
  601             # Special arg type:  functions and their arguments!
  602             elif arg['arg_type'] in ['func', 'func args']:
  603                 pass
  604 
  605             # Special arg type:  force flags.
  606             elif arg['arg_type'] in ['force flag']:
  607                 self.uf_args[arg['name']] = Force_true()
  608 
  609             # Special arg type:  spin IDs.
  610             elif arg['arg_type'] in ['spin ID']:
  611                 self.uf_args[arg['name']] = Spin_id(name=arg['name'], parent=self, default=arg['default'], element_type=arg['wiz_element_type'], sizer=sizer, desc=desc, combo_choices=arg['wiz_combo_choices'], combo_data=arg['wiz_combo_data'], tooltip=arg['desc'], divider=self._div_left, height_element=self.height_element, can_be_none=arg['can_be_none'])
  612 
  613             # Value types.
  614             elif len(dim) == 0 and ('all' in arg['basic_types'] or 'float' in arg['basic_types'] or 'int' in arg['basic_types'] or 'number' in arg['basic_types'] or 'str' in arg['basic_types']):
  615                 value_type = arg['basic_types'][0]
  616                 if value_type == 'number':
  617                     value_type = 'float'
  618                 elif value_type == 'all':
  619                     value_type = 'float'
  620                 self.uf_args[arg['name']] = Value(name=arg['name'], parent=self, default=arg['default'], element_type=arg['wiz_element_type'], value_type=value_type, min=arg['min'], max=arg['max'], sizer=sizer, desc=desc, combo_choices=arg['wiz_combo_choices'], combo_data=arg['wiz_combo_data'], tooltip=arg['desc'], divider=self._div_left, height_element=self.height_element, read_only=arg['wiz_read_only'], can_be_none=arg['can_be_none'])
  621 
  622             # Bool type.
  623             elif len(dim) == 0 and 'bool' in arg['basic_types']:
  624                 self.uf_args[arg['name']] = Selector_bool(name=arg['name'], parent=self, element_type=arg['wiz_element_type'], sizer=sizer, desc=desc, tooltip=arg['desc'], default=arg['default'], divider=self._div_left, height_element=self.height_element)
  625 
  626             # Sequence types.
  627             elif len(dim) == 1:
  628                 # The sequence type.
  629                 if 'list' in arg['container_types'] or 'all' in arg['container_types']:
  630                     seq_type = 'list'
  631                 else:
  632                     seq_type = 'tuple'
  633 
  634                 # The value type.
  635                 if 'float' in arg['basic_types'] or 'number' in arg['basic_types']:
  636                     value_type = 'float'
  637                 elif 'int' in arg['basic_types']:
  638                     value_type = 'int'
  639                 elif 'str' in arg['basic_types']:
  640                     value_type = 'str'
  641                 else:
  642                     value_type = None
  643 
  644                 # Dim conversion.
  645                 if dim == (None,):
  646                     dim = None
  647 
  648                 self.uf_args[arg['name']] = Sequence(name=arg['name'], parent=self, default=arg['default'], element_type=arg['wiz_element_type'], seq_type=seq_type, value_type=value_type, dim=dim, min=arg['min'], max=arg['max'], titles=arg['list_titles'], sizer=sizer, desc=desc, combo_choices=arg['wiz_combo_choices'], combo_data=arg['wiz_combo_data'], combo_list_min=arg['wiz_combo_list_min'], tooltip=arg['desc'], single_value=single_value, divider=self._div_left, height_element=self.height_element, read_only=arg['wiz_read_only'], can_be_none=arg['can_be_none'])
  649 
  650             # 2D sequence types.
  651             elif len(dim) == 2:
  652                 # The sequence type.
  653                 if 'list' in arg['container_types']:
  654                     seq_type = 'list'
  655                 else:
  656                     seq_type = 'tuple'
  657 
  658                 # The value type.
  659                 if 'float' in arg['basic_types'] or 'number' in arg['basic_types']:
  660                     value_type = 'float'
  661                 elif 'int' in arg['basic_types']:
  662                     value_type = 'int'
  663                 elif 'str' in arg['basic_types']:
  664                     value_type = 'str'
  665                 else:
  666                     value_type = None
  667 
  668                 self.uf_args[arg['name']] = Sequence_2D(name=arg['name'], parent=self, default=arg['default'], sizer=sizer, element_type=arg['wiz_element_type'], seq_type=seq_type, value_type=value_type, dim=dim, min=arg['min'], max=arg['max'], titles=arg['list_titles'], desc=desc, combo_choices=arg['wiz_combo_choices'], combo_data=arg['wiz_combo_data'], combo_list_min=arg['wiz_combo_list_min'], tooltip=arg['desc'], divider=self._div_left, height_element=self.height_element, read_only=arg['wiz_read_only'], can_be_none=arg['can_be_none'])
  669 
  670             # Unknown type.
  671             else:
  672                 raise RelaxError("The Python object with basic_types=%s, container_types=%s, dim=%s cannot be handled." % (arg['basic_types'], arg['container_types'], arg['dim']))
  673 
  674         # Add the free format element.
  675         if free_format:
  676             self.uf_args['free_file_format'] = Free_file_format(parent=self, sizer=sizer, element_type='mini', data_cols=free_format_data, divider=self._div_left, height_element=self.height_element, padding=0, spacer=None)
  677 
  678 
  679     def add_desc(self, sizer, max_y=220):
  680         """Add the description to the dialog.
  681 
  682         @param sizer:   A sizer object.
  683         @type sizer:    wx.Sizer instance
  684         @keyword max_y: The maximum height, in number of pixels, for the description.
  685         @type max_y:    int
  686         """
  687 
  688         # Initialise.
  689         spacing = 15
  690 
  691         # A line with spacing.
  692         sizer.AddSpacer(5)
  693         sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
  694         sizer.AddSpacer(5)
  695 
  696         # Create a scrolled panel.
  697         panel = scrolledpanel.ScrolledPanel(self, -1, name="desc")
  698 
  699         # A sizer for the panel.
  700         panel_sizer = wx.BoxSizer(wx.VERTICAL)
  701 
  702         # Initialise the text elements.
  703         tot_y = 0
  704         text_elements = []
  705         text_types = []
  706 
  707         # The user function name.
  708         name = "The %s user function" % self.name
  709         text = wx.StaticText(panel, -1, name, style=wx.TE_MULTILINE)
  710         text.SetFont(font.subtitle)
  711         text_elements.append(text)
  712         text_types.append('title')
  713 
  714         # The text size, then spacing after the title.
  715         x, y = text.GetSize()
  716         tot_y += y
  717         tot_y += spacing
  718 
  719         # The synopsis.
  720         if self.uf_data.title:
  721             # The text.
  722             text = wx.StaticText(panel, -1, self.uf_data.title, style=wx.TE_MULTILINE)
  723 
  724             # Formatting.
  725             text.SetFont(font.normal_italic)
  726 
  727             # The text size.
  728             x, y = text.GetSize()
  729             tot_y += y
  730 
  731             # The spacing after the element.
  732             tot_y += spacing
  733 
  734             # Append the text objects.
  735             text_elements.append(text)
  736             text_types.append('synopsis')
  737 
  738         # The description sections.
  739         if self.uf_data.desc != None:
  740             # Loop over the sections.
  741             for i in range(len(self.uf_data.desc)):
  742                 # Alias.
  743                 desc = self.uf_data.desc[i]
  744 
  745                 # Skip the prompt examples.
  746                 if desc.get_title() == 'Prompt examples':
  747                     continue
  748 
  749                 # Loop over the text elements.
  750                 for type, element in desc.element_loop(title=True):
  751                     # The text version of the elements.
  752                     text = ''
  753                     if isinstance(element, str):
  754                         text = element
  755 
  756                     # Format the tables.
  757                     if type == 'table':
  758                         text = format_table(uf_tables.get_table(element))
  759 
  760                     # Format the lists.
  761                     elif type == 'list':
  762                         # Loop over the list elements.
  763                         for j in range(len(element)):
  764                             text += "    - %s\n" % element[j]
  765 
  766                         # Remove the last newline character.
  767                         text = text[:-1]
  768 
  769                     # Format the item lists.
  770                     elif type == 'item list':
  771                         # Loop over the list elements.
  772                         for j in range(len(element)):
  773                             # No item.
  774                             if element[j][0] in [None, '']:
  775                                 text += "    %s\n" % element[j][1]
  776                             else:
  777                                 text += "    %s:  %s\n" % (element[j][0], element[j][1])
  778 
  779                         # Remove the last newline character.
  780                         text = text[:-1]
  781 
  782                     # Format prompt items.
  783                     elif type == 'prompt':
  784                         for j in range(len(element)):
  785                             text += "%s\n" % element[j]
  786 
  787                         # Remove the last newline character.
  788                         text = text[:-1]
  789 
  790                     # The text object.
  791                     text_obj = wx.StaticText(panel, -1, text, style=wx.TE_MULTILINE)
  792 
  793                     # Format.
  794                     if type == 'title':
  795                         text_obj.SetFont(font.subtitle)
  796                     elif type == 'paragraph':
  797                         text_obj.SetFont(font.normal)
  798                     elif type in ['table', 'verbatim', 'prompt']:
  799                         text_obj.SetFont(font.modern_small)
  800                     else:
  801                         text_obj.SetFont(font.normal)
  802 
  803                     # Wrap the paragraphs and lists (with spacing for scrollbars).
  804                     if type in ['paragraph', 'list', 'item list']:
  805                         text_obj.Wrap(self._main_size - 20)
  806 
  807                     # The text size.
  808                     x, y = text_obj.GetSize()
  809                     tot_y += y
  810 
  811                     # The spacing after each element (except the last).
  812                     tot_y += spacing
  813 
  814                     # The spacing before each section (not including the first).
  815                     if i != 0 and type == 'title':
  816                         tot_y += spacing
  817 
  818                     # Append the text objects.
  819                     text_elements.append(text_obj)
  820                     text_types.append(type)
  821 
  822         # Some extra space for who knows what?!
  823         tot_y -= spacing
  824         tot_y += 20
  825 
  826         # Set the panel size - scrolling needed.
  827         if tot_y > max_y:
  828             panel.SetInitialSize((self._main_size, max_y))
  829 
  830         # Set the panel size - no scrolling.
  831         else:
  832             panel.SetInitialSize((self._main_size, tot_y))
  833 
  834         # Add the text.
  835         n = len(text_elements)
  836         for i in range(n):
  837             # Spacing before each section (not including the first).
  838             if i > 1 and text_types[i] == 'title':
  839                 panel_sizer.AddSpacer(spacing)
  840 
  841             # The text.
  842             panel_sizer.Add(text_elements[i], 0, wx.ALIGN_LEFT, 0)
  843 
  844             # Spacer after all sections (except the end).
  845             if i != n - 1:
  846                 panel_sizer.AddSpacer(spacing)
  847 
  848         # Set up and add the panel to the sizer.
  849         panel.SetSizer(panel_sizer)
  850         panel.SetAutoLayout(1)
  851         panel.SetupScrolling(scroll_x=False, scroll_y=True)
  852         sizer.Add(panel, 0, wx.ALL|wx.EXPAND)
  853 
  854         # A line with spacing.
  855         sizer.AddSpacer(5)
  856         sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0)
  857         sizer.AddSpacer(5)
  858 
  859 
  860     def execute(self, uf, *args, **kwds):
  861         """Execute the user function, either asynchronously or synchronously.
  862 
  863         @param uf:      The user function as a string.
  864         @type uf:       str
  865         @param args:    The user function arguments.
  866         @type args:     any arguments
  867         @param kwds:    The user function keyword arguments.
  868         @type kwds:     any keyword arguments
  869         """
  870 
  871         # Synchronous execution.
  872         if self.sync or status.gui_uf_force_sync:
  873             return_status = interpreter.apply(uf, *args, **kwds)
  874             return return_status
  875 
  876         # Asynchronous execution.
  877         else:
  878             interpreter.queue(uf, *args, **kwds)
  879             return True
  880 
  881 
  882     def on_back(self):
  883         """Remove this page from the observers."""
  884 
  885         # Unregister this page with the 'gui_uf' observer.
  886         status.observers.gui_uf.unregister(self.name)
  887 
  888 
  889     def on_display(self):
  890         """Clear and update the data if needed."""
  891 
  892         # Register this page with the 'gui_uf' observer so that update_args() is called once the any user function completes.
  893         status.observers.gui_uf.register(self.name, self.update_args, method_name='update_args')
  894 
  895         # Update the args.
  896         return self.update_args()
  897 
  898 
  899     def on_execute(self, force_exec=False):
  900         """Execute the user function.
  901 
  902         @keyword force_exec:    A flag which if True will cause the execution flag to be ignored and the user function to be executed.
  903         @type force_exec:       bool
  904         """
  905 
  906         # Don't execute.
  907         if not force_exec and not self.execute_flag:
  908             return
  909 
  910         # Get the argument values.
  911         kargs = {}
  912         for i in range(len(self.uf_data.kargs)):
  913             # The argument name.
  914             name = self.uf_data.kargs[i]['name']
  915 
  916             # Store the value.
  917             kargs[name] = self.GetValue(name)
  918 
  919             # Skip execution when a Combo_list does not have enough elements.
  920             if self.uf_data.kargs[i]['wiz_combo_list_min'] != None and kargs[name] == None:
  921                 return True
  922 
  923         # Handle the free file format args.
  924         if 'free_file_format' in self.uf_args:
  925             kargs.update(self.uf_args['free_file_format'].GetValue())
  926 
  927         # Display the relax controller, if asked.
  928         if self.uf_data.display:
  929             # Get the App.
  930             app = wx.GetApp()
  931 
  932             # First show the controller.
  933             app.gui.show_controller(None)
  934 
  935             # Go to the last line.
  936             app.gui.controller.log_panel.on_goto_end(None)
  937 
  938         # The user function intro text.
  939         if status.uf_intro:
  940             # Convert the keys and values.
  941             keys = []
  942             values = []
  943             for i in range(len(self.uf_data.kargs)):
  944                 keys.append(self.uf_data.kargs[i]['name'])
  945                 values.append(kargs[self.uf_data.kargs[i]['name']])
  946 
  947             # The printout.
  948             print(self._intro_text(keys, values))
  949 
  950         # User function argument validation.
  951         for i in range(len(self.uf_data.kargs)):
  952             arg = self.uf_data.kargs[i]
  953             try:
  954                 lib.arg_check.validate_arg(kargs[arg['name']], arg['desc_short'], dim=arg['dim'], basic_types=arg['basic_types'], container_types=arg['container_types'], can_be_none=arg['can_be_none'], can_be_empty=arg['can_be_empty'], none_elements=arg['none_elements'])
  955             except AllRelaxErrors:
  956                 # Display a dialog with the error.
  957                 gui_raise(sys.exc_info()[1])
  958 
  959                 # Return as a failure.
  960                 return False
  961 
  962         # Execute the user function.
  963         return_status = self.execute(self.name, **kargs)
  964 
  965         # Bring the controller to the front.
  966         if status.show_gui and self.uf_data.display:
  967             wx.CallAfter(app.gui.controller.Raise)
  968 
  969         # Return the status.
  970         return return_status
  971 
  972 
  973     def on_next(self):
  974         """Remove this page from the observers."""
  975 
  976         # Unregister this page with the 'gui_uf' observer.
  977         status.observers.gui_uf.unregister(self.name)
  978 
  979 
  980     def update_args(self):
  981         """Update all the argument ComboBox choices.
  982 
  983         @return:    The status of the update - False if a RelaxError occurs, True otherwise.
  984         @rtype:     bool
  985         """
  986 
  987         # Loop over the arguments.
  988         for i in range(len(self.uf_data.kargs)):
  989             # The argument name.
  990             name = self.uf_data.kargs[i]['name']
  991 
  992             # No iterator method for updating the list.
  993             iterator = self.uf_data.kargs[i]['wiz_combo_iter']
  994             if iterator == None:
  995                 continue
  996 
  997             # Get the new choices and data (in a safe way).
  998             try:
  999                 choices = []
 1000                 data = []
 1001                 for vals in iterator():
 1002                     if lib.arg_check.is_tuple(vals, size=2, raise_error=False) or lib.arg_check.is_list(vals, size=2, raise_error=False):
 1003                         choices.append(vals[0])
 1004                         data.append(vals[1])
 1005                     else:
 1006                         choices.append(vals)
 1007                         data.append(vals)
 1008 
 1009             # Catch all RelaxErrors.
 1010             except AllRelaxErrors:
 1011                 instance = sys.exc_info()[1]
 1012 
 1013                 # Signal the failure to the wizard.
 1014                 self.setup_fail = True
 1015 
 1016                 # Display a dialog with the error.
 1017                 gui_raise(instance)
 1018 
 1019                 # Return as a failure.
 1020                 return False
 1021 
 1022             # Get the current value, for setting as the default.
 1023             val = self.uf_args[name].GetValue()
 1024 
 1025             # Update the GUI element.
 1026             self.UpdateChoices(name, combo_choices=choices, combo_data=data, combo_default=val)
 1027 
 1028         # Successful update.
 1029         return True
 1030 
 1031 
 1032 
 1033 class Uf_storage(dict):
 1034     """A singleton container for holding all the GUI user functions."""
 1035 
 1036     # Class variable for storing the class instance (for the singleton).
 1037     _instance = None
 1038 
 1039     def __new__(self, *args, **kargs):
 1040         """Replacement method for implementing the singleton design pattern."""
 1041 
 1042         # First instantiation.
 1043         if self._instance is None:
 1044             # Instantiate.
 1045             self._instance = dict.__new__(self, *args, **kargs)
 1046 
 1047             # Generate the user functions.
 1048             for name, data in uf_info.uf_loop():
 1049                 # The title.
 1050                 title = data.title_short
 1051                 if not title:
 1052                     title = data.title
 1053 
 1054                 # Generate a new container.
 1055                 obj = Uf_object(name, title=title, size=data.wizard_size, height_desc=data.wizard_height_desc, apply_button=data.wizard_apply_button, sync=data.gui_sync)
 1056 
 1057                 # Store it.
 1058                 self._instance[name] = obj
 1059 
 1060         # Already instantiated, so return the instance.
 1061         return self._instance
 1062 
 1063 
 1064     def get_uf(self, id=0):
 1065         """Return the name of the user function corresponding to the given wx ID.
 1066 
 1067         @keyword id:    The unique wx ID number.
 1068         @type id:       int
 1069         @return:        The name of the user function.
 1070         @rtype:         str
 1071         """
 1072 
 1073         # Loop over the elements, returning the name when a match occurs.
 1074         for name in self:
 1075             if self[name]._uf_id == id:
 1076                 return name