1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """The combo list GUI element."""
25
26
27 from copy import deepcopy
28 import wx
29
30
31 from relax_errors import RelaxError
32
33
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
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
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
93 if self._data == None:
94 self._data = deepcopy(self._choices)
95
96
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
113 self._main_sizer = wx.BoxSizer(wx.VERTICAL)
114 self._combo_boxes = []
115 self._sub_sizers = []
116
117
118 if n == None:
119 n = 1
120
121
122 if not divider:
123 self._divider = self._parent._div_left
124 else:
125 self._divider = divider
126
127
128 if n < min_length:
129 n = min_length
130 for i in range(n):
131 self._build_row()
132
133
134 self._sizer.Add(self._main_sizer, 0, wx.EXPAND|wx.ALL, 0)
135
136
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
151 self._build_row()
152
153
154 self._parent.Layout()
155
156
158 """Construct a row of the GUI element.
159
160 @param text: The text description of the
161 """
162
163
164 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
165 index = len(self._combo_boxes)
166
167
168 sub_sizer.AddSpacer(self._padding)
169
170
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
176 x, y = text.GetSize()
177 sub_sizer.AddSpacer((self._divider - x, 0))
178
179
180 else:
181 sub_sizer.AddSpacer((self._divider, 0))
182
183
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
193 if self._choices != None:
194
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
199 if self._default:
200
201 if isinstance(self._default, list):
202 if index < len(self._default):
203 self._combo_boxes[-1].SetStringSelection(self._default[index-1])
204
205
206 else:
207 self._combo_boxes[-1].SetStringSelection(self._default)
208
209
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
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
225 else:
226 sub_sizer.AddSpacer((27, 0))
227
228
229 sub_sizer.AddSpacer(self._padding)
230
231
232 self._sub_sizers.append(sub_sizer)
233 self._main_sizer.Add(sub_sizer, 1, wx.EXPAND|wx.ALL, 0)
234
235
236 if self._evt_fn:
237 self._parent.Bind(wx.EVT_COMBOBOX, self._evt_fn, combo)
238
239
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
249 """Add a new combo box.
250
251 @param event: The wx event.
252 @type event: wx event
253 """
254
255
256 self._combo_boxes.pop()
257
258
259 sub_sizer = self._sub_sizers.pop()
260 sub_sizer.DeleteWindows()
261 self._main_sizer.Remove(sub_sizer)
262
263
264 self._parent.Layout()
265
266
268 """Return the value represented by this GUI element.
269
270 @return: The list of choices.
271 @rtype: list
272 """
273
274
275 data = []
276 n = 0
277 for i in range(len(self._combo_boxes)):
278
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
286 if val == None:
287 val = self.convert_from_gui(self._combo_boxes[i].GetValue())
288
289 if val == None:
290 continue
291
292
293 data.append(val)
294
295
296 n += 1
297
298
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
315 if not isinstance(value, list):
316
317 if index == None:
318 index = 0
319
320
321 if len(self._combo_boxes) <= index:
322 for i in range(len(self._combo_boxes) - index + 1):
323 self._add(None)
324
325
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
334 if not found:
335
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
341 else:
342 self._combo_boxes[index].SetSelection(wx.NOT_FOUND)
343 self._combo_boxes[index].SetValue(self.convert_to_gui(value))
344
345
346 else:
347
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
353 for i in range(len(value)):
354
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
373 self._choices = combo_choices
374 self._data = combo_data
375 self._default = combo_default
376
377
378 if self._data == None:
379 self._data = deepcopy(self._choices)
380
381
382 if self._can_be_none:
383 self._choices.insert(0, '')
384 self._data.insert(0, None)
385
386
387 for i in range(len(self._combo_boxes)):
388
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
396 self._combo_boxes[i].Clear()
397
398
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
403 if sel == None and self._default != None:
404
405 if isinstance(self._default, list):
406
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
412 for k in range(len(self._default)):
413
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
422 self._combo_boxes[i].SetStringSelection(string)
423
424
425 else:
426
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
435 self._combo_boxes[i].SetStringSelection(string)
436
437
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