1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """The combo list GUI element."""
24
25
26 from copy import deepcopy
27 import wx
28
29
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
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
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
92 if self._data == None:
93 self._data = deepcopy(self._choices)
94
95
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
112 self._main_sizer = wx.BoxSizer(wx.VERTICAL)
113 self._combo_boxes = []
114 self._sub_sizers = []
115
116
117 if n == None:
118 n = 1
119
120
121 if not divider:
122 self._divider = self._parent._div_left
123 else:
124 self._divider = divider
125
126
127 if n < min_length:
128 n = min_length
129 for i in range(n):
130 self._build_row()
131
132
133 self._sizer.Add(self._main_sizer, 0, wx.EXPAND|wx.ALL, 0)
134
135
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
150 self._build_row()
151
152
153 self._parent.Layout()
154
155
157 """Construct a row of the GUI element.
158
159 @param text: The text description of the
160 """
161
162
163 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
164 index = len(self._combo_boxes)
165
166
167 sub_sizer.AddSpacer(self._padding)
168
169
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
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
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
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
201 if self._choices != None:
202
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
207 if self._default:
208
209 if isinstance(self._default, list):
210 if index < len(self._default):
211 self._combo_boxes[-1].SetStringSelection(self._default[index-1])
212
213
214 else:
215 self._combo_boxes[-1].SetStringSelection(self._default)
216
217
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
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
233 else:
234 if dep_check.wx_classic:
235 sub_sizer.AddSpacer((27, 0))
236 else:
237 sub_sizer.AddSpacer(27)
238
239
240 sub_sizer.AddSpacer(self._padding)
241
242
243 self._sub_sizers.append(sub_sizer)
244 self._main_sizer.Add(sub_sizer, 1, wx.EXPAND|wx.ALL, 0)
245
246
247 if self._evt_fn:
248 self._parent.Bind(wx.EVT_COMBOBOX, self._evt_fn, combo)
249
250
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
260 """Add a new combo box.
261
262 @param event: The wx event.
263 @type event: wx event
264 """
265
266
267 self._combo_boxes.pop()
268
269
270 sub_sizer = self._sub_sizers.pop()
271 sub_sizer.Clear(True)
272 self._main_sizer.Remove(sub_sizer)
273
274
275 self._parent.Layout()
276
277
279 """Return the value represented by this GUI element.
280
281 @return: The list of choices.
282 @rtype: list
283 """
284
285
286 data = []
287 n = 0
288 for i in range(len(self._combo_boxes)):
289
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
297 if val == None:
298 val = self.convert_from_gui(self._combo_boxes[i].GetValue())
299
300 if val == None:
301 continue
302
303
304 data.append(val)
305
306
307 n += 1
308
309
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
326 if not isinstance(value, list):
327
328 if index == None:
329 index = 0
330
331
332 if len(self._combo_boxes) <= index:
333 for i in range(len(self._combo_boxes) - index + 1):
334 self._add(None)
335
336
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
345 if not found:
346
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
352 else:
353 self._combo_boxes[index].SetSelection(wx.NOT_FOUND)
354 self._combo_boxes[index].SetValue(self.convert_to_gui(value))
355
356
357 else:
358
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
364 for i in range(len(value)):
365
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
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
390 self._choices = combo_choices
391 self._data = combo_data
392 self._default = combo_default
393
394
395 if self._data == None:
396 self._data = deepcopy(self._choices)
397
398
399 if self._can_be_none:
400 self._choices.insert(0, '')
401 self._data.insert(0, None)
402
403
404 for i in range(len(self._combo_boxes)):
405
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
413 self._combo_boxes[i].Clear()
414
415
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
420 if sel == None and self._default != None:
421
422 if isinstance(self._default, list):
423
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
429 for k in range(len(self._default)):
430
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
439 self._combo_boxes[i].SetStringSelection(string)
440
441
442 else:
443
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
452 self._combo_boxes[i].SetStringSelection(string)
453
454
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