Package gui :: Module uf_objects
[hide private]
[frames] | no frames]

Source Code for Module gui.uf_objects

   1  ############################################################################### 
   2  #                                                                             # 
   3  # Copyright (C) 2010-2013,2015,2019,2022 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(r'\.', 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 dc = wx.ScreenDC() 716 dc.SetFont(font.subtitle) 717 x, y = dc.GetTextExtent(name) 718 tot_y += y 719 tot_y += spacing 720 721 # The synopsis. 722 if self.uf_data.title: 723 # The text. 724 text = wx.StaticText(panel, -1, self.uf_data.title, style=wx.TE_MULTILINE) 725 726 # Formatting. 727 text.SetFont(font.normal_italic) 728 729 # The text size. 730 dc.SetFont(font.normal_italic) 731 x, y = dc.GetTextExtent(self.uf_data.title) 732 tot_y += y 733 734 # The spacing after the element. 735 tot_y += spacing 736 737 # Append the text objects. 738 text_elements.append(text) 739 text_types.append('synopsis') 740 741 # The description sections. 742 if self.uf_data.desc != None: 743 # Loop over the sections. 744 for i in range(len(self.uf_data.desc)): 745 # Alias. 746 desc = self.uf_data.desc[i] 747 748 # Skip the prompt examples. 749 if desc.get_title() == 'Prompt examples': 750 continue 751 752 # Loop over the text elements. 753 for type, element in desc.element_loop(title=True): 754 # The text version of the elements. 755 text = '' 756 if isinstance(element, str): 757 text = element 758 759 # Format the tables. 760 if type == 'table': 761 text = format_table(uf_tables.get_table(element)) 762 763 # Format the lists. 764 elif type == 'list': 765 # Loop over the list elements. 766 for j in range(len(element)): 767 text += " - %s\n" % element[j] 768 769 # Remove the last newline character. 770 text = text[:-1] 771 772 # Format the item lists. 773 elif type == 'item list': 774 # Loop over the list elements. 775 for j in range(len(element)): 776 # No item. 777 if element[j][0] in [None, '']: 778 text += " %s\n" % element[j][1] 779 else: 780 text += " %s: %s\n" % (element[j][0], element[j][1]) 781 782 # Remove the last newline character. 783 text = text[:-1] 784 785 # Format prompt items. 786 elif type == 'prompt': 787 for j in range(len(element)): 788 text += "%s\n" % element[j] 789 790 # Remove the last newline character. 791 text = text[:-1] 792 793 # The text object. 794 text_obj = wx.StaticText(panel, -1, text, style=wx.TE_MULTILINE) 795 796 # Format. 797 if type == 'title': 798 text_obj.SetFont(font.subtitle) 799 elif type == 'paragraph': 800 text_obj.SetFont(font.normal) 801 elif type in ['table', 'verbatim', 'prompt']: 802 text_obj.SetFont(font.modern_small) 803 else: 804 text_obj.SetFont(font.normal) 805 806 # Wrap the paragraphs and lists (with spacing for scrollbars). 807 if type in ['paragraph', 'list', 'item list']: 808 text_obj.Wrap(self._main_size - 20) 809 810 # The text size. 811 x, y = text_obj.GetSize() 812 tot_y += y 813 814 # The spacing after each element (except the last). 815 tot_y += spacing 816 817 # The spacing before each section (not including the first). 818 if i != 0 and type == 'title': 819 tot_y += spacing 820 821 # Append the text objects. 822 text_elements.append(text_obj) 823 text_types.append(type) 824 825 # Some extra space for who knows what?! 826 tot_y -= spacing 827 tot_y += 20 828 829 # Set the panel size - scrolling needed. 830 if tot_y > max_y: 831 panel.SetInitialSize((self._main_size, max_y)) 832 833 # Set the panel size - no scrolling. 834 else: 835 panel.SetInitialSize((self._main_size, tot_y)) 836 837 # Add the text. 838 n = len(text_elements) 839 for i in range(n): 840 # Spacing before each section (not including the first). 841 if i > 1 and text_types[i] == 'title': 842 panel_sizer.AddSpacer(spacing) 843 844 # The text. 845 panel_sizer.Add(text_elements[i], 0, wx.ALIGN_LEFT, 0) 846 847 # Spacer after all elements (except the end). 848 if i != n - 1: 849 panel_sizer.AddSpacer(spacing) 850 851 # Set up and add the panel to the sizer. 852 panel.SetSizer(panel_sizer) 853 panel.SetAutoLayout(1) 854 panel.SetupScrolling(scroll_x=False, scroll_y=True, scrollToTop=True) 855 sizer.Add(panel, 0, wx.ALL|wx.EXPAND) 856 857 # A line with spacing. 858 sizer.AddSpacer(5) 859 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 0) 860 sizer.AddSpacer(5)
861 862
863 - def execute(self, uf, *args, **kwds):
864 """Execute the user function, either asynchronously or synchronously. 865 866 @param uf: The user function as a string. 867 @type uf: str 868 @param args: The user function arguments. 869 @type args: any arguments 870 @param kwds: The user function keyword arguments. 871 @type kwds: any keyword arguments 872 """ 873 874 # Synchronous execution. 875 if self.sync or status.gui_uf_force_sync: 876 return_status = interpreter.apply(uf, *args, **kwds) 877 return return_status 878 879 # Asynchronous execution. 880 else: 881 interpreter.queue(uf, *args, **kwds) 882 return True
883 884
885 - def on_back(self):
886 """Remove this page from the observers.""" 887 888 # Unregister this page with the 'gui_uf' observer. 889 status.observers.gui_uf.unregister(self.name)
890 891
892 - def on_display(self):
893 """Clear and update the data if needed.""" 894 895 # Register this page with the 'gui_uf' observer so that update_args() is called once the any user function completes. 896 status.observers.gui_uf.register(self.name, self.update_args, method_name='update_args') 897 898 # Update the args. 899 return self.update_args()
900 901
902 - def on_execute(self, force_exec=False):
903 """Execute the user function. 904 905 @keyword force_exec: A flag which if True will cause the execution flag to be ignored and the user function to be executed. 906 @type force_exec: bool 907 """ 908 909 # Don't execute. 910 if not force_exec and not self.execute_flag: 911 return 912 913 # Get the argument values. 914 kargs = {} 915 for i in range(len(self.uf_data.kargs)): 916 # The argument name. 917 name = self.uf_data.kargs[i]['name'] 918 919 # Store the value. 920 kargs[name] = self.GetValue(name) 921 922 # Skip execution when a Combo_list does not have enough elements. 923 if self.uf_data.kargs[i]['wiz_combo_list_min'] != None and kargs[name] == None: 924 return True 925 926 # Handle the free file format args. 927 if 'free_file_format' in self.uf_args: 928 kargs.update(self.uf_args['free_file_format'].GetValue()) 929 930 # Display the relax controller, if asked. 931 if self.uf_data.display: 932 # Get the App. 933 app = wx.GetApp() 934 935 # First show the controller. 936 app.gui.show_controller(None) 937 938 # Go to the last line. 939 app.gui.controller.log_panel.on_goto_end(None) 940 941 # The user function intro text. 942 if status.uf_intro: 943 # Convert the keys and values. 944 keys = [] 945 values = [] 946 for i in range(len(self.uf_data.kargs)): 947 keys.append(self.uf_data.kargs[i]['name']) 948 values.append(kargs[self.uf_data.kargs[i]['name']]) 949 950 # The printout. 951 print(self._intro_text(keys, values)) 952 953 # User function argument validation. 954 for i in range(len(self.uf_data.kargs)): 955 arg = self.uf_data.kargs[i] 956 try: 957 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']) 958 except AllRelaxErrors: 959 # Display a dialog with the error. 960 gui_raise(sys.exc_info()[1]) 961 962 # Return as a failure. 963 return False 964 965 # Execute the user function. 966 return_status = self.execute(self.name, **kargs) 967 968 # Bring the controller to the front. 969 if status.show_gui and self.uf_data.display: 970 wx.CallAfter(app.gui.controller.Raise) 971 972 # Return the status. 973 return return_status
974 975
976 - def on_next(self):
977 """Remove this page from the observers.""" 978 979 # Unregister this page with the 'gui_uf' observer. 980 status.observers.gui_uf.unregister(self.name)
981 982
983 - def update_args(self):
984 """Update all the argument ComboBox choices. 985 986 @return: The status of the update - False if a RelaxError occurs, True otherwise. 987 @rtype: bool 988 """ 989 990 # Loop over the arguments. 991 for i in range(len(self.uf_data.kargs)): 992 # The argument name. 993 name = self.uf_data.kargs[i]['name'] 994 995 # No iterator method for updating the list. 996 iterator = self.uf_data.kargs[i]['wiz_combo_iter'] 997 if iterator == None: 998 continue 999 1000 # Get the new choices and data (in a safe way). 1001 try: 1002 choices = [] 1003 data = [] 1004 for vals in iterator(): 1005 if lib.arg_check.is_tuple(vals, size=2, raise_error=False) or lib.arg_check.is_list(vals, size=2, raise_error=False): 1006 choices.append(vals[0]) 1007 data.append(vals[1]) 1008 else: 1009 choices.append(vals) 1010 data.append(vals) 1011 1012 # Catch all RelaxErrors. 1013 except AllRelaxErrors: 1014 instance = sys.exc_info()[1] 1015 1016 # Signal the failure to the wizard. 1017 self.setup_fail = True 1018 1019 # Display a dialog with the error. 1020 gui_raise(instance) 1021 1022 # Return as a failure. 1023 return False 1024 1025 # Get the current value, for setting as the default. 1026 val = self.uf_args[name].GetValue() 1027 1028 # Update the GUI element. 1029 self.UpdateChoices(name, combo_choices=choices, combo_data=data, combo_default=val) 1030 1031 # Successful update. 1032 return True
1033 1034 1035
1036 -class Uf_storage(dict):
1037 """A singleton container for holding all the GUI user functions.""" 1038 1039 # Class variable for storing the class instance (for the singleton). 1040 _instance = None 1041
1042 - def __new__(self, *args, **kargs):
1043 """Replacement method for implementing the singleton design pattern.""" 1044 1045 # First instantiation. 1046 if self._instance is None: 1047 # Instantiate. 1048 self._instance = dict.__new__(self, *args, **kargs) 1049 1050 # Generate the user functions. 1051 for name, data in uf_info.uf_loop(): 1052 # The title. 1053 title = data.title_short 1054 if not title: 1055 title = data.title 1056 1057 # Generate a new container. 1058 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) 1059 1060 # Store it. 1061 self._instance[name] = obj 1062 1063 # Already instantiated, so return the instance. 1064 return self._instance
1065 1066
1067 - def get_uf(self, id=0):
1068 """Return the name of the user function corresponding to the given wx ID. 1069 1070 @keyword id: The unique wx ID number. 1071 @type id: int 1072 @return: The name of the user function. 1073 @rtype: str 1074 """ 1075 1076 # Loop over the elements, returning the name when a match occurs. 1077 for name in self: 1078 if self[name]._uf_id == id: 1079 return name
1080