Package gui :: Package input_elements :: Module sequence
[hide private]
[frames] | no frames]

Source Code for Module gui.input_elements.sequence

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2012 Edward d'Auvergne                                        # 
  4  #                                                                             # 
  5  # This file is part of the program relax.                                     # 
  6  #                                                                             # 
  7  # relax 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 2 of the License, or           # 
 10  # (at your option) any later version.                                         # 
 11  #                                                                             # 
 12  # relax 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 relax; if not, write to the Free Software                        # 
 19  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   # 
 20  #                                                                             # 
 21  ############################################################################### 
 22   
 23  # Module docstring. 
 24  """Module containing a set of special GUI elements to be used in the relax wizards.""" 
 25   
 26  # Python module imports. 
 27  from string import upper 
 28  import wx 
 29  import wx.lib.mixins.listctrl 
 30   
 31  # relax module imports. 
 32  from relax_errors import RelaxError 
 33  from status import Status; status = Status() 
 34   
 35  # relax GUI module imports. 
 36  from gui.input_elements.combo_list import Combo_list 
 37  from gui.fonts import font 
 38  from gui.misc import add_border 
 39  from gui import paths 
 40  from gui.string_conv import float_to_gui, gui_to_float, gui_to_int, gui_to_list, gui_to_py, gui_to_str, gui_to_tuple, int_to_gui, list_to_gui, py_to_gui, str_to_gui, tuple_to_gui 
 41   
 42   
43 -class Sequence:
44 """Wizard GUI element for the input of all types of Python sequence objects. 45 46 The supported Python types include: 47 - list of floats 48 - list of integers 49 - list of strings 50 - tuple of floats 51 - tuple of integers 52 - tuple of strings 53 """ 54
55 - def __init__(self, name=None, default=None, parent=None, element_type='default', seq_type=None, value_type=None, dim=None, min=0, max=1000, sizer=None, desc=None, combo_choices=None, combo_data=None, combo_list_min=None, tooltip=None, divider=None, padding=0, spacer=None, height_element=27, single_value=False, read_only=False, can_be_none=False):
56 """Set up the element. 57 58 @keyword name: The name of the element to use in titles, etc. 59 @type name: str 60 @keyword default: The default value of the element. 61 @type default: sequence object 62 @keyword parent: The wizard GUI element. 63 @type parent: wx.Panel instance 64 @keyword element_type: The type of GUI element to create. If set to 'default', the wx.TextCtrl element with a button to bring up a dialog with ListCtrl will be used. If set to 'combo_list', the special gui.components.combo_list.Combo_list element will be used. 65 @type element_type: str 66 @keyword seq_type: The type of Python sequence. This should be one of 'list' or 'tuple'. 67 @type seq_type: str 68 @keyword value_type: The type of Python object that the value should be. This can be one of 'float', 'int', or 'str'. 69 @type value_type: str 70 @keyword dim: The dimensions that a list or tuple must conform to. For a 1D sequence, this can be a single value or a tuple of possible sizes. For a 2D sequence (a numpy matrix or list of lists), this must be a tuple of the fixed dimension sizes, e.g. a 3x5 matrix should be specified as (3, 5). 71 @type dim: int, tuple of int or None 72 @keyword min: For a SpinCtrl, the minimum value allowed. 73 @type min: int 74 @keyword max: For a SpinCtrl, the maximum value allowed. 75 @type max: int 76 @keyword sizer: The sizer to put the input field widget into. 77 @type sizer: wx.Sizer instance 78 @keyword desc: The text description. 79 @type desc: str 80 @keyword combo_choices: The list of choices to present to the user. This is only used if the element_type is set to 'combo'. 81 @type combo_choices: list of str 82 @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. 83 @type combo_data: list 84 @keyword combo_list_min: The minimum length for the Combo_list object. 85 @type combo_list_min: int or None 86 @keyword tooltip: The tooltip which appears on hovering over the text or input field. 87 @type tooltip: str 88 @keyword divider: The position of the divider. 89 @type divider: int 90 @keyword padding: Spacing to the left and right of the widgets. 91 @type padding: int 92 @keyword spacer: The amount of spacing to add below the field in pixels. If None, a stretchable spacer will be used. 93 @type spacer: None or int 94 @keyword height_element: The height in pixels of the GUI element. 95 @type height_element: int 96 @keyword single_value: A flag which if True will cause single input values to be treated as single values rather than a list or tuple. 97 @type single_value: bool 98 @keyword read_only: A flag which if True means that the text of the element cannot be edited. 99 @type read_only: bool 100 @keyword can_be_none: A flag which specifies if the element is allowed to have the None value. 101 @type can_be_none: bool 102 """ 103 104 # Store the args. 105 self.name = name 106 self.default = default 107 self.element_type = element_type 108 self.seq_type = seq_type 109 self.value_type = value_type 110 self.dim = dim 111 self.min = min 112 self.max = max 113 self.single_value = single_value 114 self.can_be_none = can_be_none 115 116 # The base types. 117 if value_type in ['float', 'num']: 118 self.convert_from_gui = gui_to_float 119 self.convert_to_gui = float_to_gui 120 elif value_type == 'int': 121 self.convert_from_gui = gui_to_int 122 self.convert_to_gui = int_to_gui 123 elif value_type == 'str': 124 self.convert_from_gui = gui_to_str 125 self.convert_to_gui = str_to_gui 126 else: 127 self.convert_from_gui = gui_to_py 128 self.convert_to_gui = py_to_gui 129 130 # The sequence types. 131 if seq_type == 'list': 132 self.convert_from_gui_seq = gui_to_list 133 self.convert_to_gui_seq = list_to_gui 134 elif seq_type == 'tuple': 135 self.convert_from_gui_seq = gui_to_tuple 136 self.convert_to_gui_seq = tuple_to_gui 137 else: 138 raise RelaxError("Unknown sequence type '%s'." % seq_type) 139 140 # Initialise the default element. 141 if self.element_type == 'default': 142 # Translate the read_only flag if None. 143 if read_only == None: 144 read_only = False 145 146 # Init. 147 sub_sizer = wx.BoxSizer(wx.HORIZONTAL) 148 149 # Left padding. 150 sub_sizer.AddSpacer(padding) 151 152 # The description. 153 text = wx.StaticText(parent, -1, desc, style=wx.ALIGN_LEFT) 154 text.SetFont(font.normal) 155 sub_sizer.Add(text, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 0) 156 157 # The divider. 158 if not divider: 159 raise RelaxError("The divider position has not been supplied.") 160 161 # Spacing. 162 x, y = text.GetSize() 163 sub_sizer.AddSpacer((divider - x, 0)) 164 165 # The input field. 166 self._field = wx.TextCtrl(parent, -1, '') 167 self._field.SetMinSize((50, height_element)) 168 self._field.SetFont(font.normal) 169 sub_sizer.Add(self._field, 1, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0) 170 171 # Read-only. 172 if read_only: 173 self._field.SetEditable(False) 174 colour = parent.GetBackgroundColour() 175 self._field.SetOwnBackgroundColour(colour) 176 177 # A little spacing. 178 sub_sizer.AddSpacer(5) 179 180 # The edit button. 181 button = wx.BitmapButton(parent, -1, wx.Bitmap(paths.icon_16x16.edit_rename, wx.BITMAP_TYPE_ANY)) 182 button.SetMinSize((height_element, height_element)) 183 button.SetToolTipString("Edit the values.") 184 sub_sizer.Add(button, 0, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0) 185 parent.Bind(wx.EVT_BUTTON, self.open_dialog, button) 186 187 # Right padding. 188 sub_sizer.AddSpacer(padding) 189 190 # Add to the main sizer. 191 sizer.Add(sub_sizer, 1, wx.EXPAND|wx.ALL, 0) 192 193 # Spacing below the widget. 194 if spacer == None: 195 sizer.AddStretchSpacer() 196 else: 197 sizer.AddSpacer(spacer) 198 199 # Tooltip. 200 if tooltip: 201 text.SetToolTipString(tooltip) 202 self._field.SetToolTipString(tooltip) 203 204 # Set the default value. 205 if self.default != None: 206 self._field.SetValue(self.convert_to_gui_seq(self.default)) 207 208 # Initialise the combo list input field. 209 elif self.element_type == 'combo_list': 210 # Translate the read_only flag if None. 211 if read_only == None: 212 read_only = False 213 214 # Set up the Combo_list object. 215 self._field = Combo_list(parent, sizer, desc, value_type=value_type, min_length=combo_list_min, choices=combo_choices, data=combo_data, default=default, tooltip=tooltip, read_only=read_only, can_be_none=can_be_none) 216 217 # Unknown field. 218 else: 219 raise RelaxError("Unknown element type '%s'." % self.element_type)
220 221
222 - def Clear(self):
223 """Special method for clearing or resetting the GUI element.""" 224 225 # Clear the value from a TextCtrl or ComboBox. 226 if self.element_type in ['default', 'combo_list']: 227 self._field.Clear()
228 229
230 - def GetValue(self):
231 """Special method for returning the sequence values of the GUI element. 232 233 @return: The sequence of values. 234 @rtype: sequence type 235 """ 236 237 # The value. 238 value = self._field.GetValue() 239 240 # Handle Combo_list elements. 241 if self.element_type == 'combo_list': 242 # Empty lists. 243 if value == [] or value == None: 244 return None 245 246 # Non Combo_list elements. 247 else: 248 # Handle single values. 249 value_set = False 250 if self.single_value: 251 try: 252 value = self.convert_from_gui(value) 253 value_set = True 254 except: 255 pass 256 257 # Convert to a sequence, handling bad user behaviour. 258 if not value_set: 259 try: 260 value = self.convert_from_gui_seq(value) 261 262 # Set the value to None or an empty sequence. 263 except RelaxError: 264 if self.can_be_none: 265 value = None 266 elif self.seq_type == 'list': 267 value = [] 268 else: 269 value = () 270 271 # Convert sequences to single values as needed. 272 if self.single_value: 273 if (isinstance(value, list) or isinstance(value, tuple)) and len(value) == 1: 274 value = value[0] 275 276 # Convert single values to sequences as needed. 277 elif value != None: 278 if self.seq_type == 'list' and not isinstance(value, list): 279 value = [value] 280 elif self.seq_type == 'tuple' and not isinstance(value, tuple): 281 value = (value,) 282 283 # Handle empty list and tuple values. 284 if not self.single_value and len(value) == 0: 285 return None 286 287 # Return the value. 288 return value
289 290
291 - def SetValue(self, value=None, index=None):
292 """Special method for setting the value of the GUI element. 293 294 @keyword value: The value to set. 295 @type value: value or list of values 296 @keyword index: The index of the value to set, if the full list is not given. 297 @type index: int or None 298 """ 299 300 # The ComboBox list. 301 if self.element_type == 'combo_list': 302 self._field.SetValue(value=value, index=index) 303 304 # The other elements. 305 else: 306 # Handle single values. 307 if self.single_value and isinstance(value, list): 308 if len(value) == 1: 309 value = value[0] 310 else: 311 raise RelaxError("The list of values '%s' cannot be converted to a single value." % value) 312 313 # Convert and set the value. 314 self._field.SetValue(self.convert_to_gui_seq(value))
315 316
317 - def UpdateChoices(self, combo_choices=None, combo_data=None, combo_default=None):
318 """Special wizard method for updating the list of choices in a ComboBox type element. 319 320 @keyword combo_choices: The list of choices to present to the user. This is only used if the element_type is set to 'combo_list'. 321 @type combo_choices: list of str 322 @keyword combo_data: The data returned by a call to GetValue(). This is only used if the element_type is set to 'combo_list'. 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. 323 @type combo_data: list 324 @keyword combo_default: The default value of the ComboBox. This is only used if the element_type is set to 'combo_list'. 325 @type combo_default: str or None 326 """ 327 328 # The ComboBox list. 329 if self.element_type == 'combo_list': 330 self._field.UpdateChoices(combo_choices=combo_choices, combo_data=combo_data, combo_default=combo_default)
331 332
333 - def open_dialog(self, event):
334 """Open a special dialog for inputting a list of text values. 335 336 @param event: The wx event. 337 @type event: wx event 338 """ 339 340 # Initialise the model selection window. 341 win = Sequence_window(name=self.name, seq_type=self.seq_type, value_type=self.value_type, dim=self.dim) 342 343 # Set the model selector window selections. 344 win.SetValue(self.GetValue()) 345 346 # Show the model selector window. 347 if status.show_gui: 348 win.ShowModal() 349 win.Close() 350 351 # Get the value. 352 value = win.GetValue() 353 354 # No sequence data. 355 if not len(value): 356 self.Clear() 357 358 # Set the values. 359 else: 360 self.SetValue(value) 361 362 # Destroy the window. 363 del win
364 365 366
367 -class Sequence_list_ctrl(wx.ListCtrl, wx.lib.mixins.listctrl.TextEditMixin, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
368 """The string list ListCtrl object.""" 369
370 - def __init__(self, parent):
371 """Initialise the control. 372 373 @param parent: The parent window. 374 @type parent: wx.Frame instance 375 """ 376 377 # Execute the parent __init__() methods. 378 wx.ListCtrl.__init__(self, parent, -1, style=wx.BORDER_SUNKEN|wx.LC_REPORT|wx.LC_HRULES|wx.LC_VRULES) 379 wx.lib.mixins.listctrl.TextEditMixin.__init__(self) 380 wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)
381 382 383
384 -class Sequence_window(wx.Dialog):
385 """The Python sequence object editor window.""" 386 387 # The window size. 388 SIZE = (600, 600) 389 390 # A border. 391 BORDER = 10 392 393 # Sizes. 394 SIZE_BUTTON = (150, 33) 395
396 - def __init__(self, name='', seq_type='list', value_type='str', dim=None):
397 """Set up the string list editor window. 398 399 @keyword name: The name of the window. 400 @type name: str 401 @keyword seq_type: The type of Python sequence. This should be one of 'list' or 'tuple'. 402 @type seq_type: str 403 @keyword value_type: The type of Python data expected in the sequence. This should be one of 'float', 'int', or 'str'. 404 @type value_type: str 405 @keyword dim: The fixed dimension that the sequence must conform to. 406 @type dim: int or None 407 """ 408 409 # Store the args. 410 self.name = name 411 self.seq_type = seq_type 412 self.value_type = value_type 413 self.dim = dim 414 415 # The base types. 416 if value_type in ['float', 'num']: 417 self.convert_from_gui = gui_to_float 418 self.convert_to_gui = float_to_gui 419 elif value_type == 'int': 420 self.convert_from_gui = gui_to_int 421 self.convert_to_gui = int_to_gui 422 elif value_type == 'str': 423 self.convert_from_gui = gui_to_str 424 self.convert_to_gui = str_to_gui 425 else: 426 raise RelaxError("Unknown base data type '%s'." % value_type) 427 428 # The title of the dialog. 429 title = "Edit the %s values." % name 430 431 # Set up the dialog. 432 wx.Dialog.__init__(self, None, id=-1, title=title) 433 434 # Initialise some values 435 self.width = self.SIZE[0] - 2*self.BORDER 436 437 # Set the frame properties. 438 self.SetSize(self.SIZE) 439 self.Centre() 440 self.SetFont(font.normal) 441 442 # The main box sizer. 443 main_sizer = wx.BoxSizer(wx.VERTICAL) 444 445 # Pack the sizer into the frame. 446 self.SetSizer(main_sizer) 447 448 # Build the central sizer, with borders. 449 sizer = add_border(main_sizer, border=self.BORDER, packing=wx.VERTICAL) 450 451 # Add the list control. 452 self.add_list(sizer) 453 454 # Some spacing. 455 sizer.AddSpacer(self.BORDER) 456 457 # Add the bottom buttons. 458 self.add_buttons(sizer)
459 460
461 - def GetValue(self):
462 """Return the values as a sequence of values. 463 464 @return: The sequence of values. 465 @rtype: sequence type 466 """ 467 468 # Init. 469 values = [] 470 471 # Loop over the entries. 472 for i in range(self.sequence.GetItemCount()): 473 values.append(self.convert_from_gui(self.sequence.GetItemText(i))) 474 475 # Sequence conversion. 476 if self.seq_type == 'tuple': 477 values = tuple(values) 478 479 # Return the sequence. 480 return values
481 482
483 - def SetValue(self, values):
484 """Set up the list values. 485 486 @param values: The list of values to add to the list. 487 @type values: list of str or None 488 """ 489 490 # No value. 491 if values == None: 492 return 493 494 # Loop over the entries. 495 for i in range(len(values)): 496 # Fixed dimension sequences - set the values of the pre-created list. 497 if self.dim: 498 self.sequence.SetStringItem(index=i, col=0, label=self.convert_to_gui(values[i])) 499 500 # Variable dimension sequences - append the item to the end of the blank list. 501 else: 502 self.sequence.InsertStringItem(i, self.convert_to_gui(values[i]))
503 504
505 - def add_buttons(self, sizer):
506 """Add the buttons to the sizer. 507 508 @param sizer: A sizer object. 509 @type sizer: wx.Sizer instance 510 """ 511 512 # Create a horizontal layout for the buttons. 513 button_sizer = wx.BoxSizer(wx.HORIZONTAL) 514 sizer.Add(button_sizer, 0, wx.ALIGN_CENTER|wx.ALL, 0) 515 516 # The non-fixed sequence buttons. 517 if self.dim == None or (isinstance(self.dim, tuple) and self.dim[0] == None): 518 # The add button. 519 button = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " Add") 520 button.SetBitmapLabel(wx.Bitmap(paths.icon_22x22.add, wx.BITMAP_TYPE_ANY)) 521 button.SetFont(font.normal) 522 button.SetToolTipString("Add a row to the list.") 523 button.SetMinSize(self.SIZE_BUTTON) 524 button_sizer.Add(button, 0, wx.ADJUST_MINSIZE, 0) 525 self.Bind(wx.EVT_BUTTON, self.append_row, button) 526 527 # Spacer. 528 button_sizer.AddSpacer(20) 529 530 # The delete all button. 531 button = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " Delete all") 532 button.SetBitmapLabel(wx.Bitmap(paths.icon_22x22.edit_delete, wx.BITMAP_TYPE_ANY)) 533 button.SetFont(font.normal) 534 button.SetToolTipString("Delete all items.") 535 button.SetMinSize(self.SIZE_BUTTON) 536 button_sizer.Add(button, 0, wx.ADJUST_MINSIZE, 0) 537 self.Bind(wx.EVT_BUTTON, self.delete_all, button) 538 539 # Spacer. 540 button_sizer.AddSpacer(20) 541 542 # The Ok button. 543 button = wx.lib.buttons.ThemedGenBitmapTextButton(self, -1, None, " Ok") 544 button.SetBitmapLabel(wx.Bitmap(paths.icon_22x22.dialog_ok, wx.BITMAP_TYPE_ANY)) 545 button.SetFont(font.normal) 546 button.SetMinSize(self.SIZE_BUTTON) 547 button_sizer.Add(button, 0, wx.ADJUST_MINSIZE, 0) 548 self.Bind(wx.EVT_BUTTON, self.close, button)
549 550
551 - def add_list(self, sizer):
552 """Set up the list control. 553 554 @param sizer: A sizer object. 555 @type sizer: wx.Sizer instance 556 """ 557 558 # The control. 559 self.sequence = Sequence_list_ctrl(self) 560 561 # Set the column title. 562 title = "%s%s" % (upper(self.name[0]), self.name[1:]) 563 564 # Add a single column, full width. 565 self.sequence.InsertColumn(0, title) 566 self.sequence.SetColumnWidth(0, wx.LIST_AUTOSIZE) 567 568 # Add the table to the sizer. 569 sizer.Add(self.sequence, 1, wx.ALL|wx.EXPAND, 0) 570 571 # The fixed dimension sequence - add all the rows needed. 572 if self.dim: 573 for i in range(self.dim): 574 self.append_row(None)
575 576
577 - def append_row(self, event):
578 """Append a new row to the list. 579 580 @param event: The wx event. 581 @type event: wx event 582 """ 583 584 # The next index. 585 next = self.sequence.GetItemCount() 586 587 # Add a new empty row. 588 self.sequence.InsertStringItem(next, '')
589 590
591 - def close(self, event):
592 """Close the window. 593 594 @param event: The wx event. 595 @type event: wx event 596 """ 597 598 # Destroy the window. 599 self.Destroy()
600 601
602 - def delete_all(self, event):
603 """Remove all items from the list. 604 605 @param event: The wx event. 606 @type event: wx event 607 """ 608 609 # Delete. 610 self.sequence.DeleteAllItems()
611