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