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,2016 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  import dep_check 
 31  from graphics import fetch_icon 
 32  from gui.fonts import font 
 33  from gui.string_conv import float_to_gui, gui_to_float, gui_to_int, gui_to_str, int_to_gui, str_to_gui 
 34  from lib.errors import RelaxError 
 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 text.SetFont(font.normal) 173 sub_sizer.Add(text, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 0) 174 175 # Spacing. 176 dc = wx.ScreenDC() 177 dc.SetFont(font.normal) 178 x, y = dc.GetTextExtent(self._desc) 179 if dep_check.wx_classic: 180 sub_sizer.AddSpacer((self._divider - x, 0)) 181 else: 182 sub_sizer.AddSpacer(int(self._divider - x)) 183 184 # No description for other rows, so add a blank space. 185 else: 186 if dep_check.wx_classic: 187 sub_sizer.AddSpacer((self._divider, 0)) 188 else: 189 sub_sizer.AddSpacer(int(self._divider)) 190 191 # The combo box element. 192 style = wx.CB_DROPDOWN 193 if self._read_only: 194 style = style | wx.CB_READONLY 195 combo = wx.ComboBox(self._parent, -1, value='', style=style) 196 combo.SetMinSize((50, 27)) 197 sub_sizer.Add(combo, 1, wx.ALIGN_CENTER_VERTICAL, 0) 198 self._combo_boxes.append(combo) 199 200 # Choices. 201 if self._choices != None: 202 # Loop over the choices and data, adding both to the end. 203 for j in range(len(self._choices)): 204 self._combo_boxes[-1].Insert(self.convert_to_gui(self._choices[j]), j, self._data[j]) 205 206 # Set the default selection. 207 if self._default: 208 # A list. 209 if isinstance(self._default, list): 210 if index < len(self._default): 211 self._combo_boxes[-1].SetStringSelection(self._default[index-1]) 212 213 # Single value. 214 else: 215 self._combo_boxes[-1].SetStringSelection(self._default) 216 217 # The add button. 218 button = None 219 if index == 0: 220 button = wx.BitmapButton(self._parent, -1, wx.Bitmap(fetch_icon('oxygen.actions.list-add-relax-blue', "16x16"), wx.BITMAP_TYPE_ANY)) 221 button.SetMinSize((-1, 27)) 222 sub_sizer.Add(button, 0, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0) 223 self._parent.Bind(wx.EVT_BUTTON, self._add, button) 224 225 # The delete button. 226 elif index == self._min_length: 227 button = wx.BitmapButton(self._parent, -1, wx.Bitmap(fetch_icon('oxygen.actions.list-remove', "16x16"), wx.BITMAP_TYPE_ANY)) 228 button.SetMinSize((-1, 27)) 229 sub_sizer.Add(button, 0, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0) 230 self._parent.Bind(wx.EVT_BUTTON, self._delete, button) 231 232 # Otherwise empty spacing. 233 else: 234 if dep_check.wx_classic: 235 sub_sizer.AddSpacer((27, 0)) 236 else: 237 sub_sizer.AddSpacer(27) 238 239 # Right padding. 240 sub_sizer.AddSpacer(self._padding) 241 242 # Add to the main sizer. 243 self._sub_sizers.append(sub_sizer) 244 self._main_sizer.Add(sub_sizer, 1, wx.EXPAND|wx.ALL, 0) 245 246 # Bind events. 247 if self._evt_fn: 248 self._parent.Bind(wx.EVT_COMBOBOX, self._evt_fn, combo) 249 250 # Tooltip. 251 if self._tooltip: 252 if index == 0: 253 text.SetToolTip(wx.ToolTip(self._tooltip)) 254 combo.SetToolTip(wx.ToolTip(self._tooltip)) 255 if index <= 1 and button != None: 256 button.SetToolTip(wx.ToolTip(self._tooltip))
257 258
259 - def _delete(self, event):
260 """Add a new combo box. 261 262 @param event: The wx event. 263 @type event: wx event 264 """ 265 266 # Remove the combo box element from the list. 267 self._combo_boxes.pop() 268 269 # Destroy the subsizer. 270 sub_sizer = self._sub_sizers.pop() 271 sub_sizer.Clear(True) 272 self._main_sizer.Remove(sub_sizer) 273 274 # Re-perform the window layout. 275 self._parent.Layout()
276 277
278 - def GetValue(self):
279 """Return the value represented by this GUI element. 280 281 @return: The list of choices. 282 @rtype: list 283 """ 284 285 # Loop over the combo boxes. 286 data = [] 287 n = 0 288 for i in range(len(self._combo_boxes)): 289 # Get the value. 290 sel_index = self._combo_boxes[i].GetSelection() 291 if sel_index == wx.NOT_FOUND: 292 val = None 293 else: 294 val = self.convert_from_gui(self._combo_boxes[i].GetClientData(sel_index)) 295 296 # Manually added value by the user. 297 if val == None: 298 val = self.convert_from_gui(self._combo_boxes[i].GetValue()) 299 # Nothing, so skip. 300 if val == None: 301 continue 302 303 # Add the value. 304 data.append(val) 305 306 # Increment the number. 307 n += 1 308 309 # Return the list. 310 if self._min_length != None and n < self._min_length: 311 return None 312 else: 313 return data
314 315
316 - def SetValue(self, value=None, index=None):
317 """Special method for setting the value of the GUI element. 318 319 @keyword value: The value to set. 320 @type value: value or list of values 321 @keyword index: The index of the value to set. 322 @type index: int 323 """ 324 325 # Single element. 326 if not isinstance(value, list): 327 # The index default. 328 if index == None: 329 index = 0 330 331 # Add elements as needed. 332 if len(self._combo_boxes) <= index: 333 for i in range(len(self._combo_boxes) - index + 1): 334 self._add(None) 335 336 # Loop until the proper client data is found. 337 found = False 338 for j in range(self._combo_boxes[index].GetCount()): 339 if self._combo_boxes[index].GetClientData(j) == value: 340 self._combo_boxes[index].SetSelection(j) 341 found = True 342 break 343 344 # No value found. 345 if not found: 346 # Invalid value. 347 if self._read_only: 348 if value != None: 349 raise RelaxError("The Value element is read only, cannot set the value '%s'." % value) 350 351 # Set the unknown value, and remove the selection. 352 else: 353 self._combo_boxes[index].SetSelection(wx.NOT_FOUND) 354 self._combo_boxes[index].SetValue(self.convert_to_gui(value)) 355 356 # A list of values. 357 else: 358 # Add elements as needed. 359 if len(self._combo_boxes) <= len(value): 360 for i in range(len(value) - len(self._combo_boxes)): 361 self._add(None) 362 363 # Loop over the list. 364 for i in range(len(value)): 365 # Loop until the proper client data is found. 366 found = False 367 for j in range(self._combo_boxes[i].GetCount()): 368 if self._combo_boxes[i].GetClientData(j) == value[i]: 369 self._combo_boxes[i].SetSelection(j) 370 found = True 371 break 372 373 # Otherwise set the value. 374 if not found: 375 self._combo_boxes[i].SetValue(value[i])
376 377
378 - def UpdateChoices(self, combo_choices=None, combo_data=None, combo_default=None):
379 """Special wizard method for updating the list of choices in a ComboBox type element. 380 381 @keyword combo_choices: The list of choices to present to the user. This is only used if the element_type is set to 'combo'. 382 @type combo_choices: list of str 383 @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. 384 @type combo_data: list 385 @keyword combo_default: The default value of the ComboBox. This is only used if the element_type is set to 'combo'. 386 @type combo_default: str or None 387 """ 388 389 # Store the values. 390 self._choices = combo_choices 391 self._data = combo_data 392 self._default = combo_default 393 394 # Set the data if needed. 395 if self._data == None: 396 self._data = deepcopy(self._choices) 397 398 # Handle None in combo boxes by prepending a None element to the lists. 399 if self._can_be_none: 400 self._choices.insert(0, '') 401 self._data.insert(0, None) 402 403 # Loop over the combo boxes. 404 for i in range(len(self._combo_boxes)): 405 # Store the current selection's client data to restore at the end. 406 sel_index = self._combo_boxes[i].GetSelection() 407 if sel_index == wx.NOT_FOUND: 408 sel = None 409 else: 410 sel = self._combo_boxes[i].GetClientData(sel_index) 411 412 # First clear all data. 413 self._combo_boxes[i].Clear() 414 415 # Loop over the choices and data, adding both to the end. 416 for j in range(len(self._choices)): 417 self._combo_boxes[i].Insert(self.convert_to_gui(self._choices[j]), j, self._data[j]) 418 419 # Set the default selection. 420 if sel == None and self._default != None: 421 # A list. 422 if isinstance(self._default, list): 423 # Add rows as needed. 424 if len(self._default) > len(self._combo_boxes): 425 for k in range(len(self._default) - len(self._combo_boxes)): 426 self._add(None) 427 428 # Loop over the defaults. 429 for k in range(len(self._default)): 430 # Translate if needed. 431 if self._default[k] in self._choices: 432 string = self._default[k] 433 elif self._default[k] not in self._data: 434 string = self._default[k] 435 else: 436 string = self._choices[self._data.index(self._default[k])] 437 438 # Set the selection. 439 self._combo_boxes[i].SetStringSelection(string) 440 441 # Single value. 442 else: 443 # Translate if needed. 444 if self._default in self._choices: 445 string = self._default 446 elif self._default not in self._data: 447 string = self._default 448 else: 449 string = self._choices[self._data.index(self._default)] 450 451 # Set the selection. 452 self._combo_boxes[i].SetStringSelection(string) 453 454 # Restore the selection. 455 else: 456 for j in range(self._combo_boxes[i].GetCount()): 457 if self._combo_boxes[i].GetClientData(j) == sel: 458 self._combo_boxes[i].SetSelection(j)
459