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

Source Code for Module gui.input_elements.combo_list

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2011-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  """The combo list GUI element.""" 
 25   
 26  # Python module imports. 
 27  from copy import deepcopy 
 28  import wx 
 29   
 30  # relax module imports. 
 31  from relax_errors import RelaxError 
 32   
 33  # relax GUI module imports. 
 34  from gui.paths import icon_16x16 
 35  from gui.string_conv import float_to_gui, gui_to_float, gui_to_int, gui_to_str, int_to_gui, str_to_gui 
 36   
 37   
38 -class Combo_list:
39 """The combo list GUI element.""" 40
41 - def __init__(self, parent, sizer, desc, value_type=None, n=1, min_length=1, choices=None, data=None, default=None, evt_fn=None, tooltip=None, divider=None, padding=0, spacer=None, read_only=True, can_be_none=False):
42 """Build the combo box list widget for a list of list selections. 43 44 @param parent: The parent GUI element. 45 @type parent: wx object instance 46 @param sizer: The sizer to put the combo box widget into. 47 @type sizer: wx.Sizer instance 48 @param desc: The text description. 49 @type desc: str 50 @keyword value_type: The type of Python object that the value should be. This can be one of 'float', 'int', or 'str'. 51 @type value_type: str 52 @keyword n: The number of initial entries. 53 @type n: int 54 @keyword min_length: The minimum length for the Combo_list object. 55 @type min_length: int 56 @keyword choices: The list of choices (all combo boxes will have the same list). 57 @type choices: list of str 58 @keyword 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 choices list. If not supplied, the choices list will be used for the returned data. 59 @type data: list 60 @keyword default: The default value of the ComboBox. This is only used if the element_type is set to 'combo'. 61 @type default: str or None 62 @keyword evt_fn: The event handling function. 63 @type evt_fn: func 64 @keyword tooltip: The tooltip which appears on hovering over the text or input field. 65 @type tooltip: str 66 @keyword divider: The optional position of the divider. If None, the parent class variable _div_left will be used if present. 67 @type divider: None or int 68 @keyword padding: Spacing to the left and right of the widgets. 69 @type padding: int 70 @keyword spacer: The amount of spacing to add below the field in pixels. If None, a stretchable spacer will be used. 71 @type spacer: None or int 72 @keyword read_only: A flag which if True means that text cannot be typed into the combo box widget. 73 @type read_only: bool 74 @keyword can_be_none: A flag which specifies if the element is allowed to have the None value. 75 @type can_be_none: bool 76 """ 77 78 # Store some args. 79 self._parent = parent 80 self._sizer = sizer 81 self._desc = desc 82 self._choices = choices 83 self._data = data 84 self._default = default 85 self._evt_fn = evt_fn 86 self._tooltip = tooltip 87 self._padding = padding 88 self._read_only = read_only 89 self._can_be_none = can_be_none 90 self._min_length = min_length 91 92 # Set the data if needed. 93 if self._data == None: 94 self._data = deepcopy(self._choices) 95 96 # The value types. 97 if value_type in ['float', 'num']: 98 self.convert_from_gui = gui_to_float 99 self.convert_to_gui = float_to_gui 100 self.type_string = 'float' 101 elif value_type == 'int': 102 self.convert_from_gui = gui_to_int 103 self.convert_to_gui = int_to_gui 104 self.type_string = 'integer' 105 elif value_type == 'str': 106 self.convert_from_gui = gui_to_str 107 self.convert_to_gui = str_to_gui 108 self.type_string = 'string' 109 else: 110 raise RelaxError("Unknown value type '%s'." % value_type) 111 112 # Init. 113 self._main_sizer = wx.BoxSizer(wx.VERTICAL) 114 self._combo_boxes = [] 115 self._sub_sizers = [] 116 117 # Set the initial size, if needed. 118 if n == None: 119 n = 1 120 121 # The divider. 122 if not divider: 123 self._divider = self._parent._div_left 124 else: 125 self._divider = divider 126 127 # Build the first rows. 128 if n < min_length: 129 n = min_length 130 for i in range(n): 131 self._build_row() 132 133 # Add the main sizer. 134 self._sizer.Add(self._main_sizer, 0, wx.EXPAND|wx.ALL, 0) 135 136 # Spacing below the widget. 137 if spacer == None: 138 self._sizer.AddStretchSpacer() 139 else: 140 self._sizer.AddSpacer(spacer)
141 142
143 - def _add(self, event):
144 """Add a new combo box. 145 146 @param event: The wx event. 147 @type event: wx event 148 """ 149 150 # Add another row. 151 self._build_row() 152 153 # Re-perform the window layout. 154 self._parent.Layout()
155 156
157 - def _build_row(self, text=None):
158 """Construct a row of the GUI element. 159 160 @param text: The text description of the 161 """ 162 163 # Init. 164 sub_sizer = wx.BoxSizer(wx.HORIZONTAL) 165 index = len(self._combo_boxes) 166 167 # Left padding. 168 sub_sizer.AddSpacer(self._padding) 169 170 # The description. 171 if index == 0: 172 text = wx.StaticText(self._parent, -1, self._desc, style=wx.ALIGN_LEFT) 173 sub_sizer.Add(text, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 0) 174 175 # Spacing. 176 x, y = text.GetSize() 177 sub_sizer.AddSpacer((self._divider - x, 0)) 178 179 # No description for other rows, so add a blank space. 180 else: 181 sub_sizer.AddSpacer((self._divider, 0)) 182 183 # The combo box element. 184 style = wx.CB_DROPDOWN 185 if self._read_only: 186 style = style | wx.CB_READONLY 187 combo = wx.ComboBox(self._parent, -1, value='', style=style) 188 combo.SetMinSize((50, 27)) 189 sub_sizer.Add(combo, 1, wx.ALIGN_CENTER_VERTICAL, 0) 190 self._combo_boxes.append(combo) 191 192 # Choices. 193 if self._choices != None: 194 # Loop over the choices and data, adding both to the end. 195 for j in range(len(self._choices)): 196 self._combo_boxes[-1].Insert(self.convert_to_gui(self._choices[j]), j, self._data[j]) 197 198 # Set the default selection. 199 if self._default: 200 # A list. 201 if isinstance(self._default, list): 202 if index < len(self._default): 203 self._combo_boxes[-1].SetStringSelection(self._default[index-1]) 204 205 # Single value. 206 else: 207 self._combo_boxes[-1].SetStringSelection(self._default) 208 209 # The add button. 210 button = None 211 if index == 0: 212 button = wx.BitmapButton(self._parent, -1, wx.Bitmap(icon_16x16.add, wx.BITMAP_TYPE_ANY)) 213 button.SetMinSize((27, 27)) 214 sub_sizer.Add(button, 0, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0) 215 self._parent.Bind(wx.EVT_BUTTON, self._add, button) 216 217 # The delete button. 218 elif index == self._min_length: 219 button = wx.BitmapButton(self._parent, -1, wx.Bitmap(icon_16x16.remove, wx.BITMAP_TYPE_ANY)) 220 button.SetMinSize((27, 27)) 221 sub_sizer.Add(button, 0, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0) 222 self._parent.Bind(wx.EVT_BUTTON, self._delete, button) 223 224 # Otherwise empty spacing. 225 else: 226 sub_sizer.AddSpacer((27, 0)) 227 228 # Right padding. 229 sub_sizer.AddSpacer(self._padding) 230 231 # Add to the main sizer. 232 self._sub_sizers.append(sub_sizer) 233 self._main_sizer.Add(sub_sizer, 1, wx.EXPAND|wx.ALL, 0) 234 235 # Bind events. 236 if self._evt_fn: 237 self._parent.Bind(wx.EVT_COMBOBOX, self._evt_fn, combo) 238 239 # Tooltip. 240 if self._tooltip: 241 if index == 0: 242 text.SetToolTipString(self._tooltip) 243 combo.SetToolTipString(self._tooltip) 244 if index <= 1 and button != None: 245 button.SetToolTipString(self._tooltip)
246 247
248 - def _delete(self, event):
249 """Add a new combo box. 250 251 @param event: The wx event. 252 @type event: wx event 253 """ 254 255 # Remove the combo box element from the list. 256 self._combo_boxes.pop() 257 258 # Destroy the subsizer. 259 sub_sizer = self._sub_sizers.pop() 260 sub_sizer.DeleteWindows() 261 self._main_sizer.Remove(sub_sizer) 262 263 # Re-perform the window layout. 264 self._parent.Layout()
265 266
267 - def GetValue(self):
268 """Return the value represented by this GUI element. 269 270 @return: The list of choices. 271 @rtype: list 272 """ 273 274 # Loop over the combo boxes. 275 data = [] 276 n = 0 277 for i in range(len(self._combo_boxes)): 278 # Get the value. 279 sel_index = self._combo_boxes[i].GetSelection() 280 if sel_index == wx.NOT_FOUND: 281 val = None 282 else: 283 val = self.convert_from_gui(self._combo_boxes[i].GetClientData(sel_index)) 284 285 # Manually added value by the user. 286 if val == None: 287 val = self.convert_from_gui(self._combo_boxes[i].GetValue()) 288 # Nothing, so skip. 289 if val == None: 290 continue 291 292 # Add the value. 293 data.append(val) 294 295 # Increment the number. 296 n += 1 297 298 # Return the list. 299 if self._min_length != None and n < self._min_length: 300 return None 301 else: 302 return data
303 304
305 - def SetValue(self, value=None, index=None):
306 """Special method for setting the value of the GUI element. 307 308 @keyword value: The value to set. 309 @type value: value or list of values 310 @keyword index: The index of the value to set. 311 @type index: int 312 """ 313 314 # Single element. 315 if not isinstance(value, list): 316 # The index default. 317 if index == None: 318 index = 0 319 320 # Add elements as needed. 321 if len(self._combo_boxes) <= index: 322 for i in range(len(self._combo_boxes) - index + 1): 323 self._add(None) 324 325 # Loop until the proper client data is found. 326 found = False 327 for j in range(self._combo_boxes[index].GetCount()): 328 if self._combo_boxes[index].GetClientData(j) == value: 329 self._combo_boxes[index].SetSelection(j) 330 found = True 331 break 332 333 # No value found. 334 if not found: 335 # Invalid value. 336 if self._read_only: 337 if value != None: 338 raise RelaxError("The Value element is read only, cannot set the value '%s'." % value) 339 340 # Set the unknown value, and remove the selection. 341 else: 342 self._combo_boxes[index].SetSelection(wx.NOT_FOUND) 343 self._combo_boxes[index].SetValue(self.convert_to_gui(value)) 344 345 # A list of values. 346 else: 347 # Add elements as needed. 348 if len(self._combo_boxes) <= len(value): 349 for i in range(len(value) - len(self._combo_boxes)): 350 self._add(None) 351 352 # Loop over the list. 353 for i in range(len(value)): 354 # Loop until the proper client data is found. 355 for j in range(self._combo_boxes[i].GetCount()): 356 if self._combo_boxes[i].GetClientData(j) == value[i]: 357 self._combo_boxes[i].SetSelection(j) 358 break
359 360
361 - def UpdateChoices(self, combo_choices=None, combo_data=None, combo_default=None):
362 """Special wizard method for updating the list of choices in a ComboBox type element. 363 364 @keyword combo_choices: The list of choices to present to the user. This is only used if the element_type is set to 'combo'. 365 @type combo_choices: list of str 366 @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. 367 @type combo_data: list 368 @keyword combo_default: The default value of the ComboBox. This is only used if the element_type is set to 'combo'. 369 @type combo_default: str or None 370 """ 371 372 # Store the values. 373 self._choices = combo_choices 374 self._data = combo_data 375 self._default = combo_default 376 377 # Set the data if needed. 378 if self._data == None: 379 self._data = deepcopy(self._choices) 380 381 # Handle None in combo boxes by prepending a None element to the lists. 382 if self._can_be_none: 383 self._choices.insert(0, '') 384 self._data.insert(0, None) 385 386 # Loop over the combo boxes. 387 for i in range(len(self._combo_boxes)): 388 # Store the current selection's client data to restore at the end. 389 sel_index = self._combo_boxes[i].GetSelection() 390 if sel_index == wx.NOT_FOUND: 391 sel = None 392 else: 393 sel = self._combo_boxes[i].GetClientData(sel_index) 394 395 # First clear all data. 396 self._combo_boxes[i].Clear() 397 398 # Loop over the choices and data, adding both to the end. 399 for j in range(len(self._choices)): 400 self._combo_boxes[i].Insert(self.convert_to_gui(self._choices[j]), j, self._data[j]) 401 402 # Set the default selection. 403 if sel == None and self._default != None: 404 # A list. 405 if isinstance(self._default, list): 406 # Add rows as needed. 407 if len(self._default) > len(self._combo_boxes): 408 for k in range(len(self._default) - len(self._combo_boxes)): 409 self._add(None) 410 411 # Loop over the defaults. 412 for k in range(len(self._default)): 413 # Translate if needed. 414 if self._default[k] in self._choices: 415 string = self._default[k] 416 elif self._default[k] not in self._data: 417 string = self._default[k] 418 else: 419 string = self._choices[self._data.index(self._default[k])] 420 421 # Set the selection. 422 self._combo_boxes[i].SetStringSelection(string) 423 424 # Single value. 425 else: 426 # Translate if needed. 427 if self._default in self._choices: 428 string = self._default 429 elif self._default not in self._data: 430 string = self._default 431 else: 432 string = self._choices[self._data.index(self._default)] 433 434 # Set the selection. 435 self._combo_boxes[i].SetStringSelection(string) 436 437 # Restore the selection. 438 else: 439 for j in range(self._combo_boxes[i].GetCount()): 440 if self._combo_boxes[i].GetClientData(j) == sel: 441 self._combo_boxes[i].SetSelection(j)
442