1414 SelectAttributesDomainContextHandler
1515from Orange .widgets .settings import ContextSetting , Setting
1616from Orange .widgets .utils .listfilter import VariablesListItemView , slices , variables_filter
17- from Orange .widgets .widget import Input , Output
17+ from Orange .widgets .widget import Input , Output , AttributeList , Msg
1818from Orange .data .table import Table
1919from Orange .widgets .utils import vartype
2020from Orange .widgets .utils .itemmodels import VariableListModel
@@ -106,21 +106,29 @@ class OWSelectAttributes(widget.OWWidget):
106106 keywords = ["filter" ]
107107
108108 class Inputs :
109- data = Input ("Data" , Table )
109+ data = Input ("Data" , Table , default = True )
110+ features = Input ("Features" , AttributeList )
110111
111112 class Outputs :
112113 data = Output ("Data" , Table )
113- features = Output ("Features" , widget . AttributeList , dynamic = False )
114+ features = Output ("Features" , AttributeList , dynamic = False )
114115
115116 want_main_area = False
116117 want_control_area = True
117118
118119 settingsHandler = SelectAttributesDomainContextHandler ()
119120 domain_role_hints = ContextSetting ({})
121+ use_input_features = Setting (False )
120122 auto_commit = Setting (True )
121123
124+ class Warning (widget .OWWidget .Warning ):
125+ mismatching_domain = Msg ("Features and data domain do not match" )
126+
122127 def __init__ (self ):
123128 super ().__init__ ()
129+ self .data = None
130+ self .features = None
131+
124132 # Schedule interface updates (enabled buttons) using a coalescing
125133 # single shot timer (complex interactions on selection and filtering
126134 # updates in the 'available_attrs_view')
@@ -161,6 +169,8 @@ def dropcompleted(action):
161169
162170 box = gui .vBox (self .controlArea , "Features" , addToLayout = False )
163171 self .used_attrs = VariablesListItemModel ()
172+ self .used_attrs .rowsInserted .connect (self .__used_attrs_changed )
173+ self .used_attrs .rowsRemoved .connect (self .__used_attrs_changed )
164174 self .used_attrs_view = VariablesListItemView (
165175 acceptedType = (Orange .data .DiscreteVariable ,
166176 Orange .data .ContinuousVariable ))
@@ -169,6 +179,14 @@ def dropcompleted(action):
169179 self .used_attrs_view .selectionModel ().selectionChanged .connect (
170180 partial (update_on_change , self .used_attrs_view ))
171181 self .used_attrs_view .dragDropActionDidComplete .connect (dropcompleted )
182+ self .use_features_box = gui .auto_commit (
183+ self .controlArea , self , "use_input_features" ,
184+ "Use input features" , "Always use input features" ,
185+ box = False , commit = self .__use_features_clicked ,
186+ callback = self .__use_features_changed , addToLayout = False
187+ )
188+ self .enable_use_features_box ()
189+ box .layout ().addWidget (self .use_features_box )
172190 box .layout ().addWidget (self .used_attrs_view )
173191 layout .addWidget (box , 0 , 2 , 1 , 1 )
174192
@@ -244,11 +262,39 @@ def dropcompleted(action):
244262 layout .setHorizontalSpacing (0 )
245263 self .controlArea .setLayout (layout )
246264
247- self .data = None
248265 self .output_data = None
249266 self .original_completer_items = []
250267
251- self .resize (500 , 600 )
268+ self .resize (600 , 600 )
269+
270+ @property
271+ def features_from_data_attributes (self ):
272+ if self .data is None or self .features is None :
273+ return []
274+ domain = self .data .domain
275+ return [domain [feature .name ] for feature in self .features
276+ if feature .name in domain and domain [feature .name ]
277+ in domain .attributes ]
278+
279+ def can_use_features (self ):
280+ return bool (self .features_from_data_attributes ) and \
281+ self .features_from_data_attributes != self .used_attrs [:]
282+
283+ def __use_features_changed (self ): # Use input features check box
284+ # Needs a check since callback is invoked before object is created
285+ if not hasattr (self , "use_features_box" ):
286+ return
287+ self .enable_used_attrs (not self .use_input_features )
288+ if self .use_input_features and self .can_use_features ():
289+ self .use_features ()
290+ if not self .use_input_features :
291+ self .enable_use_features_box ()
292+
293+ def __use_features_clicked (self ): # Use input features button
294+ self .use_features ()
295+
296+ def __used_attrs_changed (self ):
297+ self .enable_use_features_box ()
252298
253299 @Inputs .data
254300 def set_data (self , data = None ):
@@ -302,8 +348,6 @@ def set_data(self, data=None):
302348 self .meta_attrs [:] = []
303349 self .available_attrs [:] = []
304350
305- self .unconditional_commit ()
306-
307351 def update_domain_role_hints (self ):
308352 """ Update the domain hints to be stored in the widgets settings.
309353 """
@@ -316,6 +360,46 @@ def update_domain_role_hints(self):
316360 hints .update (hints_from_model ("meta" , self .meta_attrs ))
317361 self .domain_role_hints = hints
318362
363+ @Inputs .features
364+ def set_features (self , features ):
365+ self .features = features
366+
367+ def handleNewSignals (self ):
368+ self .check_data ()
369+ self .enable_used_attrs ()
370+ self .enable_use_features_box ()
371+ if self .use_input_features and len (self .features_from_data_attributes ):
372+ self .enable_used_attrs (False )
373+ self .use_features ()
374+ self .unconditional_commit ()
375+
376+ def check_data (self ):
377+ self .Warning .mismatching_domain .clear ()
378+ if self .data is not None and self .features is not None and \
379+ not len (self .features_from_data_attributes ):
380+ self .Warning .mismatching_domain ()
381+
382+ def enable_used_attrs (self , enable = True ):
383+ self .up_attr_button .setEnabled (enable )
384+ self .move_attr_button .setEnabled (enable )
385+ self .down_attr_button .setEnabled (enable )
386+ self .used_attrs_view .setEnabled (enable )
387+ self .used_attrs_view .repaint ()
388+
389+ def enable_use_features_box (self ):
390+ self .use_features_box .button .setEnabled (self .can_use_features ())
391+ enable_checkbox = bool (self .features_from_data_attributes )
392+ self .use_features_box .setHidden (not enable_checkbox )
393+ self .use_features_box .repaint ()
394+
395+ def use_features (self ):
396+ attributes = self .features_from_data_attributes
397+ available , used = self .available_attrs [:], self .used_attrs [:]
398+ self .available_attrs [:] = [attr for attr in used + available
399+ if attr not in attributes ]
400+ self .used_attrs [:] = attributes
401+ self .commit ()
402+
319403 def selected_rows (self , view ):
320404 """ Return the selected rows in the view.
321405 """
@@ -397,8 +481,9 @@ def selected_vars(view):
397481 all_primitive = all (var .is_primitive ()
398482 for var in available_types )
399483
400- move_attr_enabled = (available_selected and all_primitive ) or \
401- attrs_selected
484+ move_attr_enabled = \
485+ ((available_selected and all_primitive ) or attrs_selected ) and \
486+ self .used_attrs_view .isEnabled ()
402487
403488 self .move_attr_button .setEnabled (bool (move_attr_enabled ))
404489 if move_attr_enabled :
@@ -429,13 +514,15 @@ def commit(self):
429514 newdata = self .data .transform (domain )
430515 self .output_data = newdata
431516 self .Outputs .data .send (newdata )
432- self .Outputs .features .send (widget . AttributeList (attributes ))
517+ self .Outputs .features .send (AttributeList (attributes ))
433518 else :
434519 self .output_data = None
435520 self .Outputs .data .send (None )
436521 self .Outputs .features .send (None )
437522
438523 def reset (self ):
524+ self .enable_used_attrs ()
525+ self .use_features_box .checkbox .setChecked (False )
439526 if self .data is not None :
440527 self .available_attrs [:] = []
441528 self .used_attrs [:] = self .data .domain .attributes
@@ -476,6 +563,8 @@ def main(argv=None): # pragma: no cover
476563 w = OWSelectAttributes ()
477564 data = Orange .data .Table (filename )
478565 w .set_data (data )
566+ w .set_features (AttributeList (data .domain .attributes [:2 ]))
567+ w .handleNewSignals ()
479568 w .show ()
480569 w .raise_ ()
481570 rval = app .exec_ ()
0 commit comments