source: orange/Orange/OrangeWidgets/Data/OWDataDomain.py @ 9996:0191d66861df

Revision 9996:0191d66861df, 25.9 KB checked in by ales_erjavec, 2 years ago (diff)

Orange.data.new_meta_id -> Orange.feature.Descriptor.new_meta_id

Line 
1"""
2<name>Select Attributes</name>
3<description>Manual selection of attributes.</description>
4<icon>icons/SelectAttributes.png</icon>
5<priority>1100</priority>
6<contact>Ales Erjavec (ales.erjavec@fri.uni-lj.si)</contact>
7"""
8
9import sys
10
11from OWWidget import *
12from OWItemModels import PyListModel, VariableListModel
13
14import Orange
15
16def slices(indices):
17    """ Group the given integer indices into slices
18    """
19    indices = list(sorted(indices))
20    if indices:
21        first = last = indices[0]
22        for i in indices[1:]:
23            if i == last + 1:
24                last = i
25            else:
26                yield first, last + 1
27                first = last = i
28        yield first, last + 1
29       
30def source_model(view):
31    """ Return the source model for the Qt Item View if it uses
32    the QSortFilterProxyModel.
33   
34    """
35    if isinstance(view.model(),  QSortFilterProxyModel):
36        return view.model().sourceModel()
37    else:
38        return view.model()
39
40def source_indexes(indexes, view):
41    """ Map model indexes through a views QSortFilterProxyModel
42    """
43    model = view.model()
44    if isinstance(model, QSortFilterProxyModel):
45        return map(model.mapToSource, indexes)
46    else:
47        return indexes
48           
49def delslice(model, start, end):
50    """ Delete the start, end slice (rows) from the model.
51    """
52    if isinstance(model, PyListModel):
53        model.__delslice__(start, end)
54    elif isinstance(model, QAbstractItemModel):
55        model.removeRows(start, end-start)
56    else:
57        raise TypeError(type(model))
58       
59class VariablesListItemModel(VariableListModel):
60    """ An Qt item model for for list of orange.Variable objects.
61    Supports drag operations
62   
63    """
64       
65    def flags(self, index):
66        flags = VariableListModel.flags(self, index)
67        if index.isValid():
68            flags |= Qt.ItemIsDragEnabled
69        else:
70            flags |= Qt.ItemIsDropEnabled
71        return flags
72   
73    ###########
74    # Drag/Drop
75    ###########
76   
77    MIME_TYPE = "application/x-Orange-VariableListModelData"
78   
79    def supportedDropActions(self):
80        return Qt.MoveAction
81   
82    def supportedDragActions(self):
83        return Qt.MoveAction
84   
85    def mimeTypes(self):
86        return [self.MIME_TYPE]
87   
88    def mimeData(self, indexlist):
89        descriptors = []
90        vars = []
91        item_data = []
92        for index in indexlist:
93            var = self[index.row()]
94            descriptors.append((var.name, var.varType))
95            vars.append(var)
96            item_data.append(self.itemData(index))
97       
98        mime = QMimeData()
99        mime.setData(self.MIME_TYPE, QByteArray(str(descriptors)))
100        mime._vars = vars
101        mime._item_data = item_data
102        return mime
103   
104    def dropMimeData(self, mime, action, row, column, parent):
105        if action == Qt.IgnoreAction:
106            return True
107   
108        vars, item_data = self.items_from_mime_data(mime)
109        if vars is None:
110            return False
111       
112        if row == -1:
113            row = len(self)
114           
115        self.__setslice__(row, row, vars)
116       
117        for i, data in enumerate(item_data):
118            self.setItemData(self.index(row + i), data)
119           
120        return True
121   
122    def items_from_mime_data(self, mime):
123        if not mime.hasFormat(self.MIME_TYPE):
124            return None, None
125       
126        if hasattr(mime, "_vars"):
127            vars = mime._vars
128            item_data = mime._item_data
129            return vars, item_data
130        else:
131            #TODO: get vars from orange.Variable.getExisting
132            return None, None
133       
134class ClassVarListItemModel(VariablesListItemModel):
135    def dropMimeData(self, mime, action, row, column, parent):
136        """ Ensure only one variable can be dropped onto the view.
137        """
138        vars, _ = self.items_from_mime_data(mime)
139       
140        if vars is None or len(self) + len(vars) > 1:
141            return False
142       
143        if action == Qt.IgnoreAction:
144            return True
145       
146        return VariablesListItemModel.dropMimeData(self, mime, action, row, column, parent)
147       
148class VariablesListItemView(QListView):
149    """ A Simple QListView subclass initialized for displaying
150    variables.
151     
152    """
153    def __init__(self, parent=None):
154        QListView.__init__(self, parent)
155        self.setSelectionMode(QListView.ExtendedSelection)
156        self.setAcceptDrops(True)
157        self.setDragEnabled(True)
158        self.setDropIndicatorShown(True)
159        self.setDragDropMode(QListView.DragDrop)
160        if hasattr(self, "setDefaultDropAction"):
161            # For compatibility with Qt version < 4.6
162            self.setDefaultDropAction(Qt.MoveAction)
163        self.setDragDropOverwriteMode(False)
164        self.viewport().setAcceptDrops(True)
165   
166    def startDrag(self, supported_actions):
167        indices = self.selectionModel().selectedIndexes()
168        indices = [i for i in indices if i.flags() & Qt.ItemIsDragEnabled]
169        if indices:
170            data = self.model().mimeData(indices)
171            if not data:
172                return
173            rect = QRect()
174#            pixmap = self.render_to_pixmap(indices)
175           
176            drag = QDrag(self)
177            drag.setMimeData(data)
178#            drag.setPixmap(pixmap)
179           
180            default_action = Qt.IgnoreAction
181            if hasattr(self, "defaultDropAction") and \
182                    self.defaultDropAction() != Qt.IgnoreAction and \
183                    supported_actions & self.defaultDropAction():
184                default_action = self.defaultDropAction()
185            elif supported_actions & Qt.CopyAction and dragDropMode() != QListView.InternalMove:
186                default_action = Qt.CopyAction
187           
188            res = drag.exec_(supported_actions, default_action)
189               
190            if res == Qt.MoveAction:
191                selected = self.selectionModel().selectedIndexes()
192                rows = map(QModelIndex.row, selected)
193               
194                for s1, s2 in reversed(list(slices(rows))):
195                    delslice(self.model(), s1, s2)
196#                    del source_model(self)[s1:s2]
197   
198    def render_to_pixmap(self, indices):
199        pass
200
201class ClassVariableItemView(VariablesListItemView):
202    def __init__(self, parent=None):
203        VariablesListItemView.__init__(self, parent)
204        self.setDropIndicatorShown(False)
205   
206    def dragEnterEvent(self, event):
207        """ Don't accept drops if the class is already present in the model.
208        """
209        if self.accepts_drop(event):
210            event.accept()
211        else:
212            event.ignore()
213           
214    def accepts_drop(self, event):
215        mime = event.mimeData()
216        vars, _ = self.model().items_from_mime_data(mime)
217        if vars is None:
218            return event.ignore()
219       
220        if len(self.model()) + len(vars) > 1:
221            return event.ignore()
222        return True
223   
224class VariableFilterProxyModel(QSortFilterProxyModel):
225    """ A proxy model for filtering a list of variables based on
226    their names and labels.
227     
228    """
229    def __init__(self, parent=None):
230        QSortFilterProxyModel.__init__(self, parent)
231        self._filter_string = ""
232       
233    def set_filter_string(self, filter):
234        self._filter_string = str(filter).lower()
235        self.invalidateFilter()
236       
237    def filter_accepts_variable(self, var):
238        row_str = var.name + " ".join(("%s=%s" % item) \
239                    for item in var.attributes.items())
240        row_str = row_str.lower()
241        filters = self._filter_string.split()
242       
243        return all(f in row_str for f in filters)
244                   
245    def filterAcceptsRow(self, source_row, source_parent):
246        model = self.sourceModel()
247        if isinstance(model, VariableListModel):
248            var = model[source_row]
249            return self.filter_accepts_variable(var)
250        else:
251            return True
252       
253USE_COMPLETER = True
254
255class CompleterNavigator(QObject):
256    """ An event filter to be installed on a QLineEdit, to enable
257    Key up/ down to navigate between posible completions.
258   
259    """
260    def eventFilter(self, obj, event):
261        if event.type() == QEvent.KeyPress and isinstance(obj, QLineEdit):
262            if event.key() == Qt.Key_Down:
263                diff = 1
264            elif event.key() == Qt.Key_Up:
265                diff = -1
266            else:
267                return False
268               
269            completer = obj.completer()
270            if completer is not None and completer.completionCount() > 0:
271                current = completer.currentRow()
272                current += diff
273                r = completer.setCurrentRow(current % completer.completionCount())
274                completer.complete()
275            return True
276        else:
277            return False
278   
279   
280from functools import partial
281class OWDataDomain(OWWidget):
282    contextHandlers = {"": DomainContextHandler("", [ContextField("domain_role_hints")])}
283   
284    def __init__(self, parent=None, signalManager=None, name="Select Attributes"):
285        OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False)
286       
287        self.inputs = [("Data", ExampleTable, self.set_data)]
288        self.outputs = [("Data", ExampleTable), ("Features", AttributeList)]
289       
290        self.domain_role_hints = {}
291       
292        self.loadSettings()
293       
294        # ####
295        # GUI
296        # ####
297       
298        import sip
299        sip.delete(self.controlArea.layout())
300       
301        layout = QGridLayout()
302        layout.setMargin(0)
303        box = OWGUI.widgetBox(self.controlArea, "Available attributes", addToLayout=False)
304       
305        self.filter_edit = QLineEdit()
306        self.filter_edit.setToolTip("Filter the list of available variables.")
307       
308        box.layout().addWidget(self.filter_edit)
309       
310        if hasattr(self.filter_edit, "setPlaceholderText"): # For Compatibility with Qt version > 4.7
311            self.filter_edit.setPlaceholderText("Filter")
312       
313        # Completer
314        if USE_COMPLETER:
315            self.completer = QCompleter()
316            self.completer.setCompletionMode(QCompleter.InlineCompletion)
317            self.completer_model = QStringListModel()
318            self.completer.setModel(self.completer_model)
319            self.completer.setModelSorting(QCompleter.CaseSensitivelySortedModel)
320   
321            self.filter_edit.setCompleter(self.completer)
322            self.completer_navigator = CompleterNavigator(self)
323            self.filter_edit.installEventFilter(self.completer_navigator)
324           
325        self.available_attrs = VariablesListItemModel()
326        self.available_attrs_proxy = VariableFilterProxyModel()
327        self.available_attrs_proxy.setSourceModel(self.available_attrs)
328        self.available_attrs_view = VariablesListItemView()
329        self.available_attrs_view.setModel(self.available_attrs_proxy)
330       
331       
332        self.connect(self.filter_edit,
333                     SIGNAL("textChanged(QString)"),
334                     self.available_attrs_proxy.set_filter_string)
335       
336        self.connect(self.available_attrs_view.selectionModel(),
337                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
338                     partial(self.update_interface_state,
339                             self.available_attrs_view))
340       
341        if USE_COMPLETER:
342            self.connect(self.filter_edit,
343                         SIGNAL("textChanged(QString)"),
344                         self.update_completer_prefix)
345       
346            self.connect(self.available_attrs,
347                         SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
348                         self.update_completer_model)
349           
350            self.connect(self.available_attrs,
351                         SIGNAL("rowsInserted(QModelIndex, int, int)"),
352                         self.update_completer_model)
353           
354            self.connect(self.available_attrs,
355                         SIGNAL("rowsRemoved(QModelIndex, int, int)"),
356                         self.update_completer_model)
357       
358        box.layout().addWidget(self.available_attrs_view)
359        layout.addWidget(box, 0, 0, 3, 1)
360       
361        box = OWGUI.widgetBox(self.controlArea, "Attributes", addToLayout=False)
362        self.used_attrs = VariablesListItemModel()
363        self.used_attrs_view = VariablesListItemView()
364        self.used_attrs_view.setModel(self.used_attrs)
365        self.connect(self.used_attrs_view.selectionModel(),
366                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
367                     partial(self.update_interface_state,
368                             self.used_attrs_view))
369       
370        box.layout().addWidget(self.used_attrs_view)
371        layout.addWidget(box, 0, 2, 1, 1)
372       
373        box = OWGUI.widgetBox(self.controlArea, "Class", addToLayout=False)
374        self.class_attrs = ClassVarListItemModel()
375        self.class_attrs_view = ClassVariableItemView()
376        self.class_attrs_view.setModel(self.class_attrs)
377        self.connect(self.class_attrs_view.selectionModel(),
378                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
379                     partial(self.update_interface_state,
380                             self.class_attrs_view))
381       
382        self.class_attrs_view.setMaximumHeight(24)
383        box.layout().addWidget(self.class_attrs_view)
384        layout.addWidget(box, 1, 2, 1, 1)
385       
386        box = OWGUI.widgetBox(self.controlArea, "Meta Attributes", addToLayout=False)
387        self.meta_attrs = VariablesListItemModel()
388        self.meta_attrs_view = VariablesListItemView()
389        self.meta_attrs_view.setModel(self.meta_attrs)
390        self.connect(self.meta_attrs_view.selectionModel(),
391                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
392                     partial(self.update_interface_state,
393                             self.meta_attrs_view))
394       
395        box.layout().addWidget(self.meta_attrs_view)
396        layout.addWidget(box, 2, 2, 1, 1)
397       
398        bbox = OWGUI.widgetBox(self.controlArea, addToLayout=False, margin=0)
399        layout.addWidget(bbox, 0, 1, 1, 1)
400       
401        self.up_attr_button = OWGUI.button(bbox, self, "Up", 
402                    callback = partial(self.move_up, self.used_attrs_view))
403        self.move_attr_button = OWGUI.button(bbox, self, ">",
404                    callback = partial(self.move_selected, self.used_attrs_view))
405        self.down_attr_button = OWGUI.button(bbox, self, "Down",
406                    callback = partial(self.move_down, self.used_attrs_view))
407       
408        bbox = OWGUI.widgetBox(self.controlArea, addToLayout=False, margin=0)
409        layout.addWidget(bbox, 1, 1, 1, 1)
410        self.move_class_button = OWGUI.button(bbox, self, ">",
411                    callback = partial(self.move_selected,
412                            self.class_attrs_view, exclusive=True))
413       
414        bbox = OWGUI.widgetBox(self.controlArea, addToLayout=False, margin=0)
415        layout.addWidget(bbox, 2, 1, 1, 1)
416        self.up_meta_button = OWGUI.button(bbox, self, "Up",
417                    callback = partial(self.move_up, self.meta_attrs_view))
418        self.move_meta_button = OWGUI.button(bbox, self, ">",
419                    callback = partial(self.move_selected, self.meta_attrs_view))
420        self.down_meta_button = OWGUI.button(bbox, self, "Down",
421                    callback = partial(self.move_down, self.meta_attrs_view))
422       
423        bbox = OWGUI.widgetBox(self.controlArea, orientation="horizontal", addToLayout=False, margin=0)
424        applyButton = OWGUI.button(bbox, self, "Apply", callback=self.commit)
425        resetButton = OWGUI.button(bbox, self, "Reset", callback=self.reset)
426       
427        layout.addWidget(bbox, 3, 0, 1, 3)
428       
429        layout.setRowStretch(0, 4)
430        layout.setRowStretch(1, 0)
431        layout.setRowStretch(2, 2)
432        layout.setHorizontalSpacing(0)
433        self.controlArea.setLayout(layout)
434       
435        self.data = None
436        self.output_report = None
437        self.original_completer_items = []
438
439        self.resize(500, 600)
440       
441        # For automatic widget testing using
442        self._guiElements.extend( \
443                  [(QListView, self.available_attrs_view),
444                   (QListView, self.used_attrs_view),
445                   (QListView, self.class_attrs_view),
446                   (QListView, self.meta_attrs_view),
447                  ])
448       
449    def set_data(self, data=None):
450        self.update_domain_role_hints()
451        self.closeContext("")
452        self.data = data
453        if data is not None:
454            self.openContext("", data)
455            all_vars = data.domain.variables + data.domain.getmetas().values()
456           
457            var_sig = lambda attr: (attr.name, attr.varType)
458           
459            domain_hints = dict([(var_sig(attr), ("attribute", i)) \
460                            for i, attr in enumerate(data.domain.attributes)])
461           
462            domain_hints.update(dict([(var_sig(attr), ("meta", i)) \
463                for i, attr in enumerate(data.domain.getmetas().values())]))
464           
465            if data.domain.class_var:
466                domain_hints[var_sig(data.domain.class_var)] = ("class", 0)
467                   
468            domain_hints.update(self.domain_role_hints) # update the hints from context settings
469           
470            attrs_for_role = lambda role: [(domain_hints[var_sig(attr)][1], attr) \
471                    for attr in all_vars if domain_hints[var_sig(attr)][0] == role]
472           
473            attributes = [attr for place, attr in sorted(attrs_for_role("attribute"))]
474            classes = [attr for place, attr in sorted(attrs_for_role("class"))]
475            metas = [attr for place, attr in sorted(attrs_for_role("meta"))]
476            available = [attr for place, attr in sorted(attrs_for_role("available"))]
477           
478            self.used_attrs[:] = attributes
479            self.class_attrs[:] = classes
480            self.meta_attrs[:] = metas
481            self.available_attrs[:] = available
482        else:
483            self.used_attrs[:] = []
484            self.class_attrs[:] = []
485            self.meta_attrs[:] = []
486            self.available_attrs[:] = []
487       
488        self.commit()
489       
490    def update_domain_role_hints(self):
491        """ Update the domain hints to be stored in the widgets settings.
492        """
493        hints_from_model = lambda role, model: \
494                [((attr.name, attr.varType), (role, i)) \
495                 for i, attr in enumerate(model)]
496       
497        hints = dict(hints_from_model("available", self.available_attrs))
498        hints.update(hints_from_model("attribute", self.used_attrs))
499        hints.update(hints_from_model("class", self.class_attrs))
500        hints.update(hints_from_model("meta", self.meta_attrs))
501        self.domain_role_hints = hints
502       
503    def selected_rows(self, view):
504        """ Return the selected rows in the view.
505        """
506        rows = view.selectionModel().selectedRows()
507        model = view.model()
508        if isinstance(model, QSortFilterProxyModel):
509            rows = [model.mapToSource(r) for r in rows]
510        return [r.row() for r in rows]
511   
512    def move_rows(self, view, rows, offset):
513        model = view.model()
514        newrows = [min(max(0, row + offset), len(model) - 1) for row in rows]
515       
516        for row, newrow in sorted(zip(rows, newrows), reverse=offset > 0):
517            model[row], model[newrow] = model[newrow], model[row]
518           
519        selection = QItemSelection()
520        for nrow in newrows:
521            index = model.index(nrow, 0)
522            selection.select(index, index)
523        view.selectionModel().select(selection, QItemSelectionModel.ClearAndSelect)
524       
525    def move_up(self, view):
526        selected = self.selected_rows(view)
527        self.move_rows(view, selected, -1)
528   
529    def move_down(self, view):
530        selected = self.selected_rows(view)
531        self.move_rows(view, selected, 1)
532       
533    def move_selected(self, view, exclusive=False):
534        if self.selected_rows(view):
535            self.move_selected_from_to(view, self.available_attrs_view)
536        elif self.selected_rows(self.available_attrs_view):
537            self.move_selected_from_to(self.available_attrs_view, view, exclusive)
538   
539    def move_selected_from_to(self, src, dst, exclusive=False):
540        self.move_from_to(src, dst, self.selected_rows(src), exclusive)   
541       
542    def move_from_to(self, src, dst, rows, exclusive=False):
543        src_model = source_model(src)
544        attrs = [src_model[r] for r in rows]
545       
546        if exclusive and len(attrs) != 1:
547            return
548       
549        for s1, s2 in reversed(list(slices(rows))):
550            del src_model[s1:s2]
551           
552        dst_model = source_model(dst)
553        if exclusive and len(dst_model) > 0:
554            src_model.append(dst_model[0])
555            del dst_model[0]
556           
557        dst_model.extend(attrs)
558       
559    def update_interface_state(self, focus=None, selected=None, deselected=None):
560        for view in [self.available_attrs_view, self.used_attrs_view,
561                     self.class_attrs_view, self.meta_attrs_view]:
562            if view is not focus and not view.hasFocus() and self.selected_rows(view):
563                view.selectionModel().clear()
564               
565        available_selected = bool(self.selected_rows(self.available_attrs_view))
566                                 
567        move_attr_enabled = bool(self.selected_rows(self.available_attrs_view) or \
568                                self.selected_rows(self.used_attrs_view))
569        self.move_attr_button.setEnabled(move_attr_enabled)
570        if move_attr_enabled:
571            self.move_attr_button.setText(">" if available_selected else "<")
572           
573        move_class_enabled = bool(len(self.selected_rows(self.available_attrs_view)) == 1 or \
574                                  self.selected_rows(self.class_attrs_view))
575       
576        self.move_class_button.setEnabled(move_class_enabled)
577        if move_class_enabled:
578            self.move_class_button.setText(">" if available_selected else "<")
579           
580        move_meta_enabled = bool(self.selected_rows(self.available_attrs_view) or \
581                                 self.selected_rows(self.meta_attrs_view))
582        self.move_meta_button.setEnabled(move_meta_enabled)
583        if move_meta_enabled:
584            self.move_meta_button.setText(">" if available_selected else "<")
585           
586    def update_completer_model(self, *args):
587        """ This gets called when the model for available attributes changes
588        through either drag/drop or the left/right button actions.
589         
590        """
591        vars = list(self.available_attrs)
592        items = [var.name for var in vars]
593        labels = reduce(list.__add__, [v.attributes.items() for v in vars], [])
594        items.extend(["%s=%s" % item for item in labels])
595        items.extend(reduce(list.__add__, map(list, labels), []))
596       
597        new = sorted(set(items))
598        if new != self.original_completer_items:
599            self.original_completer_items = new
600            self.completer_model.setStringList(self.original_completer_items)
601       
602    def update_completer_prefix(self, filter):
603        """ Prefixes all items in the completer model with the current
604        already done completion to enable the completion of multiple keywords.   
605        """
606        prefix = str(self.completer.completionPrefix())
607        if not prefix.endswith(" ") and " " in prefix:
608            prefix, _ = prefix.rsplit(" ", 1)
609            items = [prefix + " " + item for item in self.original_completer_items]
610        else:
611            items = self.original_completer_items
612        old = map(str, self.completer_model.stringList())
613       
614        if set(old) != set(items):
615            self.completer_model.setStringList(items)
616       
617    def commit(self):
618        self.update_domain_role_hints()
619        if self.data is not None:
620            attributes = list(self.used_attrs)
621            class_var = list(self.class_attrs)
622            metas = list(self.meta_attrs)
623           
624            domain = Orange.data.Domain(attributes + class_var, bool(class_var))
625            original_metas = dict(map(reversed, self.data.domain.getmetas().items()))
626           
627            for meta in metas:
628                if meta in original_metas:
629                    mid = original_metas[meta]
630                else:
631                    mid = Orange.feature.Descriptor.new_meta_id()
632                domain.addmeta(mid, meta)
633            newdata = Orange.data.Table(domain, self.data)
634            self.output_report = self.prepareDataReport(newdata)
635            self.output_domain = domain
636            self.send("Data", newdata)
637            self.send("Features", orange.VarList(attributes))
638        else:
639            self.output_report = []
640            self.send("Data", None)
641            self.send("Features", None)
642   
643    def reset(self):
644        if self.data is not None:
645            self.available_attrs[:] = []
646            self.used_attrs[:] = self.data.domain.attributes
647            self.class_attrs[:] = [self.data.domain.class_var] if self.data.domain.class_var else []
648            self.meta_attrs[:] = self.data.domain.getmetas().values()
649            self.update_domain_role_hints()
650           
651    def sendReport(self):
652        self.reportData(self.data, "Input data")
653        self.reportData(self.output_report, "Output data")
654        if self.data:
655            all_vars = self.data.domain.variables + self.data.domain.getmetas().values()
656            used_vars = self.output_domain.variables + self.output_domain.getmetas().values()
657            if len(all_vars) != len(used_vars):
658                removed = set(all_vars).difference(set(used_vars))
659                self.reportSettings("", [("Removed", "%i (%s)" % (len(removed), ", ".join(x.name for x in removed)))])
660   
661if __name__ == "__main__":   
662    app = QApplication(sys.argv)
663    w = OWDataDomain()
664#    data = Orange.data.Table("rep:dicty-express.tab")
665    data = Orange.data.Table("brown-selected.tab")
666    w.set_data(data)
667    w.show()
668    app.exec_()
669    w.set_data(None)
670    w.saveSettings()
671   
672   
Note: See TracBrowser for help on using the repository browser.