11import sys
22from functools import partial
3+ from typing import Optional # pylint: disable=unused-import
34
45from AnyQt .QtWidgets import QWidget , QGridLayout
5- from AnyQt .QtCore import Qt , QSortFilterProxyModel , QItemSelection , QItemSelectionModel
6+ from AnyQt .QtWidgets import QListView # pylint: disable=unused-import
7+ from AnyQt .QtCore import (
8+ Qt , QTimer , QSortFilterProxyModel , QItemSelection , QItemSelectionModel
9+ )
610
711from Orange .util import deprecated
812from Orange .widgets import gui , widget
@@ -77,6 +81,7 @@ def acceptsDropEvent(self, event):
7781
7882
7983class OWSelectAttributes (widget .OWWidget ):
84+ # pylint: disable=too-many-instance-attributes
8085 name = "Select Columns"
8186 description = "Select columns from the data table and assign them to " \
8287 "data features, classes or meta variables."
@@ -99,6 +104,20 @@ class Outputs:
99104
100105 def __init__ (self ):
101106 super ().__init__ ()
107+ # Schedule interface updates (enabled buttons) using a coalescing
108+ # single shot timer (complex interactions on selection and filtering
109+ # updates in the 'available_attrs_view')
110+ self .__interface_update_timer = QTimer (self , interval = 0 , singleShot = True )
111+ self .__interface_update_timer .timeout .connect (
112+ self .__update_interface_state )
113+ # The last view that has the selection for move operation's source
114+ self .__last_active_view = None # type: Optional[QListView]
115+
116+ def update_on_change (view ):
117+ # Schedule interface state update on selection change in `view`
118+ self .__last_active_view = view
119+ self .__interface_update_timer .start ()
120+
102121 self .controlArea = QWidget (self .controlArea )
103122 self .layout ().addWidget (self .controlArea )
104123 layout = QGridLayout ()
@@ -117,9 +136,7 @@ def dropcompleted(action):
117136 self .commit ()
118137
119138 self .available_attrs_view .selectionModel ().selectionChanged .connect (
120- partial (self .update_interface_state , self .available_attrs_view ))
121- self .available_attrs_view .selectionModel ().selectionChanged .connect (
122- partial (self .update_interface_state , self .available_attrs_view ))
139+ partial (update_on_change , self .available_attrs_view ))
123140 self .available_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
124141
125142 box .layout ().addWidget (self .available_attrs_view )
@@ -133,7 +150,7 @@ def dropcompleted(action):
133150
134151 self .used_attrs_view .setModel (self .used_attrs )
135152 self .used_attrs_view .selectionModel ().selectionChanged .connect (
136- partial (self . update_interface_state , self .used_attrs_view ))
153+ partial (update_on_change , self .used_attrs_view ))
137154 self .used_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
138155 box .layout ().addWidget (self .used_attrs_view )
139156 layout .addWidget (box , 0 , 2 , 1 , 1 )
@@ -145,7 +162,7 @@ def dropcompleted(action):
145162 Orange .data .ContinuousVariable ))
146163 self .class_attrs_view .setModel (self .class_attrs )
147164 self .class_attrs_view .selectionModel ().selectionChanged .connect (
148- partial (self . update_interface_state , self .class_attrs_view ))
165+ partial (update_on_change , self .class_attrs_view ))
149166 self .class_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
150167 self .class_attrs_view .setMaximumHeight (72 )
151168 box .layout ().addWidget (self .class_attrs_view )
@@ -157,7 +174,7 @@ def dropcompleted(action):
157174 acceptedType = Orange .data .Variable )
158175 self .meta_attrs_view .setModel (self .meta_attrs )
159176 self .meta_attrs_view .selectionModel ().selectionChanged .connect (
160- partial (self . update_interface_state , self .meta_attrs_view ))
177+ partial (update_on_change , self .meta_attrs_view ))
161178 self .meta_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
162179 box .layout ().addWidget (self .meta_attrs_view )
163180 layout .addWidget (box , 2 , 2 , 1 , 1 )
@@ -338,10 +355,16 @@ def move_from_to(self, src, dst, rows, exclusive=False):
338355
339356 self .commit ()
340357
358+ def __update_interface_state (self ):
359+ last_view = self .__last_active_view
360+ if last_view is not None :
361+ self .update_interface_state (last_view )
362+
341363 def update_interface_state (self , focus = None , selected = None , deselected = None ):
342364 for view in [self .available_attrs_view , self .used_attrs_view ,
343365 self .class_attrs_view , self .meta_attrs_view ]:
344- if view is not focus and not view .hasFocus () and self .selected_rows (view ):
366+ if view is not focus and not view .hasFocus () \
367+ and view .selectionModel ().hasSelection ():
345368 view .selectionModel ().clear ()
346369
347370 def selected_vars (view ):
@@ -358,7 +381,7 @@ def selected_vars(view):
358381 for var in available_types )
359382
360383 move_attr_enabled = (available_selected and all_primitive ) or \
361- attrs_selected
384+ attrs_selected
362385
363386 self .move_attr_button .setEnabled (bool (move_attr_enabled ))
364387 if move_attr_enabled :
@@ -375,6 +398,9 @@ def selected_vars(view):
375398 if move_meta_enabled :
376399 self .move_meta_button .setText (">" if available_selected else "<" )
377400
401+ self .__last_active_view = None
402+ self .__interface_update_timer .stop ()
403+
378404 def commit (self ):
379405 self .update_domain_role_hints ()
380406 if self .data is not None :
@@ -418,7 +444,7 @@ def send_report(self):
418444 self .report_items ((("Removed" , text ),))
419445
420446
421- def test_main (argv = None ):
447+ def main (argv = None ): # pragma: no cover
422448 from AnyQt .QtWidgets import QApplication
423449 if argv is None :
424450 argv = sys .argv
@@ -440,5 +466,6 @@ def test_main(argv=None):
440466 w .saveSettings ()
441467 return rval
442468
443- if __name__ == "__main__" :
444- sys .exit (test_main ())
469+
470+ if __name__ == "__main__" : # pragma: no cover
471+ sys .exit (main ())
0 commit comments