1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
|
# Copyright 2007-2008 Nanorex, Inc. See LICENSE file for details.
"""
PM_SelectionListWidget.py
@author: Ninad
@version: $Id$
@copyright: 2007-2008 Nanorex, Inc. All rights reserved.
TODO:
- Probably need to revise the tag instructions. At the moment it is confusing.
If a list widget item is selected from that widget, what should happen to the
corresponding item in the GLPane -- Should it get selected? or should it just
be tagged? or do both? Popular cad programs would just tag entities in the
glpane. But, in our case (Insert DNA Duplex for Rattlesnake user story V6)
we need to also select strands in the glpane when those are selected from the
list widget. So, in the current implementation, this is handled by a flag
'self._tagInstructions'. It is confusing because of the overuse of the
term 'select' . I have tried to resolve this by using terms 'pick' and unpick
and explicitely mentioning 'glpane' . But overall, needs discussion and
cleanup. see also: PM_SelectionListWidget.tagItems [-- Ninad 2007-11-12]
- the attr 'iconPath' needs to be defined for various objects. Need a better
name? (Example: see class Atom.iconPath that specifies the atom icon path
as a string)
- Review Changes to be done: (Bruce's email)
As for self._tagInstruction in PM_SelectionListWidget.py
-- things will be cleaner if the widget code does not know a lot of details
about how to manipulate display and model info in a graphicsMode. Also,
there are update issues about storing atom posns in the graphicsMode.list of
tag posns and then the user moves the atoms. So, what I suggest as a
refactoring at some point is for the widget to just be given a callback
function, so that whenever the set of selected list items is different,
it calls that function with the new list. Then the specific graphicsModes can
clear whatever they stored last time, then scan that list and do whatever they
want with it. No graphicsMode knowledge is needed in the widget, and whatever
atom posn updates are needed is handled entirely in the graphicsMode -- either
it updates the list of tag posns whenever model_changed, or it just stores a
list of atoms in the first place, not a list of their positions, so it uses up
to date posns each time it draws.
**Comments/Questions: Implementation of this callback function
-- where should this be defined? In the propMgr that initializes this list
widget? If so, it needs to be defined in each propMgr that will define this
listwidget (example: MotorPM and DnaDuplexPM each will need to define the
callback methods. In the current implementation, they just set a
'tag instruction' for this widget.)What if propMgr.model_changed also calls a
model_changed method defined in this widget?
"""
from PM.PM_ListWidget import PM_ListWidget
from PyQt4.Qt import QListWidgetItem
from PyQt4.Qt import SIGNAL
from PyQt4.Qt import QPalette
from PyQt4.Qt import QAbstractItemView
from PyQt4.Qt import Qt
from PM.PM_Colors import getPalette
from widgets.widget_helpers import RGBf_to_QColor
from utilities.constants import yellow, white
from utilities.icon_utilities import geticon
from utilities.debug import print_compact_traceback
TAG_INSTRUCTIONS = ['TAG_ITEM_IN_GLPANE',
'PICK_ITEM_IN_GLPANE',
'TAG_AND_PICK_ITEM_IN_GLPANE']
class PM_SelectionListWidget(PM_ListWidget):
"""
Appends a QListWidget (Qt) widget to the I{parentWidget},
a Property Manager group box. This is a selection list widget, that
means if you select the items in this list widget, the corresponding
item in the GLPane will be tagged or picked or both depending on the
'tag instructions'
@param _tagInstruction: Sets the tag instruction. Based on its value, the
items selected in the List widget will be either
tagged or picked or both in the glpane.
@type _tagInstruction: string
@param _itemDictionary: This QListWidgetItem object defines a 'key' of a
dictionary and the 'value' of this key is the object
specified by the 'item' itself. Example:
self._itemDictionary['C6'] = instance of class Atom.
@type _itemDictionary: dictionary
"""
def __init__(self,
parentWidget,
win,
label = '',
color = None,
heightByRows = 6,
spanWidth = False):
"""
Appends a QListWidget (Qt) widget to the I{parentWidget},
a Property Manager group box. This is a selection list widget, that
means if you select the items in this list widget, the corresponding
item in the GLPane will be tagged or picked or both depending on the
'tag instructions'
@param parentWidget: The parent group box containing this widget.
@type parentWidget: PM_GroupBox
@param win: Mainwindow object
@type win: MWSemantics
@param label: The label that appears to the left or right of the
checkbox.
If spanWidth is True, the label will be displayed on
its own row directly above the list widget.
To suppress the label, set I{label} to an
empty string.
@type label: str
@param color: color of the ListWidget
@type : Array
@param heightByRows: The height of the list widget.
@type heightByRows: int
@param spanWidth: If True, the widget and its label will span the width
of the group box. Its label will appear directly above
the widget (unless the label is empty) and is left
justified.
@type spanWidth: bool
@see: U{B{QListWidget}<http://doc.trolltech.com/4/qlistwidget.html>}
"""
self.win = win
self.glpane = self.win.glpane
#Note: self._tagInstruction and self._itemDictionary are instance
#variables and not class constants as we
#have many PM_SelectionListWidget objects (e.g. in Build Dna mode, we
# have Srand and Segment list widgets. Defining self._itemDictionary
#as a class constant will make class objects share it and create bugs.
self._tagInstruction = 'TAG_ITEM_IN_GLPANE'
self._itemDictionary = {}
#The following flag suppresses the itemSelectionChanged signal
#see self.updateSelection for more comments.
self._suppress_itemSelectionChanged_signal = False
#The following flag suppresses the itemChanged signal
#ItemChanged signal is emitted too frequently. We use this to know that
#the data of an item has changed...example : to know that the renaming
#operation of the widget is completed. When a widgetItem is renamed,
#we want to rename the corresponding object in the glpane (which is
#stored as a value in self._itemDictionary) As of 2008-04-16 this signal
#is the most convienent way to do it (in Qt4.2.3). If this flag
#is set to False, it simply returns from the method that gets called
#when itemItemChanged signal is sent. The flag is set to True
#while updating items in self.isertItems. When itemDoubleClicked signal
#is sent, the flag is explicitely set to False -- Ninad 2008-04-16
#@see: self.renameItemValue(),
#@self.editItem() (This is a QListWidget method)
self._suppress_itemChanged_signal = False
PM_ListWidget.__init__(self,
parentWidget,
label = '',
heightByRows = heightByRows,
spanWidth = spanWidth)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
#Assigning color to the widget -- to be revised. (The color should
#change only when the focus is inside this widget -- the color change
#will also suggest object(s) selected in glpane will be added as
#items in this widget (could be a selective addition depending on
# the use) -- Niand 2007-11-12
if color:
self.setAutoFillBackground(True)
self.setPalette(getPalette( None,
QPalette.Base,
color))
def deleteSelection(self):
"""
Remove the selected items from the list widget (and
self._itemDictionary)
"""
for key in self.selectedItems():
assert self._itemDictionary.has_key(key)
del self._itemDictionary[key]
def getItemDictonary(self):
"""
Returns the dictonary of self's items.
@see: MultipleSegments_PropertyManager.listWidget_keyPressEvent_delegate
for details.
"""
return self._itemDictionary
def connect_or_disconnect_signals(self, isConnect):
"""
Connect or disconnect widget signals sent to their slot methods.
This can be overridden in subclasses. By default it does nothing.
@param isConnect: If True the widget will send the signals to the slot
method.
@type isConnect: boolean
"""
if isConnect:
change_connect = self.win.connect
else:
change_connect = self.win.disconnect
change_connect(self,
SIGNAL('itemSelectionChanged()'),
self.tagItems)
change_connect(self,
SIGNAL('itemDoubleClicked ( QListWidgetItem *)'),
self.editItem)
change_connect(self,
SIGNAL('itemChanged ( QListWidgetItem *)'),
self.renameItemValue)
def editItem(self, item):
"""
Edit the widget item.
@see: self.insertItems for a comment
@see: self.renameItemValue()
@self.editItem() (This is a QListWidget method
"""
#explicitely set the flag to False for safety.
self._suppress_itemChanged_signal = False
PM_ListWidget.editItem(self, item)
def renameItemValue(self, item):
"""
slot method that gets called when itemChanged signal is emitted.
Example: 1. User double clicks an item in the strand list widget of the
BuildDna mode 2. Edits the name. 3.Hits Enter key or clicks outside the
selection to end rename operation.
During step1, itemDoubleClicked signal is emitted which calls self.editItem
and at the end of step3, it emits itemChanged signal which calls this
method
@self.editItem() (This is a QListWidget method)
"""
#See a detailed note in self.__init__ where the following flag is
#declared. The flag is set to True when
if self._suppress_itemChanged_signal:
return
if self._itemDictionary.has_key(item):
val = self._itemDictionary[item]
#Check if the 'val' (obj which this widgetitem represents) has an
#attr name.
if not hasattr(val, 'name'):
if not item.text():
#don't permit empty names -- doesn't make sense.
item.setText('name')
return
#Do the actual renaming of the 'val'
if item.text():
val.name = item.text()
self.win.win_update()
else:
#Don't allow assignment of a blank name
item.setText(val.name)
def insertItems(self, row, items, setAsDefault = True):
"""
Insert the <items> specified items in this list widget.
The list widget shows item name string , as a QListwidgetItem.
This QListWidgetItem object defines a 'key' of a dictionary
(self._itemDictionary) and the 'value' of this key is the object
specified by the 'item' itself.
Example: self._itemDictionary['C6'] = instance of class Atom.
@param row: The row number for the item.
@type row: int
@param items: A list of objects. These objects are treated as values in
the self._itemDictionary
@type items: list
@param setAsDefault: Not used here. See PM_ListWidget.insertItems where
it is used.
@see: self.renameItemValue() for a comment about
self._suppress_itemChanged_signal
"""
#delete unused argument. Should this be provided as an argument in this
#class method ?
del setAsDefault
#self.__init__ for a comment about this flag
self._suppress_itemChanged_signal = True
#Clear the previous contents of the self._itemDictionary
self._itemDictionary.clear()
#Clear the contents of this list widget, using QListWidget.clear()
#See U{<http://doc.trolltech.com/4.2/qlistwidget.html>} for details
self.clear()
for item in items:
if hasattr(item.__class__, 'name'):
itemName = item.name
else:
itemName = str(item)
listWidgetItem = QListWidgetItem(itemName, self)
#When we support editing list widget items , uncomment out the
#following line . See also self.editItems -- Ninad 2008-01-16
listWidgetItem.setFlags( listWidgetItem.flags()| Qt.ItemIsEditable)
if hasattr(item.__class__, 'iconPath'):
try:
listWidgetItem.setIcon(geticon(item.iconPath))
except:
print_compact_traceback()
self._itemDictionary[listWidgetItem] = item
#Reset the flag that temporarily suppresses itemChange signal.
self._suppress_itemChanged_signal = False
def setColor(self, color):
"""
Set the color of the widget to the one given by param color
@param color: new palette color of self.
"""
self.setAutoFillBackground(True)
color = RGBf_to_QColor(color)
self.setPalette(getPalette( None,
QPalette.Base,
color))
def resetColor(self):
"""
Reset the paletter color of the widget (and set it to white)
"""
self.setAutoFillBackground(True)
color = RGBf_to_QColor(white)
self.setPalette(getPalette( None,
QPalette.Base,
color))
def clearTags(self):
"""
Clear the previously drawn tags if any.
"""
### TODO: this should not go through the current graphicsMode,
# in case that belongs to a temporary command or to nullCommand
# (which would cause bugs). Rather, it should find "this PM's graphicsMode".
# [bruce 081002 comment]
self.glpane.graphicsMode.setDrawTags(tagPositions = ())
def setTagInstruction(self, tagInstruction = 'TAG_ITEM_IN_GLPANE'):
"""
Sets the client specified tag instruction.
If a list widget item is selected from that widget, what should happen
to the corresponding item in the GLPane --
Should it get selected? or should it just be tagged? or do both? This
is decided using the tag instruction.
"""
assert tagInstruction in TAG_INSTRUCTIONS
self._tagInstruction = tagInstruction
def tagItems(self):
"""
For the selected items in the list widget, tag and/or select the
corresponding item in the GLPane based on the self._tagInstruction
@see: self.setTagInstruction
"""
if self._suppress_itemSelectionChanged_signal:
return
graphicsMode = self.glpane.graphicsMode
# TODO: fix this, in the same way as described in self.clearTags.
# [bruce 081002 comment]
#Clear the previous tags if any
self.clearTags()
if self._tagInstruction != 'TAG_ITEM_IN_GLPANE':
#Unpick all list widget items in the 3D workspace
#if no modifier key is pressed. Earlier implementation
#used to only unpick items that were also present in the list
#widgets.But if we have multiple list widgets, example like
#in Build DNA mode, it can lead to confusion like in Bug 2681
#NOTE ABOUT A NEW POSSIBLE BUG:
#self.glpan.modkeys not changed when focus is inside the
#Property manager? Example: User holds down Shift key and starts
#selecting things inside -- Ninad 2008-03-18
if self.glpane.modkeys is None:
self.win.assy.unpickall_in_GLPane()
#Deprecated call of _unpick_all_listWidgetItems_in_glpane
#(deprecated on 2008-03-18
##self._unpick_all_listWidgetItems_in_glpane()
#Now pick the items selected in this list widget
self._pick_selected_listWidgetItems_in_glpane()
if self._tagInstruction != 'PICK_ITEM_IN_GLPANE':
tagPositions = []
#Note: method selectedItems() is inherited from QListWidget
#see: U{<http://doc.trolltech.com/4.2/qlistwidget.html>} for details
for key in self.selectedItems():
assert self._itemDictionary.has_key(key)
item = self._itemDictionary[key]
if isinstance(item, self.win.assy.DnaSegment):
end1, end2 = item.getAxisEndPoints()
for endPoint in (end1, end2):
if endPoint is not None:
tagPositions.append(endPoint)
elif hasattr(item.__class__, 'posn'):
tagPositions.append(item.posn())
if tagPositions:
graphicsMode.setDrawTags(tagPositions = tagPositions,
tagColor = yellow)
self.glpane.gl_update()
def _unpick_all_listWidgetItems_in_glpane(self):
"""
Deselect (unpick) all the items (object) in the GLPane that
correspond to the items in this list widget.
Deprecated as of 2008-03-18. See a comment in self.tagItems
"""
for item in self._itemDictionary.values():
if item.picked:
item.unpick()
def _pick_selected_listWidgetItems_in_glpane(self):
"""
If some items in the list widgets are selected (in the widget)
also select (pick) them from the glpane(3D workspace)
"""
for key in self.selectedItems():
assert self._itemDictionary.has_key(key)
item = self._itemDictionary[key]
if not item.picked:
item.pick()
def updateSelection(self, selectedItemList):
"""
Update the selected items in this selection list widget. The items
given by the parameter selectedItemList will get selected.
This suppresses the 'itemSelectionChanged signal because the items
are already selected in the 3D workspace and we just want to select
the corresponding items (QWidgetListItems) in this list widget.
@param selectedItemList: List of items provided by the client
that need to be selected in this list widget
@type selectedItemList: list
@see: B{BuildDna_PropertyManager.model_changed}
"""
#The following flag suppresses the itemSelectionChanged signal , thereby
#prevents self.tagItems from calling. This is done because the
#items selection was changed from the 3D workspace. After this, the
#selection state of the corresponding items in the list widget must be
#updated.
self._suppress_itemSelectionChanged_signal = True
for key, value in self._itemDictionary.iteritems():
if value in selectedItemList:
if not key.isSelected():
key.setSelected(True)
else:
if key.isSelected():
key.setSelected(False)
# Contrary to this method's docstring, it is possible that items
# in selectedItemList() are not selected in the glpane, so (re)pick
# them as a precaution. --Mark 2008-12-22
self._pick_selected_listWidgetItems_in_glpane()
self._suppress_itemSelectionChanged_signal = False
def clear(self):
"""
Clear everything inside this list widget including the
contents of self._itemDictionary and tags if any.
Overrides QListWidget.clear()
"""
self.clearTags()
#Clear the previous contents of the self._itemDictionary
self._itemDictionary.clear()
#Clear the contents of this list widget, using QListWidget.clear()
#See U{<http://doc.trolltech.com/4.2/qlistwidget.html>} for details
PM_ListWidget.clear(self)
def getPickedItem(self):
"""
Return the 'real' item picked (selected) inside this selection list
widget. The 'real' item is the object whose name appears inside the
selection list widget. (it does not return the 'QWidgetItem' but the
key.value() that actually stores the NE1 object)
NOTE: If there are more than one items selected, it returns only the
FIRST ITEM in the list. This class is designed to select only
a single item at a time , but in case this implementation changes,
this method should be revised.
@see: BuildDna_PropertyManager.assignStrandSequence
"""
#Using self.selectedItems() doesn't work for some reason! (when you
# select item , go to the sequence editor and hit assign button,
# it spits an error
pickedItem = None
selectedItemList = self.selectedItems()
key = selectedItemList[0]
pickedItem = self._itemDictionary[key]
return pickedItem
#for item in self._itemDictionary.values():
#if item.picked:
#return item
#return None
|