1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """Module containing a set of special GUI elements to be used in the relax wizards."""
24
25
26 import wx
27 import wx.lib.mixins.listctrl
28
29
30 import dep_check
31 from graphics import fetch_icon
32 from gui.input_elements.combo_list import Combo_list
33 from gui.fonts import font
34 from gui.misc import add_border
35 from gui.string_conv import float_to_gui, gui_to_float, gui_to_int, gui_to_list, gui_to_py, gui_to_str, gui_to_tuple, int_to_gui, list_to_gui, py_to_gui, str_to_gui, tuple_to_gui
36 from lib.check_types import is_list
37 from lib.errors import RelaxError
38 from status import Status; status = Status()
39
40
42 """Wizard GUI element for the input of all types of Python sequence objects.
43
44 The supported Python types include:
45 - list of floats
46 - list of integers
47 - list of strings
48 - tuple of floats
49 - tuple of integers
50 - tuple of strings
51 """
52
53 - def __init__(self, name=None, default=None, parent=None, element_type='default', seq_type=None, value_type=None, dim=None, min=0, max=1000, sizer=None, titles=None, desc=None, combo_choices=None, combo_data=None, combo_list_min=None, tooltip=None, divider=None, padding=0, spacer=None, height_element=27, single_value=False, read_only=False, can_be_none=False):
54 """Set up the element.
55
56 @keyword name: The name of the element to use in titles, etc.
57 @type name: str
58 @keyword default: The default value of the element.
59 @type default: sequence object
60 @keyword parent: The wizard GUI element.
61 @type parent: wx.Panel instance
62 @keyword element_type: The type of GUI element to create. If set to 'default', the wx.TextCtrl element with a button to bring up a dialog with ListCtrl will be used. If set to 'combo_list', the special gui.components.combo_list.Combo_list element will be used.
63 @type element_type: str
64 @keyword seq_type: The type of Python sequence. This should be one of 'list' or 'tuple'.
65 @type seq_type: str
66 @keyword value_type: The type of Python object that the value should be. This can be one of 'float', 'int', or 'str'.
67 @type value_type: str
68 @keyword dim: The dimensions that a list or tuple must conform to. For a 1D sequence, this can be a single value or a tuple of possible sizes. For a 2D sequence (a numpy matrix or list of lists), this must be a tuple of the fixed dimension sizes, e.g. a 3x5 matrix should be specified as (3, 5).
69 @type dim: int, tuple of int or None
70 @keyword min: For a SpinCtrl, the minimum value allowed.
71 @type min: int
72 @keyword max: For a SpinCtrl, the maximum value allowed.
73 @type max: int
74 @keyword sizer: The sizer to put the input field widget into.
75 @type sizer: wx.Sizer instance
76 @keyword titles: The titles of each of the elements of the fixed dimension elements.
77 @type titles: list of str
78 @keyword desc: The text description.
79 @type desc: str
80 @keyword combo_choices: The list of choices to present to the user. This is only used if the element_type is set to 'combo'.
81 @type combo_choices: list of str
82 @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.
83 @type combo_data: list
84 @keyword combo_list_min: The minimum length for the Combo_list object.
85 @type combo_list_min: int or None
86 @keyword tooltip: The tooltip which appears on hovering over the text or input field.
87 @type tooltip: str
88 @keyword divider: The position of the divider.
89 @type divider: int
90 @keyword padding: Spacing to the left and right of the widgets.
91 @type padding: int
92 @keyword spacer: The amount of spacing to add below the field in pixels. If None, a stretchable spacer will be used.
93 @type spacer: None or int
94 @keyword height_element: The height in pixels of the GUI element.
95 @type height_element: int
96 @keyword single_value: A flag which if True will cause single input values to be treated as single values rather than a list or tuple.
97 @type single_value: bool
98 @keyword read_only: A flag which if True means that the text of the element cannot be edited.
99 @type read_only: bool
100 @keyword can_be_none: A flag which specifies if the element is allowed to have the None value.
101 @type can_be_none: bool
102 """
103
104
105 self.parent = parent
106 self.name = name
107 self.default = default
108 self.element_type = element_type
109 self.seq_type = seq_type
110 self.value_type = value_type
111 self.dim = dim
112 self.min = min
113 self.max = max
114 self.titles = titles
115 self.single_value = single_value
116 self.can_be_none = can_be_none
117
118
119 if value_type in ['float', 'num']:
120 self.convert_from_gui = gui_to_float
121 self.convert_to_gui = float_to_gui
122 elif value_type == 'int':
123 self.convert_from_gui = gui_to_int
124 self.convert_to_gui = int_to_gui
125 elif value_type == 'str':
126 self.convert_from_gui = gui_to_str
127 self.convert_to_gui = str_to_gui
128 else:
129 self.convert_from_gui = gui_to_py
130 self.convert_to_gui = py_to_gui
131
132
133 if seq_type == 'list':
134 self.convert_from_gui_seq = gui_to_list
135 self.convert_to_gui_seq = list_to_gui
136 elif seq_type == 'tuple':
137 self.convert_from_gui_seq = gui_to_tuple
138 self.convert_to_gui_seq = tuple_to_gui
139 else:
140 raise RelaxError("Unknown sequence type '%s'." % seq_type)
141
142
143 if self.element_type == 'default':
144
145 if read_only == None:
146 read_only = False
147
148
149 sub_sizer = wx.BoxSizer(wx.HORIZONTAL)
150
151
152 sub_sizer.AddSpacer(padding)
153
154
155 text = wx.StaticText(parent, -1, desc, style=wx.ALIGN_LEFT)
156 text.SetFont(font.normal)
157 sub_sizer.Add(text, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 0)
158
159
160 if not divider:
161 raise RelaxError("The divider position has not been supplied.")
162
163
164 dc = wx.ScreenDC()
165 dc.SetFont(font.normal)
166 x, y = dc.GetTextExtent(desc)
167 if dep_check.wx_classic:
168 sub_sizer.AddSpacer((divider - x, 0))
169 else:
170 sub_sizer.AddSpacer(int(divider - x))
171
172
173 self._field = wx.TextCtrl(parent, -1, '')
174 self._field.SetMinSize((50, height_element))
175 self._field.SetFont(font.normal)
176 sub_sizer.Add(self._field, 1, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0)
177
178
179 if read_only:
180 self._field.SetEditable(False)
181 colour = parent.GetBackgroundColour()
182 self._field.SetOwnBackgroundColour(colour)
183
184
185 sub_sizer.AddSpacer(5)
186
187
188 button = wx.BitmapButton(parent, -1, wx.Bitmap(fetch_icon('oxygen.actions.edit-rename', "16x16"), wx.BITMAP_TYPE_ANY))
189 button.SetMinSize((-1, height_element))
190 button.SetToolTip(wx.ToolTip("Edit the values."))
191 sub_sizer.Add(button, 0, wx.ADJUST_MINSIZE|wx.ALIGN_CENTER_VERTICAL, 0)
192 parent.Bind(wx.EVT_BUTTON, self.open_dialog, button)
193
194
195 sub_sizer.AddSpacer(padding)
196
197
198 sizer.Add(sub_sizer, 1, wx.EXPAND|wx.ALL, 0)
199
200
201 if spacer == None:
202 sizer.AddStretchSpacer()
203 else:
204 sizer.AddSpacer(spacer)
205
206
207 if tooltip:
208 text.SetToolTip(wx.ToolTip(tooltip))
209 self._field.SetToolTip(wx.ToolTip(tooltip))
210
211
212 if self.default is not None:
213 self._field.SetValue(self.convert_to_gui_seq(self.default))
214
215
216 elif self.element_type == 'combo_list':
217
218 if read_only == None:
219 read_only = False
220
221
222 if combo_list_min == None:
223 combo_list_min = 1
224
225
226 self._field = Combo_list(parent, sizer, desc, value_type=value_type, min_length=combo_list_min, choices=combo_choices, data=combo_data, default=default, tooltip=tooltip, read_only=read_only, can_be_none=can_be_none)
227
228
229 else:
230 raise RelaxError("Unknown element type '%s'." % self.element_type)
231
232
234 """Special method for clearing or resetting the GUI element."""
235
236
237 if self.element_type in ['default', 'combo_list']:
238 self._field.Clear()
239
240
242 """Special method for returning the sequence values of the GUI element.
243
244 @return: The sequence of values.
245 @rtype: sequence type
246 """
247
248
249 value = self._field.GetValue()
250
251
252 if self.element_type == 'combo_list':
253
254 if value == [] or value == None:
255 return None
256
257
258 else:
259
260 value_set = False
261 if self.single_value:
262 try:
263
264 value = self.convert_from_gui(value)
265
266
267 if value == None and self.can_be_none:
268 value_set = True
269 elif self.value_type == None:
270 value_set = True
271 elif self.value_type in ['float', 'num']:
272 if isinstance(value, int) or isinstance(value, float):
273 value_set = True
274 elif self.value_type == 'int':
275 if isinstance(value, int):
276 value_set = True
277 elif self.value_type == 'str':
278 if self.seq_type == 'list' and value[0] != '[':
279 value_set = True
280 elif self.seq_type == 'tuple' and value[0] != '(':
281 value_set = True
282 except:
283 pass
284
285
286 if not value_set:
287 try:
288 value = self.convert_from_gui_seq(value)
289
290
291 except RelaxError:
292 if self.can_be_none:
293 value = None
294 elif self.seq_type == 'list':
295 value = []
296 else:
297 value = ()
298
299
300 except:
301 value = None
302
303
304 if value == None:
305 return None
306
307
308 if self.single_value:
309 if (isinstance(value, list) or isinstance(value, tuple)) and len(value) == 1:
310 value = value[0]
311
312
313 elif value != None:
314 if self.seq_type == 'list' and not isinstance(value, list):
315 value = [value]
316 elif self.seq_type == 'tuple' and not isinstance(value, tuple):
317 value = (value,)
318
319
320 if not self.single_value and len(value) == 0:
321 return None
322
323
324 return value
325
326
327 - def SetValue(self, value=None, index=None):
328 """Special method for setting the value of the GUI element.
329
330 @keyword value: The value to set.
331 @type value: value or list of values
332 @keyword index: The index of the value to set, if the full list is not given.
333 @type index: int or None
334 """
335
336
337 if self.element_type == 'combo_list':
338 self._field.SetValue(value=value, index=index)
339
340
341 else:
342
343 if self.single_value and isinstance(value, list) and len(value) == 1:
344 value = value[0]
345
346
347 self._field.SetValue(self.convert_to_gui_seq(value))
348
349
350 - def UpdateChoices(self, combo_choices=None, combo_data=None, combo_default=None):
351 """Special wizard method for updating the list of choices in a ComboBox type element.
352
353 @keyword combo_choices: The list of choices to present to the user. This is only used if the element_type is set to 'combo_list'.
354 @type combo_choices: list of str
355 @keyword combo_data: The data returned by a call to GetValue(). This is only used if the element_type is set to 'combo_list'. 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.
356 @type combo_data: list
357 @keyword combo_default: The default value of the ComboBox. This is only used if the element_type is set to 'combo_list'.
358 @type combo_default: str or None
359 """
360
361
362 if self.element_type == 'combo_list':
363 self._field.UpdateChoices(combo_choices=combo_choices, combo_data=combo_data, combo_default=combo_default)
364
365
367 """Open a special dialog for inputting a list of text values.
368
369 @param event: The wx event.
370 @type event: wx event
371 """
372
373
374 self.selection_win_show()
375
376
377 self.selection_win_data()
378
379
380 self.sel_win.Destroy()
381 del self.sel_win
382
383
385 """Extract the data from the selection window."""
386
387
388 value = self.sel_win.GetValue()
389
390
391 if value == None or not len(value):
392 self.Clear()
393
394
395 else:
396 self.SetValue(value)
397
398
400 """Show the selection window."""
401
402
403 if hasattr(self, 'sel_win'):
404 self.sel_win.Destroy()
405 del self.sel_win
406
407
408 self.sel_win = Sequence_window(parent=self.parent, name=self.name, seq_type=self.seq_type, value_type=self.value_type, titles=self.titles, dim=self.dim)
409
410
411 self.sel_win.SetValue(self.GetValue())
412
413
414 if status.show_gui:
415 self.sel_win.ShowModal()
416 self.sel_win.Close()
417
418
419
420 -class Sequence_list_ctrl(wx.ListCtrl, wx.lib.mixins.listctrl.TextEditMixin, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
421 """The string list ListCtrl object."""
422
424 """Initialise the control.
425
426 @param parent: The parent window.
427 @type parent: wx.Frame instance
428 """
429
430
431 wx.ListCtrl.__init__(self, parent, -1, style=wx.BORDER_SUNKEN|wx.LC_REPORT|wx.LC_HRULES|wx.LC_VRULES)
432 wx.lib.mixins.listctrl.TextEditMixin.__init__(self)
433 wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)
434
435
436 self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.begin_label_edit)
437
438
440 """Catch edits to make the first column read only.
441
442 @param event: The wx event.
443 @type event: wx event
444 """
445
446
447 if event.m_col == 0:
448 event.Veto()
449
450
451 else:
452 event.Skip()
453
454
455
457 """The Python sequence object editor window."""
458
459
460 SIZE = (800, 600)
461
462
463 BORDER = 10
464
465
466 SIZE_BUTTON = (150, 33)
467
468 - def __init__(self, parent=None, name='', seq_type='list', value_type='str', dim=None, titles=None):
469 """Set up the string list editor window.
470
471 @keyword parent: The parent GUI element.
472 @type parent: wx.Window instance or None
473 @keyword name: The name of the window.
474 @type name: str
475 @keyword seq_type: The type of Python sequence. This should be one of 'list' or 'tuple'.
476 @type seq_type: str
477 @keyword value_type: The type of Python data expected in the sequence. This should be one of 'float', 'int', or 'str'.
478 @type value_type: str
479 @keyword dim: The fixed dimension that the sequence must conform to.
480 @type dim: int or None
481 @keyword titles: The titles of each of the elements of the fixed dimension elements.
482 @type titles: list of str
483 """
484
485
486 self.name = name
487 self.seq_type = seq_type
488 self.value_type = value_type
489 self.dim = dim
490 self.titles = titles
491
492
493 if value_type in ['float', 'num']:
494 self.convert_from_gui = gui_to_float
495 self.convert_to_gui = float_to_gui
496 elif value_type == 'int':
497 self.convert_from_gui = gui_to_int
498 self.convert_to_gui = int_to_gui
499 elif value_type == 'str':
500 self.convert_from_gui = gui_to_str
501 self.convert_to_gui = str_to_gui
502 else:
503 raise RelaxError("Unknown base data type '%s'." % value_type)
504
505
506 if not hasattr(self, 'variable_length'):
507 self.variable_length = False
508 self.offset = 0
509 if dim == None:
510 self.variable_length = True
511 self.offset = 1
512
513
514 title = "Edit the %s values." % name
515
516
517 wx.Dialog.__init__(self, parent, id=-1, title=title)
518
519
520 self.width = self.SIZE[0] - 2*self.BORDER
521
522
523 self.SetSize(self.SIZE)
524 if not dep_check.wx_classic and status.show_gui:
525 self.Centre()
526 self.SetFont(font.normal)
527
528
529 main_sizer = wx.BoxSizer(wx.VERTICAL)
530
531
532 self.SetSizer(main_sizer)
533
534
535 sizer = add_border(main_sizer, border=self.BORDER, packing=wx.VERTICAL)
536
537
538 self.add_list(sizer)
539
540
541 sizer.AddSpacer(self.BORDER)
542
543
544 self.add_buttons(sizer)
545
546
547 if not self.sequence.GetItemCount():
548 self.add_element()
549
550
552 """Return the values as a sequence of values.
553
554 @return: The sequence of values.
555 @rtype: sequence type
556 """
557
558
559 values = []
560
561
562 for i in range(self.sequence.GetItemCount()):
563
564 item = self.sequence.GetItem(i, col=1)
565 text = item.GetText()
566
567
568 try:
569 value = self.convert_from_gui(text)
570 except:
571 value = None
572 values.append(value)
573
574
575 if self.seq_type == 'tuple':
576 values = tuple(values)
577
578
579 empty = True
580 for i in range(len(values)):
581 if values[i] != None:
582 empty = False
583 break
584
585
586 if empty:
587 return None
588
589
590 return values
591
592
594 """Set up the list values.
595
596 @param values: The list of values to add to the list.
597 @type values: list of str or None
598 """
599
600
601 if values == None:
602 return
603
604
605 if not self.variable_length and is_list(values) and len(values) != self.dim:
606 return
607
608
609 try:
610 len(values)
611 except TypeError:
612 if self.seq_type == 'list':
613 values = [values]
614 elif self.seq_type == 'tuple':
615 values = (values,)
616
617
618 for i in range(len(values)):
619
620 if not self.variable_length:
621 if dep_check.wx_classic:
622 self.sequence.SetStringItem(i, 1, self.convert_to_gui(values[i]))
623 else:
624 self.sequence.SetItem(i, 1, self.convert_to_gui(values[i]))
625
626
627 else:
628
629 if i != 0:
630
631 if dep_check.wx_classic:
632 self.sequence.InsertStringItem(i, int_to_gui(i+1))
633 else:
634 self.sequence.InsertItem(i, int_to_gui(i+1))
635
636
637 if dep_check.wx_classic:
638 self.sequence.SetStringItem(i, 1, self.convert_to_gui(values[i]))
639 else:
640 self.sequence.SetItem(i, 1, self.convert_to_gui(values[i]))
641
642
699
700
724
725
727 """Set up the list control.
728
729 @param sizer: A sizer object.
730 @type sizer: wx.Sizer instance
731 """
732
733
734 self.sequence = Sequence_list_ctrl(self)
735
736
737 title = "%s%s" % (self.name[0].upper(), self.name[1:])
738
739
740 if self.titles:
741 self.sequence.InsertColumn(0, "Title")
742 self.sequence.SetColumnWidth(0, 200)
743 else:
744 self.sequence.InsertColumn(0, "Number")
745 self.sequence.SetColumnWidth(0, 70)
746
747
748 self.sequence.InsertColumn(1, title)
749 self.sequence.SetColumnWidth(1, wx.LIST_AUTOSIZE)
750
751
752 sizer.Add(self.sequence, 1, wx.ALL|wx.EXPAND, 0)
753
754
755 if not self.variable_length:
756 for i in range(self.dim):
757
758 self.add_element()
759
760
761 if self.titles:
762 if dep_check.wx_classic:
763 self.sequence.SetStringItem(i, 0, str_to_gui(self.titles[i]))
764 else:
765 self.sequence.SetItem(i, 0, str_to_gui(self.titles[i]))
766
767
768 else:
769 if dep_check.wx_classic:
770 self.sequence.SetStringItem(i, 0, int_to_gui(i+1))
771 else:
772 self.sequence.SetItem(i, 0, int_to_gui(i+1))
773
774
776 """Close the window.
777
778 @param event: The wx event.
779 @type event: wx event
780 """
781
782
783 self.Close()
784
785
787 """Remove the last item from the list.
788
789 @param event: The wx event.
790 @type event: wx event
791 """
792
793
794 item = self.sequence.GetItemCount()
795 self.sequence.DeleteItem(item-1)
796
797
798 if not self.sequence.GetItemCount():
799 self.add_element()
800
801
803 """Remove all items from the list.
804
805 @param event: The wx event.
806 @type event: wx event
807 """
808
809
810 self.sequence.DeleteAllItems()
811
812
813 self.add_element()
814