1 """This modules implements the atom selection dialog used in almost all nMOLDYN analysis.
2
3 The atom selection can be performed for various purposes such as selection of atoms for the simulation, selection
4 of hydrogen atoms to deuterate or selection of several group of atoms on which an analysis will be performed
5 collectively.
6
7 Classes:
8 * SelectionDialog: sets up a selection dialog in the scope of an analysis dialog.
9 """
10
11
12 import copy
13 import os
14 import sys
15
16
17 import Dialog
18 from Tkinter import *
19 from tkFileDialog import askopenfilename
20
21
22 from nMOLDYN.Core.Logger import LogMessage
23 from nMOLDYN.GUI.Widgets import ComboFileBrowser, ComboFrame, ComboListbox, ComboRadiobutton, ComboText
24
26 """Sets up a dialog from which the user can perform an atom selection.
27 """
28
29 - def __init__(self, parent, selectionType, univContents):
30 """The constructor.
31
32 @param parent: the parent widget.
33
34 @param selectionType: a string being one of 'subset', 'deuteration' or 'group' specifying the
35 atom selection type thatwill be performed.
36 @type selectionType: string
37
38 @param univContents: a dictionnary that contains the universe contents.
39 @type univContents: dict
40 """
41
42 Toplevel.__init__(self, parent)
43 self.transient(parent)
44
45 self.title('%s selection dialog' % selectionType)
46
47 self.parent = parent
48
49
50 self.selectionType = selectionType
51
52
53 self.univContents = copy.deepcopy(univContents)
54
55
56
57 self.keywords = set()
58 for v in self.univContents.values():
59 self.keywords.update(v.keys())
60
61
62 self.selection = {}
63
64
65 self.currentSelection = {}
66
67
68
69
70
71
72 if self.selectionType == 'group':
73 self.selectionString = self.parent.widgets['group'].getValue()
74 self.selectedPrefixName = None
75
76 elif self.selectionType == 'triplet':
77 self.selectionType = 'group'
78 self.selectionString = self.parent.widgets['triplet'].getValue()
79 self.selectedPrefixName = None
80
81 elif self.selectionType == 'bond':
82 self.selectionType = 'group'
83 self.selectionString = self.parent.widgets['bond'].getValue()
84 self.selectedPrefixName = None
85
86 elif self.selectionType == 'subset':
87 self.selectionString = self.parent.widgets['subset'].getValue()
88 self.selectedPrefixName = self.selectionType
89
90 elif self.selectionType == 'deuteration':
91 self.selectionString = self.parent.widgets['deuteration'].getValue()
92 self.selectedPrefixName = self.selectionType
93
94
95 self.selectedObjectName = None
96
97
98 self.selectedKeyword = None
99
100 body = Frame(self)
101 self.initial_focus = self.body(body)
102 body.grid(row = 0, column = 0, sticky = EW)
103
104 self.buttonbox()
105
106 self.grab_set()
107
108 if not self.initial_focus:
109 self.initial_focus = self
110
111 self.protocol("WM_DELETE_WINDOW", self.cancel)
112
113 self.resizable(width = NO, height = NO)
114
115 self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
116
117 self.initial_focus.focus_set()
118
119 self.wait_window(self)
120
121 - def body(self, master):
122 """Create dialog body. Return widget that should have initial focus.
123 """
124
125 self.settingsFrame = LabelFrame(master, text = 'Settings', bd = 2, relief = GROOVE)
126 self.settingsFrame.grid(row = 0, column = 0, sticky = EW, padx = 3, pady = 3)
127 self.settingsFrame.grid_columnconfigure(0, weight = 1)
128
129 self.selectionMediasRadio = ComboRadiobutton(self.settingsFrame,\
130 frameLabel = "Selection media",\
131 contents = ["from a selection file",\
132 "from the loaded trajectory",\
133 "from an expression string"],\
134 layout = (1,3),\
135 default = 1)
136 [r.config(width = 22, command = self.changeSelectionMedia) for r in self.selectionMediasRadio.radio]
137 self.selectionMediasRadio.grid(row = 0, column = 0, padx = 2, pady = 5, sticky = EW)
138
139
140 self.selectionFrame = Frame(self.settingsFrame, bd = 2, relief = GROOVE, width = 1000, height = 300)
141 self.selectionFrame.grid(row = 1, column = 0, padx = 3, pady = 3, sticky = 'NEW')
142 self.selectionFrame.grid_propagate(0)
143 self.selectionFrame.grid_columnconfigure(0, weight = 1)
144 self.selectionFrame.grid_rowconfigure(10, weight = 1)
145
146
147 self.fileBrowser = ComboFileBrowser(self.selectionFrame,\
148 frameLabel = 'Selection from a selection file',\
149 command = self.selectFromFile)
150 self.fileBrowser.entry.config(width = 100)
151 self.fileBrowser.entry.bind('<Return>', self.selectFromFile)
152
153
154 self.objectBrowser = ComboFrame(self.selectionFrame, frameLabel = 'Selection from the loaded trajectory')
155 self.objectBrowser.grid(row = 0, column = 0, padx = 3, pady = 3, sticky = EW)
156 self.objectBrowser.grid_columnconfigure(0, weight = 1)
157 self.objectBrowser.grid_columnconfigure(1, weight = 1)
158 self.objectBrowser.grid_columnconfigure(2, weight = 1)
159
160 if self.selectionType == 'group':
161 self.objectBrowser.grid_columnconfigure(3, weight = 1)
162 self.objectBrowser.grid_columnconfigure(4, weight = 1)
163
164 f = Frame(self.objectBrowser, bd = 0)
165 f.grid(row = 0, column = 0, sticky = EW)
166
167 self.prefixName = ComboListbox(f, frameLabel = 'Group number', contents = ['group1'])
168 self.prefixName.lb.config(width = 8, height = 8, exportselection = 0, selectmode = SINGLE)
169
170 Button(f, width = 6, text = 'New group', command = self.createNewGroup).grid(row = 1, column = 0, padx = 2, pady = 2, sticky = EW)
171
172 f.grid_rowconfigure(0, weight = 1)
173
174 self.prefixName.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = EW)
175 self.prefixName.grid_columnconfigure(0, weight = 1)
176 self.prefixName.lb.bind('<ButtonRelease-1>', self.selectPrefixName)
177 self.prefixName.lb.bind('<Double-1>', self.deletePrefixName)
178
179 colNum = 1
180
181 else:
182 colNum = 0
183
184
185 self.objectName = ComboListbox(self.objectBrowser,\
186 frameLabel = 'Object name',\
187 contents = sorted(self.univContents.keys()))
188 self.objectName.grid(row = 0, column = colNum, padx = 2, pady = 2, sticky = EW)
189 self.objectName.grid_columnconfigure(0, weight = 1)
190 self.objectName.lb.config(width = 20, exportselection = 0, selectmode = SINGLE)
191
192 self.objectName.lb.bind('<ButtonRelease-1>', self.selectObjectName)
193 self.objectName.lb.bind('<Double-1>', self.deleteObjectName)
194
195 if self.selectionType == 'group':
196
197 self.groupingLevel = ComboListbox(self.objectBrowser,'Grouping level')
198 self.groupingLevel.grid(row = 0, column = colNum+1, padx = 2, pady = 2, sticky = EW)
199 self.groupingLevel.grid_columnconfigure(0, weight = 1)
200 self.groupingLevel.lb.config(width = 20, exportselection = 0, selectmode = SINGLE)
201 self.groupingLevel.lb.bind('<ButtonRelease-1>', self.selectGroupingLevel)
202 colNum += 1
203
204
205 self.selectionKeyword = ComboListbox(self.objectBrowser, 'Selection keyword')
206 self.selectionKeyword.grid(row = 0, column = colNum+1, padx = 2, pady = 2, sticky = EW)
207 self.selectionKeyword.grid_columnconfigure(0, weight = 1)
208 self.selectionKeyword.lb.config(width = 20, exportselection = 0, selectmode = SINGLE)
209 self.selectionKeyword.lb.bind('<ButtonRelease-1>', self.selectKeyword)
210
211
212 self.selectionValue = ComboListbox(self.objectBrowser, 'Selection value')
213 self.selectionValue.grid(row = 0, column = colNum+2, padx = 2, pady = 2, sticky = EW)
214 self.selectionValue.grid_columnconfigure(0, weight = 1)
215 self.selectionValue.lb.config(width = 40, exportselection = 0, selectmode = MULTIPLE)
216 self.selectionValue.lb.bind('<ButtonRelease-1>', self.selectValue)
217 self.selectionValue.lb.bind('<Control-a>', self.selectValue)
218
219
220 selectionButtons = ComboFrame(self.objectBrowser, frameLabel = 'Linkers')
221 selectionButtons.grid(row = 1, column = 0, columnspan = 5, padx = 2, pady = 2, sticky = EW)
222
223
224 Button(selectionButtons, text = 'Clear', width = 4, command = self.clear).grid(row = 0, column = 0)
225 Button(selectionButtons, text = '(' , width = 4, command = lambda : self.appendLinker('(')).grid(row = 0, column = 1)
226 Button(selectionButtons, text = ')' , width = 4, command = lambda : self.appendLinker(')')).grid(row = 0, column = 2)
227 Button(selectionButtons, text = 'AND' , width = 4, command = lambda : self.appendLinker('AND')).grid(row = 0, column = 3)
228 Button(selectionButtons, text = 'OR' , width = 4, command = lambda : self.appendLinker('OR')).grid(row = 0, column = 4)
229
230
231 self.expressionBrowser = ComboText(self.selectionFrame, 'Selection from an expression string')
232 self.expressionBrowser.text.config(height = 10, state = NORMAL)
233 self.expressionBrowser.text.bind('<Return>', self.selectFromExpression)
234
235 self.selectionPreview = ComboText(self.settingsFrame, 'Selection preview')
236 self.selectionPreview.grid(row = 2, column = 0, padx = 3, pady = 3, sticky = EW)
237 self.selectionPreview.grid_columnconfigure(0, weight = 1)
238 self.selectionPreview.text.config(height = 10, font = ('Courier', '12'))
239 self.selectionPreview.text.tag_config('prefix' , foreground = 'pink' , font = ('Courier','12','bold'))
240 self.selectionPreview.text.tag_config('italic' , foreground = 'black' , font = ('Courier','12','normal', 'italic'))
241 self.selectionPreview.text.tag_config('bold' , foreground = 'black' , font = ('Courier','12','bold'))
242 self.selectionPreview.text.tag_config('objectname', foreground = 'red' , font = ('Courier','12','bold'))
243 self.selectionPreview.text.tag_config('filename' , foreground = 'blue' , font = ('Courier','12','bold'))
244 self.selectionPreview.text.tag_config('expression', foreground = 'green' , font = ('Courier','12','bold'))
245 self.selectionPreview.text.tag_config('()' , foreground = 'black' , font = ('Courier','12','bold'))
246 self.selectionPreview.text.tag_config('AND' , foreground = 'purple', font = ('Courier','12','bold'))
247 self.selectionPreview.text.tag_config('OR' , foreground = 'purple', font = ('Courier','12','bold'))
248
249 self.displaySelectionString()
250
251 return None
252
269
270
271 - def ok(self, event = None):
272 """This method is called when the user clicks on the 'OK' button of the selection editor
273 dialog. It closes the selection editor dialog and build the selection string.
274 """
275
276 if not self.validate():
277 self.initial_focus.focus_set()
278 return
279
280 self.withdraw()
281 self.update_idletasks()
282
283 self.apply()
284
285
286 self.parent.focus_set()
287 self.destroy()
288
289 - def cancel(self, event = None):
290 """Cancel the selection setting up the selection string to a selection type-dependant value.
291 """
292
293 if self.selectionType == 'group':
294 self.selectionString = 'all'
295
296 elif self.selectionType == 'subset':
297 self.selectionString = 'all'
298
299 elif self.selectionType == 'deuteration':
300 self.selectionString = 'no'
301
302
303 self.parent.focus_set()
304 self.destroy()
305
308
310 """Performs a last checking of the selection string before closing the selection dialog.
311 """
312
313
314
315 for key in self.selection.keys():
316
317
318 if key in ['filename','expression']:
319 return
320
321 for subkey in self.selection[key].keys():
322 if self.selection[key][subkey]:
323 if self.selection[key][subkey][-1] in ['AND', 'OR']:
324 del self.selection[key][subkey][-1]
325 if not self.selection[key][subkey]:
326 del self.selection[key][subkey]
327 else:
328 del self.selection[key][subkey]
329
330 if not self.selection:
331 return
332
333
334 self.buildSelectionString()
335
337 """This method returns the self.selectionString class attributes.
338
339 Thanks to this method, the selection dialog can be used like any other ComboWidget for which the getValue
340 allows to fetch their contents.
341 """
342 return self.selectionString
343
345 """Sets the selection string to its default value. This value depends on the the selection type.
346 """
347
348 if self.selectionType == 'group':
349 self.selectionString = 'all'
350
351 elif self.selectionType == 'subset':
352 self.selectionString = 'all'
353
354 elif self.selectionType == 'deuteration':
355 self.selectionString = 'no'
356
384
386 """This methods clears all the listboxes of the 'Selection from the loaded trajectory' browser.
387 It resets the selection listboxes and their associated variables, it resets the selection
388 string, and updates the 'Selection preview' text widget.
389 """
390
391 self.selectedPrefixName = self.selectionType
392 if self.selectionType == 'group':
393 self.prefixName.lb.selection_clear(0, END)
394 self.groupingLevel.lb.selection_clear(0, END)
395
396 self.selection = {}
397
398 self.selectedObjectName = None
399
400
401 self.objectName.lb.selection_clear(0, END)
402
403 self.selectedKeyword = None
404 self.selectionKeyword.cleanup()
405
406 self.currentSelection = {}
407 self.selectionValue.cleanup()
408
409
410 self.buildSelectionString()
411
412
413 self.displaySelectionString()
414
416 """This method actually build the selection string out of the |self.selection| dictionnary.
417 """
418
419
420
421 if self.selection.has_key('filename'):
422 self.selectionString = 'filename %s' % self.selection['filename']
423
424 elif self.selection.has_key('expression'):
425 self.selectionString = 'expression %s' % self.selection['expression']
426
427 else:
428 self.selectionString = ''
429 for prefix in sorted(self.selection.keys()):
430
431
432 if prefix not in ['subset', 'deuteration']:
433 self.selectionString += prefix + ' '
434
435 if self.selectionType == 'group':
436 self.selectionString += 'groupinglevel %s ' % self.groupingLevel.lb.get(int(self.groupingLevel.lb.curselection()[0]))
437
438 sortedObjectNames = sorted(self.selection[prefix].keys())
439 for index1 in range(len(sortedObjectNames)):
440 objectName = sortedObjectNames[index1]
441 objectSelection = self.selection[prefix][objectName]
442
443 if objectSelection:
444 self.selectionString += 'objectname %s ' % objectName
445
446 for objSel in objectSelection:
447 if isinstance(objSel,dict):
448 sortedKeywords = sorted(objSel.keys())
449 for index in range(len(sortedKeywords)):
450 keyword = sortedKeywords[index]
451 value = objSel[keyword]
452 self.selectionString += '%s %s ' % (keyword, ','.join(value))
453 if index < len(sortedKeywords) - 1:
454 self.selectionString += 'OR '
455 else:
456 self.selectionString += objSel + ' '
457 if index1 < len(sortedObjectNames) - 1:
458 self.selectionString += 'OR '
459
460
461 self.selectionString = self.selectionString.strip()
462
464 """Displays in the 'Selection preview' textwidget the selection string under process.
465 """
466
467
468 self.selectionPreview.cleanup()
469
470
471 if not self.selectionString:
472 return
473
474
475 self.selectionPreview.insert(contents = self.selectionString)
476
477
478 for k in self.selection.keys():
479 self.selectionPreview.tag(k,'prefix')
480
481
482 for k in self.keywords:
483 self.selectionPreview.tag(k,'italic')
484
485
486 self.selectionPreview.tag('objectname','objectname')
487
488 self.selectionPreview.tag('filename','filename')
489
490 self.selectionPreview.tag('expression','expression')
491
492
493 self.selectionPreview.tag('(','()')
494 self.selectionPreview.tag(')','()')
495 self.selectionPreview.tag('AND','AND')
496 self.selectionPreview.tag('OR','OR')
497
499 """This callback performs a selection from a expression string by writing the expression in its corresponding text
500 widget.
501
502 The expression must be a set of valid ;-separated python instructions the last one being 'selection = ...'
503 as the selection string parser will search for the selection variables when executing the expression string.
504
505 To refer to the universe just use the variable 'self.universe'.
506 """
507
508 if not self.expressionBrowser.text.get('0.0', END).strip():
509 return
510
511
512 self.selection = {'expression' : self.expressionBrowser.text.get('0.0', END).strip()}
513
514
515 self.buildSelectionString()
516
517
518 self.displaySelectionString()
519
520 return 'break'
521
523 """This method/callback performs a selection from a file by selection the file from a browser.
524 """
525
526
527 if event:
528
529
530 filename = self.fileBrowser.getValue()
531
532
533 else:
534
535 filename = askopenfilename(parent = self, filetypes = [('NMS file','.nms'), ('Python file', '.py'), ('All files', '.*')])
536
537
538 if not filename:
539 self.fileBrowser.setValue('')
540 return
541
542
543 if not os.path.exists(filename):
544 self.fileBrowser.setValue('')
545 return
546
547
548 self.fileBrowser.setValue(filename)
549
550
551 self.selection = {'filename' : filename}
552
553
554 self.buildSelectionString()
555
556
557 self.displaySelectionString()
558
559 return 'break'
560
561
563 """This callback is called when the user clicks on one item of the 'Group number' listbox of the selection editor.
564 """
565
566
567 self.selectedObjectName = None
568
569
570 self.objectName.lb.selection_clear(0, END)
571
572
573 self.selectionKeyword.cleanup()
574
575
576 self.selectionValue.cleanup()
577
578
579 self.currentSelection = {}
580
581
582 if not self.prefixName.lb.curselection():
583 self.selectedPrefixName = None
584 else:
585
586 self.selectedPrefixName = self.prefixName.lb.get(int(self.prefixName.lb.curselection()[0]))
587
589 """This callback will remove the selection string associated to the selected prefix name.
590 """
591
592
593 if not self.prefixName.lb.curselection():
594 return
595
596
597 sel = self.prefixName.lb.get(int(self.prefixName.lb.curselection()[0]))
598
599
600 if self.selection.has_key(sel):
601 del self.selection[sel]
602
603
604 self.buildSelectionString()
605
606
607 self.displaySelectionString()
608
610 """This callback will create a new group entry in the 'Group number' listbox.
611 """
612
613
614 if self.selectionType != 'group':
615 return
616
617
618 self.prefixName.lb.insert(END, 'group' + str(len(self.prefixName.lb.get(0, END))+1))
619
621 """This callback is called when the user clicks on one item of the 'Object name' listbox of the
622 selection editor. It will display into the 'Selection keyword' listbox all the selection keywords
623 corresponding to the selected object type.
624 """
625
626
627 if not self.selectedPrefixName:
628 return
629
630
631 if not self.selection.has_key(self.selectedPrefixName):
632 self.selection[self.selectedPrefixName] = {}
633
634
635 if not self.objectName.lb.curselection():
636 return
637
638
639 self.selectedObjectName = self.objectName.lb.get(int(self.objectName.lb.curselection()[0]))
640
641
642
643 if not self.selection[self.selectedPrefixName].has_key(self.selectedObjectName):
644 self.selection[self.selectedPrefixName][self.selectedObjectName] = []
645
646 if self.selectionType == 'group':
647
648 self.groupingLevel.cleanup()
649 for gl in sorted(self.univContents[self.selectedObjectName]['groupinglevel'][1]):
650 self.groupingLevel.lb.insert(END, gl)
651 self.groupingLevel.lb.selection_set(self.univContents[self.selectedObjectName]['groupinglevel'][0])
652
653
654 self.selectionKeyword.cleanup()
655 for k in sorted(self.univContents[self.selectedObjectName].keys()):
656 if k in ['number','objectclass', 'groupinglevel']:
657 continue
658
659 self.selectionKeyword.lb.insert(END, k)
660
661
662 self.selectionValue.cleanup()
663
664
665 self.currentSelection = {}
666
676
678 """This callback is called whenever the user clicks on one item of the 'Selection keyword' listbox.
679 It will display in the 'Selection value' listbox the selection values available for the
680 selected keyword.
681 """
682
683
684 if not self.selectionKeyword.lb.curselection():
685 return
686
687
688 self.selectedKeyword = self.selectionKeyword.lb.get(int(self.selectionKeyword.lb.curselection()[0]))
689
690
691 self.selectionValue.cleanup()
692
693
694 values = self.univContents[self.selectedObjectName][self.selectedKeyword]
695
696
697 self.selectionValue.insert(values)
698
699
700 for index in range(len(values)):
701 value = values[index]
702 try:
703
704
705 if value in self.currentSelection[self.selectedKeyword]:
706 self.selectionValue.lb.selection_set(index)
707 except KeyError:
708 continue
709
711 """This method will delete the object name from the selection string if it was previously selected.
712 """
713
714
715 if not self.objectName.lb.curselection():
716 return
717
718
719 objectName = self.objectName.lb.get(int(self.objectName.lb.curselection()[0]))
720
721
722 for k in self.selection.keys():
723 if self.selection[k].has_key(objectName):
724 del self.selection[k][objectName]
725
726
727 self.buildSelectionString()
728
729
730 self.displaySelectionString()
731
733 """This callback is called whenever the user clicks on one entry of the 'Selection value' listbox.
734 It will update the current selection.
735 """
736
737
738 if event.keysym == 'a':
739 self.selectionValue.lb.selection_set(0, END)
740
741
742
743 if self.selectionValue.lb.curselection():
744 selectedValues = [self.selectionValue.lb.get(int(v)) for v in self.selectionValue.lb.curselection()]
745 self.currentSelection[self.selectedKeyword] = selectedValues
746
747
748
749 else:
750 if self.currentSelection.has_key(self.selectedKeyword):
751 del self.currentSelection[self.selectedKeyword]
752 else:
753 return
754
755
756 prefixName = self.selectedPrefixName
757
758
759 objectName = self.selectedObjectName
760
761
762 if not self.selection[prefixName][objectName]:
763 if self.currentSelection:
764
765 self.selection[prefixName][objectName].append(self.currentSelection)
766 else:
767
768 if isinstance(self.selection[prefixName][objectName][-1],dict):
769 if self.currentSelection:
770 self.selection[prefixName][objectName][-1] = self.currentSelection
771 else:
772 del self.selection[prefixName][objectName][-1]
773
774 else:
775 if self.currentSelection:
776 self.selection[prefixName][objectName].append(self.currentSelection)
777
778 reset = True
779 for k in self.selection.keys():
780 for kk in self.selection[k].keys():
781 if self.selection[k][kk]:
782 reset = False
783 else:
784 if reset:
785 self.clear()
786
787
788 self.buildSelectionString()
789
790
791 self.displaySelectionString()
792
794 """
795 This method is called when the user press the '(', ')', 'AND' or 'OR' buttons of the atom selection
796 dialog. It:
797 - checks whether the selected linker can be actually be appended
798 - appends the linker if so.
799 """
800
801
802 if self.selectedObjectName is None:
803 return
804
805
806
807
808
809 if linker not in ['(',')','AND','OR']:
810 return
811
812 selList = self.selection[self.selectedPrefixName][self.selectedObjectName]
813
814
815 if not selList:
816 if linker != '(':
817 return
818 else:
819
820 if linker == '(':
821 if selList[-1] not in ['AND','OR','(']:
822 return
823
824
825
826 elif linker == ')':
827 if selList.count('(') <= selList.count(')'):
828 return
829 if selList[-1] in ['AND', 'OR', '(']:
830 return
831
832 elif linker == 'AND':
833 if selList[-1] in ['AND','OR','(']:
834 return
835
836 elif linker == 'OR':
837 if selList[-1] in ['AND','OR','(']:
838 return
839
840
841 selList.append(linker)
842
843
844 self.selectionValue.lb.selection_clear(0, END)
845
846
847 self.currentSelection = {}
848
849
850 self.buildSelectionString()
851
852
853 self.displaySelectionString()
854