source: orange/Orange/OrangeWidgets/Data/OWDataDomain.py @ 11748:467f952c108d

Revision 11748:467f952c108d, 26.0 KB checked in by blaz <blaz.zupan@…>, 6 months ago (diff)

Changes in headers, widget descriptions text.

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