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