source: orange/orange/OrangeWidgets/Prototypes/OWDataDomainMk2.py @ 9546:2b6cc6f397fe

Revision 9546:2b6cc6f397fe, 25.8 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Renamed widget channel names in line with the new naming rules/convention.
Added backwards compatibility in orngDoc loadDocument to enable loading of schemas saved before the change.

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