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