11import sys
22from functools import partial
3+ from itertools import chain
4+ from typing import Optional # pylint: disable=unused-import
35
46from AnyQt .QtWidgets import QWidget , QGridLayout
5- from AnyQt .QtCore import Qt , QSortFilterProxyModel , QItemSelection , QItemSelectionModel
7+ from AnyQt .QtWidgets import QListView # pylint: disable=unused-import
8+ from AnyQt .QtCore import (
9+ Qt , QTimer , QSortFilterProxyModel , QItemSelection , QItemSelectionModel
10+ )
611
712from Orange .util import deprecated
813from Orange .widgets import gui , widget
@@ -77,6 +82,7 @@ def acceptsDropEvent(self, event):
7782
7883
7984class OWSelectAttributes (widget .OWWidget ):
85+ # pylint: disable=too-many-instance-attributes
8086 name = "Select Columns"
8187 description = "Select columns from the data table and assign them to " \
8288 "data features, classes or meta variables."
@@ -99,6 +105,20 @@ class Outputs:
99105
100106 def __init__ (self ):
101107 super ().__init__ ()
108+ # Schedule interface updates (enabled buttons) using a coalescing
109+ # single shot timer (complex interactions on selection and filtering
110+ # updates in the 'available_attrs_view')
111+ self .__interface_update_timer = QTimer (self , interval = 0 , singleShot = True )
112+ self .__interface_update_timer .timeout .connect (
113+ self .__update_interface_state )
114+ # The last view that has the selection for move operation's source
115+ self .__last_active_view = None # type: Optional[QListView]
116+
117+ def update_on_change (view ):
118+ # Schedule interface state update on selection change in `view`
119+ self .__last_active_view = view
120+ self .__interface_update_timer .start ()
121+
102122 self .controlArea = QWidget (self .controlArea )
103123 self .layout ().addWidget (self .controlArea )
104124 layout = QGridLayout ()
@@ -117,9 +137,7 @@ def dropcompleted(action):
117137 self .commit ()
118138
119139 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 ))
140+ partial (update_on_change , self .available_attrs_view ))
123141 self .available_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
124142
125143 box .layout ().addWidget (self .available_attrs_view )
@@ -133,7 +151,7 @@ def dropcompleted(action):
133151
134152 self .used_attrs_view .setModel (self .used_attrs )
135153 self .used_attrs_view .selectionModel ().selectionChanged .connect (
136- partial (self . update_interface_state , self .used_attrs_view ))
154+ partial (update_on_change , self .used_attrs_view ))
137155 self .used_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
138156 box .layout ().addWidget (self .used_attrs_view )
139157 layout .addWidget (box , 0 , 2 , 1 , 1 )
@@ -145,7 +163,7 @@ def dropcompleted(action):
145163 Orange .data .ContinuousVariable ))
146164 self .class_attrs_view .setModel (self .class_attrs )
147165 self .class_attrs_view .selectionModel ().selectionChanged .connect (
148- partial (self . update_interface_state , self .class_attrs_view ))
166+ partial (update_on_change , self .class_attrs_view ))
149167 self .class_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
150168 self .class_attrs_view .setMaximumHeight (72 )
151169 box .layout ().addWidget (self .class_attrs_view )
@@ -157,7 +175,7 @@ def dropcompleted(action):
157175 acceptedType = Orange .data .Variable )
158176 self .meta_attrs_view .setModel (self .meta_attrs )
159177 self .meta_attrs_view .selectionModel ().selectionChanged .connect (
160- partial (self . update_interface_state , self .meta_attrs_view ))
178+ partial (update_on_change , self .meta_attrs_view ))
161179 self .meta_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
162180 box .layout ().addWidget (self .meta_attrs_view )
163181 layout .addWidget (box , 2 , 2 , 1 , 1 )
@@ -338,10 +356,16 @@ def move_from_to(self, src, dst, rows, exclusive=False):
338356
339357 self .commit ()
340358
359+ def __update_interface_state (self ):
360+ last_view = self .__last_active_view
361+ if last_view is not None :
362+ self .update_interface_state (last_view )
363+
341364 def update_interface_state (self , focus = None , selected = None , deselected = None ):
342365 for view in [self .available_attrs_view , self .used_attrs_view ,
343366 self .class_attrs_view , self .meta_attrs_view ]:
344- if view is not focus and not view .hasFocus () and self .selected_rows (view ):
367+ if view is not focus and not view .hasFocus () \
368+ and view .selectionModel ().hasSelection ():
345369 view .selectionModel ().clear ()
346370
347371 def selected_vars (view ):
@@ -358,7 +382,7 @@ def selected_vars(view):
358382 for var in available_types )
359383
360384 move_attr_enabled = (available_selected and all_primitive ) or \
361- attrs_selected
385+ attrs_selected
362386
363387 self .move_attr_button .setEnabled (bool (move_attr_enabled ))
364388 if move_attr_enabled :
@@ -375,6 +399,9 @@ def selected_vars(view):
375399 if move_meta_enabled :
376400 self .move_meta_button .setText (">" if available_selected else "<" )
377401
402+ self .__last_active_view = None
403+ self .__interface_update_timer .stop ()
404+
378405 def commit (self ):
379406 self .update_domain_role_hints ()
380407 if self .data is not None :
@@ -418,7 +445,7 @@ def send_report(self):
418445 self .report_items ((("Removed" , text ),))
419446
420447
421- def test_main (argv = None ):
448+ def main (argv = None ): # pragma: no cover
422449 from AnyQt .QtWidgets import QApplication
423450 if argv is None :
424451 argv = sys .argv
@@ -440,5 +467,6 @@ def test_main(argv=None):
440467 w .saveSettings ()
441468 return rval
442469
443- if __name__ == "__main__" :
444- sys .exit (test_main ())
470+
471+ if __name__ == "__main__" : # pragma: no cover
472+ sys .exit (main ())
0 commit comments