source: orange/orange/OrangeWidgets/OWItemModels.py @ 9235:be543f273dcf

Revision 9235:be543f273dcf, 16.1 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Compatibility with Qt 4.4 and Python 2.5

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