source: orange/Orange/OrangeWidgets/OWItemModels.py @ 10947:65cc61e515a6

Revision 10947:65cc61e515a6, 17.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 22 months ago (diff)

Handle open ended slices in delslice.

Line 
1from __future__ import with_statement
2
3from PyQt4.QtCore import *
4from PyQt4.QtGui import *
5
6from functools import wraps, partial
7from collections import defaultdict
8from contextlib import contextmanager
9
10class _store(dict):
11    pass
12
13def _argsort(seq, cmp=None, key=None, reverse=False):
14    if key is not None:
15        items = sorted(zip(range(len(seq)), seq), key=lambda i,v: key(v))
16    elif cmp is not None:
17        items = sorted(zip(range(len(seq)), seq), cmp=lambda a,b: cmp(a[1], b[1]))
18    else:
19        items = sorted(zip(range(len(seq)), seq), key=seq.__getitem__)
20    if reverse:
21        items = reversed(items)
22    return items
23
24
25@contextmanager
26def signal_blocking(object):
27    blocked = object.signalsBlocked()
28    object.blockSignals(True)
29    yield
30    object.blockSignals(blocked)
31
32
33class PyListModel(QAbstractListModel):
34    """ A model for displaying python list like objects in Qt item view classes
35    """
36    MIME_TYPES = ["application/x-Orange-PyListModelData"]
37
38    def __init__(self, iterable=[], parent=None,
39                 flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled,
40                 list_item_role=Qt.DisplayRole,
41                 supportedDropActions=Qt.MoveAction):
42        QAbstractListModel.__init__(self, parent)
43        self._list = []
44        self._other_data = []
45        self._flags = flags
46        self.list_item_role = list_item_role
47
48        self._supportedDropActions = supportedDropActions
49        self.extend(iterable)
50
51    def _is_index_valid_for(self, index, list_like):
52        if isinstance(index, QModelIndex) and index.isValid() :
53            row, column = index.row(), index.column()
54            return row < len(list_like) and row >= 0 and column == 0
55        elif isinstance(index, int):
56            return index < len(list_like) and index > -len(self)
57        else:
58            return False
59
60    def wrap(self, list):
61        """ Wrap the list with this model. All changes to the model
62        are done in place on the passed list
63        """
64        self._list = list
65        self._other_data = [_store() for _ in list]
66        self.reset()
67
68    def index(self, row, column=0, parent=QModelIndex()):
69        if self._is_index_valid_for(row, self) and column == 0:
70            return QAbstractListModel.createIndex(self, row, column, parent)
71        else:
72            return QModelIndex()
73
74    def headerData(self, section, orientation, role=Qt.DisplayRole):
75        if role == Qt.DisplayRole:
76            return QVariant(str(section))
77
78    def rowCount(self, parent=QModelIndex()):
79        return 0 if parent.isValid() else len(self)
80
81    def columnCount(self, parent=QModelIndex()):
82        return 0 if parent.isValid() else 1
83
84    def data(self, index, role=Qt.DisplayRole):
85        row = index.row()
86        if role in [self.list_item_role, Qt.EditRole] \
87                and self._is_index_valid_for(index, self):
88            return QVariant(self[row])
89        elif self._is_index_valid_for(row, self._other_data):
90            return QVariant(self._other_data[row].get(role, QVariant()))
91        else:
92            return QVariant()
93
94    def itemData(self, index):
95        map = QAbstractListModel.itemData(self, index)
96        if self._is_index_valid_for(index, self._other_data):
97            items = self._other_data[index.row()].items()
98        else:
99            items = []
100
101        for key, value in items:
102            map[key] = QVariant(value)
103
104        return map
105
106    def parent(self, index=QModelIndex()):
107        return QModelIndex()
108
109    def setData(self, index, value, role=Qt.EditRole):
110        if role == Qt.EditRole and self._is_index_valid_for(index, self):
111            obj = value.toPyObject()
112            self[index.row()] = obj # Will emit proper dataChanged signal
113            return True
114        elif self._is_index_valid_for(index, self._other_data):
115            self._other_data[index.row()][role] = value
116            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
117            return True
118        else:
119            return False
120
121    def setItemData(self, index, data):
122        data = dict(data)
123        with signal_blocking(self):
124            for role, value in data.items():
125                if role == Qt.EditRole and \
126                        self._is_index_valid_for(index, self):
127                    self[index.row()] = value.toPyObject()
128                elif self._is_index_valid_for(index, self._other_data):
129                    self._other_data[index.row()][role] = value
130
131        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
132        return True
133
134    def flags(self, index):
135        if self._is_index_valid_for(index, self._other_data):
136            return self._other_data[index.row()].get("flags", self._flags)
137        else:
138            return self._flags | Qt.ItemIsDropEnabled
139
140    def insertRows(self, row, count, parent=QModelIndex()):
141        """ Insert ``count`` rows at ``row``, the list fill be filled
142        with ``None``
143        """
144        if not parent.isValid():
145            self.__setslice__(row, row, [None] * count)
146            return True
147        else:
148            return False
149
150    def removeRows(self, row, count, parent=QModelIndex()):
151        """Remove ``count`` rows starting at ``row``
152        """
153        if not parent.isValid():
154            self.__delslice__(row, row + count)
155            return True
156        else:
157            return False
158
159    def extend(self, iterable):
160        list_ = list(iterable)
161        self.beginInsertRows(QModelIndex(), len(self), len(self) + len(list_) - 1)
162        self._list.extend(list_)
163        self._other_data.extend([_store() for _ in list_])
164        self.endInsertRows()
165
166    def append(self, item):
167        self.extend([item])
168
169    def insert(self, i, val):
170        self.beginInsertRows(QModelIndex(), i, i)
171        self._list.insert(i, val)
172        self._other_data.insert(i, _store())
173        self.endInsertRows()
174
175    def remove(self, val):
176        i = self._list.index(val)
177        self.__delitem__(i)
178
179    def pop(self, i):
180        item = self._list[i]
181        self.__delitem__(i)
182        return item
183
184    def __len__(self):
185        return len(self._list)
186
187    def __iter__(self):
188        return iter(self._list)
189
190    def __getitem__(self, i):
191        return self._list[i]
192
193    def __getslice__(self, i, j):
194        return self._list[i:j]
195
196    def __add__(self, iterable):
197        new_list = PyListModel(list(self._list), 
198                           self.parent(),
199                           flags=self._flags,
200                           list_item_role=self.list_item_role,
201                           supportedDropActions=self.supportedDropActions()
202                           )
203        new_list._other_data = list(self._other_data)
204        new_list.extend(iterable)
205        return new_list
206
207    def __iadd__(self, iterable):
208        self.extend(iterable)
209
210    def __delitem__(self, i):
211        self.beginRemoveRows(QModelIndex(), i, i)
212        del self._list[i]
213        del self._other_data[i]
214        self.endRemoveRows()
215
216    def __delslice__(self, i, j):
217        max_i = len(self)
218        i = max(0, min(i, max_i))
219        j = max(0, min(j, max_i))
220
221        if j > i and i < max_i:
222            self.beginRemoveRows(QModelIndex(), i, j - 1)
223            del self._list[i:j]
224            del self._other_data[i:j]
225            self.endRemoveRows()
226
227    def __setitem__(self, i, value):
228        self._list[i] = value
229        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(i), self.index(i))
230
231    def __setslice__(self, i, j, iterable):
232        self.__delslice__(i, j)
233        all = list(iterable)
234        if len(all):
235            self.beginInsertRows(QModelIndex(), i, i + len(all) - 1)
236            self._list[i:i] = all
237            self._other_data[i:i] = [_store() for _ in all]
238            self.endInsertRows()
239
240    def reverse(self):
241        self._list.reverse()
242        self._other_data.reverse()
243        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(0), self.index(len(self) -1))
244
245    def sort(self, *args, **kwargs):
246        indices = _argsort(self._list, *args, **kwargs)
247        list = [self._list[i] for i in indices]
248        other = [self._other_data[i] for i in indices]
249        for i, new_l, new_o in enumerate(zip(list, other)):
250            self._list[i] = new_l
251            self._other_data[i] = new_o
252        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(0), self.index(len(self) -1))
253
254    def __repr__(self):
255        return "PyListModel(%s)" % repr(self._list)
256
257    def __nonzero__(self):
258        return len(self) != 0
259
260    #for Python 3000
261    def __bool__(self):
262        return len(self) != 0
263
264    def emitDataChanged(self, indexList):
265        if isinstance(indexList, int):
266            indexList = [indexList]
267
268        #TODO: group indexes into ranges
269        for ind in indexList:
270            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(ind), self.index(ind))
271
272    ###########
273    # Drag/drop
274    ###########
275
276    def supportedDropActions(self):
277        return self._supportedDropActions
278
279    def decode_qt_data(self, data):
280        """ Decode internal Qt 'application/x-qabstractitemmodeldatalist'
281        mime data
282        """
283        stream = QDataStream(data)
284        items = []
285        while not stream.atEnd():
286            row = ds.readInt()
287            col = ds.readInt()
288            item_count = ds.readInt()
289            item = {}
290            for i in range(item_count):
291                role = ds.readInt()
292                value = ds.readQVariant()
293                item[role] = value
294            items.append((row, column, item))
295        return items
296
297    def mimeTypes(self):
298        return self.MIME_TYPES + list(QAbstractListModel.mimeTypes(self))
299
300    def mimeData(self, indexlist):
301        if len(indexlist) <= 0:
302            return None
303
304        items = [self[i.row()] for i in indexlist]
305        mime = QAbstractListModel.mimeData(self, indexlist)
306        data = cPickle.dumps(vars)
307        mime.setData(self.MIME_TYPE, QByteArray(data))
308        mime._items = items
309        return mime
310
311    def dropMimeData(self, mime, action, row, column, parent):
312        if action == Qt.IgnoreAction:
313            return True
314
315        if not mime.hasFormat(self.MIME_TYPE):
316            return False
317
318        if hasattr(mime, "_vars"):
319            vars = mime._vars
320        else:
321            desc = str(mime.data(self.MIME_TYPE))
322            vars = cPickle.loads(desc)
323
324        return QAbstractListModel.dropMimeData(self, mime, action, row, column, parent)
325
326
327import OWGUI
328import orange
329import Orange
330import cPickle
331
332class VariableListModel(PyListModel):
333
334    MIME_TYPE = "application/x-Orange-VariableList"
335
336    def data(self, index, role=Qt.DisplayRole):
337        if self._is_index_valid_for(index, self):
338            i = index.row()
339            var = self[i]
340            if role == Qt.DisplayRole:
341                return QVariant(var.name)
342            elif role == Qt.DecorationRole:
343                return QVariant(OWGUI.getAttributeIcons().get(var.varType, -1))
344            elif role == Qt.ToolTipRole:
345                return QVariant(self.variable_tooltip(var))
346            else:
347                return PyListModel.data(self, index, role)
348        else:
349            return QVariant()
350
351    def variable_tooltip(self, var):
352        if isinstance(var, Orange.feature.Discrete):
353            return self.discrete_variable_tooltip(var)
354        elif isinstance(var, Orange.feature.Continuous):
355            return self.continuous_variable_toltip(var)
356        elif isinstance(var, Orange.feature.String):
357            return self.string_variable_tooltip(var)
358
359    def variable_labels_tooltip(self, var):
360        text = ""
361        if var.attributes:
362            items = var.attributes.items()
363            items = [(safe_text(key), safe_text(value)) for key, value in items]
364            labels = map("%s = %s".__mod__, items)
365            text += "<br/>Variable Labels:<br/>"
366            text += "<br/>".join(labels)
367        return text
368
369    def discrete_variable_tooltip(self, var):
370        text = "<b>%s</b><br/>Discrete with %i values: " % (safe_text(var.name), len(var.values))
371        text += ", ".join("%r" % safe_text(v) for v in var.values)
372        text += self.variable_labels_tooltip(var)
373        return text
374
375    def continuous_variable_toltip(self, var):
376        text = "<b>%s</b><br/>Continuous" % safe_text(var.name)
377        text += self.variable_labels_tooltip(var)
378        return text
379
380    def string_variable_tooltip(self, var):
381        text = "<b>%s</b><br/>String" % safe_text(var.name)
382        text += self.variable_labels_tooltip(var)
383        return text
384
385    def python_variable_tooltip(self, var):
386        text = "<b>%s</b><br/>Python" % safe_text(var.name)
387        text += self.variable_labels_tooltip(var)
388        return text
389
390_html_replace = [("<", "&lt;"), (">", "&gt;")]
391
392def safe_text(text):
393    for old, new in _html_replace:
394        text = text.replace(old, new)
395    return text
396
397class VariableEditor(QWidget):
398    def __init__(self, var, parent):
399        QWidget.__init__(self, parent)
400        self.var = var
401        layout = QHBoxLayout()
402        self._attrs = OWGUI.getAttributeIcons()
403        self.type_cb = QComboBox(self)
404        for attr, icon in self._attrs.items():
405            if attr != -1:
406                self.type_cb.addItem(icon, str(attr))
407        layout.addWidget(self.type_cb)
408       
409        self.name_le = QLineEdit(self)
410        layout.addWidget(self.name_le)
411       
412        self.setLayout(layout)
413       
414        self.connect(self.type_cb, SIGNAL("currentIndexChanged(int)"), self.edited)
415        self.connect(self.name_le, SIGNAL("editingFinished()"), self.edited)
416   
417    def edited(self, *args):
418        self.emit(SIGNAL("edited()"))
419         
420    def setData(self, type, name):
421        self.type_cb.setCurrentIndex(self._attr.keys().index(type))
422        self.name_le.setText(name)
423       
424class EnumVariableEditor(VariableEditor):
425    def __init__(self, var, parent):
426        VariableEditor.__init__(self, var, parent)
427       
428class FloatVariableEditor(QLineEdit):
429   
430    def setVariable(self, var):
431        self.setText(str(var.name))
432       
433    def getVariable(self):
434        return orange.FloatVariable(str(self.text()))
435
436   
437class StringVariableEditor(QLineEdit):
438    def setVariable(self, var):
439        self.setText(str(var.name))
440       
441    def getVariable(self):
442        return orange.StringVariable(str(self.text()))
443       
444class VariableDelegate(QStyledItemDelegate):
445    def createEditor(self, parent, option, index):
446        var = index.data(Qt.EditRole).toPyObject()
447        if isinstance(var, orange.EnumVariable):
448            return EnumVariableEditor(parent)
449        elif isinstance(var, orange.FloatVariable):
450            return FloatVariableEditor(parent)
451        elif isinstance(var, orange.StringVariable):
452            return StringVariableEditor(parent)
453#        return VariableEditor(var, parent)
454   
455    def setEditorData(self, editor, index):
456        var = index.data(Qt.EditRole).toPyObject()
457        editor.variable = var
458       
459    def setModelData(self, editor, model, index):
460        model.setData(index, QVariant(editor.variable), Qt.EditRole)
461       
462#    def displayText(self, value, locale):
463#        return value.toPyObject().name
464       
465class ListSingleSelectionModel(QItemSelectionModel):
466    """ Item selection model for list item models with single selection.
467   
468    Defines signal:
469        - selectedIndexChanged(QModelIndex)
470       
471    """
472    def __init__(self, model, parent=None):
473        QItemSelectionModel.__init__(self, model, parent)
474        self.connect(self, SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.onSelectionChanged)
475       
476    def onSelectionChanged(self, new, old):
477        index = list(new.indexes())
478        if index:
479            index = index.pop()
480        else:
481            index = QModelIndex()
482        self.emit(SIGNAL("selectedIndexChanged(QModelIndex)"), index)
483       
484    def selectedRow(self):
485        """ Return QModelIndex of the selected row or invalid if no selection.
486        """
487        rows = self.selectedRows()
488        if rows:
489            return rows[0]
490        else:
491            return QModelIndex()
492       
493    def select(self, index, flags=QItemSelectionModel.ClearAndSelect):
494        if isinstance(index, int):
495            index = self.model().index(index)
496        return QItemSelectionModel.select(self, index, flags)
497   
498       
499class ModelActionsWidget(QWidget):
500    def __init__(self, actions=[], parent=None, direction=QBoxLayout.LeftToRight):
501        QWidget.__init__(self, parent)
502        self.actions = []
503        self.buttons = []
504        layout = QBoxLayout(direction)
505        layout.setContentsMargins(0, 0, 0, 0)
506        self.setContentsMargins(0, 0, 0, 0)
507        self.setLayout(layout)
508        for action in actions:
509            self.addAction(action)
510        self.setLayout(layout)
511           
512    def actionButton(self, action):
513        if isinstance(action, QAction):
514            button = QToolButton(self)
515            button.setDefaultAction(action)
516            return button
517        elif isinstance(action, QAbstractButton):
518            return action
519           
520    def insertAction(self, ind, action, *args):
521        button = self.actionButton(action)
522        self.layout().insertWidget(ind, button, *args)
523        self.buttons.insert(ind, button)
524        self.actions.insert(ind, action)
525        return button
526       
527    def addAction(self, action, *args):
528        return self.insertAction(-1, action, *args)
529
530   
Note: See TracBrowser for help on using the repository browser.