GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Orange.widgets.utils.PyListModel   F
last analyzed

Complexity

Total Complexity 82

Size/Duplication

Total Lines 278
Duplicated Lines 0 %
Metric Value
dl 0
loc 278
rs 1.5789
wmc 82

36 Methods

Rating   Name   Duplication   Size   Complexity  
A extend() 0 7 2
A flags() 0 5 2
A mimeData() 0 10 3
A insertRows() 0 9 2
A dropMimeData() 0 15 4
A rowCount() 0 2 2
A emitDataChanged() 0 7 3
A __iter__() 0 2 1
A __iadd__() 0 2 1
A sort() 0 8 4
A parent() 0 2 1
A data() 0 7 4
A setData() 0 10 4
B setItemData() 0 12 6
A remove() 0 3 1
A wrap() 0 7 2
A mimeTypes() 0 2 1
A insert() 0 5 1
A __getitem__() 0 2 1
A headerData() 0 3 2
A __add__() 0 10 1
A columnCount() 0 2 2
A _is_index_valid_for() 0 8 4
A reverse() 0 4 1
A itemData() 0 9 3
A supportedDropActions() 0 2 1
A __delitem__() 0 11 3
C __setitem__() 0 21 8
A removeRows() 0 8 2
A pop() 0 4 1
A __repr__() 0 2 1
A __len__() 0 2 1
A __init__() 0 13 2
A index() 0 5 3
A __bool__() 0 2 1
A append() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like Orange.widgets.utils.PyListModel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import pickle
2
import operator
3
from collections import namedtuple
4
from contextlib import contextmanager
5
from functools import reduce, partial, lru_cache
0 ignored issues
show
Unused Code introduced by
Unused lru_cache imported from functools
Loading history...
6
from xml.sax.saxutils import escape
7
8
from PyQt4.QtGui import  QItemSelectionModel, QColor
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtGui could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
9
from PyQt4.QtCore import (
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtCore could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
10
    Qt, QAbstractListModel, QAbstractTableModel, QModelIndex, QByteArray
11
)
12
from PyQt4.QtCore import pyqtSignal as Signal
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtCore could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
13
14
from PyQt4.QtGui import (
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtGui could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
15
    QWidget, QBoxLayout, QToolButton, QAbstractButton, QAction
16
)
17
18
import numpy
0 ignored issues
show
Configuration introduced by
The import numpy could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
19
20
from Orange.data import Variable, Storage
21
from Orange.widgets import gui
22
from Orange.widgets.utils import datacaching
23
from Orange.statistics import basic_stats
24
25
26
class _store(dict):
27
    pass
28
29
30
def _argsort(seq, cmp=None, key=None, reverse=False):
31
    if key is not None:
32
        items = sorted(enumerate(seq), key=lambda pair: key(pair[1]))
33
    elif cmp is not None:
34
        items = sorted(enumerate(seq), cmp=lambda a, b: cmp(a[1], b[1]))
35
    else:
36
        items = sorted(enumerate(seq), key=operator.itemgetter(1))
37
    if reverse:
38
        items = reversed(items)
39
    return items
40
41
42
@contextmanager
43
def signal_blocking(obj):
44
    blocked = obj.signalsBlocked()
45
    obj.blockSignals(True)
46
    try:
47
        yield
48
    finally:
49
        obj.blockSignals(blocked)
50
51
52
def _as_contiguous_range(start, stop, step):
53
    if step == -1:
54
        # Equivalent range with positive step
55
        start, stop = stop + 1, start + 1
56
    elif not (step == 1 or step is None):
57
        raise IndexError("Non-contiguous range.")
58
    return start, stop, step
59
60
61
class PyListModel(QAbstractListModel):
62
    """ A model for displaying python list like objects in Qt item view classes
63
    """
64
    MIME_TYPES = ["application/x-Orange-PyListModelData"]
65
    Separator = object()
66
67
    def __init__(self, iterable=None, parent=None,
68
                 flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled,
69
                 list_item_role=Qt.DisplayRole,
70
                 supportedDropActions=Qt.MoveAction):
71
        super().__init__(parent)
72
        self._list = []
73
        self._other_data = []
74
        self._flags = flags
75
        self.list_item_role = list_item_role
76
77
        self._supportedDropActions = supportedDropActions
78
        if iterable is not None:
79
            self.extend(iterable)
80
81
    def _is_index_valid_for(self, index, list_like):
82
        if isinstance(index, QModelIndex) and index.isValid():
83
            row, column = index.row(), index.column()
84
            return 0 <= row < len(list_like) and not column
85
        elif isinstance(index, int):
86
            return -len(self) < index < len(list_like)
87
        else:
88
            return False
89
90
    def wrap(self, lst):
91
        """ Wrap the list with this model. All changes to the model
92
        are done in place on the passed list
93
        """
94
        self._list = lst
95
        self._other_data = [_store() for _ in lst]
96
        self.reset()
97
98
99
    # noinspection PyMethodOverriding
100
    def index(self, row, column=0, parent=QModelIndex()):
101
        if self._is_index_valid_for(row, self) and column == 0:
102
            return QAbstractListModel.createIndex(self, row, column, parent)
103
        else:
104
            return QModelIndex()
105
106
    def headerData(self, section, orientation, role=Qt.DisplayRole):
0 ignored issues
show
Unused Code introduced by
The argument orientation seems to be unused.
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
107
        if role == Qt.DisplayRole:
108
            return str(section)
109
110
111
    # noinspection PyMethodOverriding
112
    def rowCount(self, parent=QModelIndex()):
113
        return 0 if parent.isValid() else len(self)
114
115
    def columnCount(self, parent=QModelIndex()):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
116
        return 0 if parent.isValid() else 1
117
118
    def data(self, index, role=Qt.DisplayRole):
119
        row = index.row()
120
        if role in [self.list_item_role, Qt.EditRole] \
121
                and self._is_index_valid_for(index, self):
122
            return self[row]
123
        elif self._is_index_valid_for(row, self._other_data):
124
            return self._other_data[row].get(role, None)
125
126
    def itemData(self, index):
127
        mapping = QAbstractListModel.itemData(self, index)
128
        if self._is_index_valid_for(index, self._other_data):
129
            items = list(self._other_data[index.row()].items())
130
        else:
131
            items = []
132
        for key, value in items:
133
            mapping[key] = value
134
        return mapping
135
136
    def parent(self, index=QModelIndex()):
0 ignored issues
show
Unused Code introduced by
The argument index seems to be unused.
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
137
        return QModelIndex()
138
139
    def setData(self, index, value, role=Qt.EditRole):
140
        if role == Qt.EditRole and self._is_index_valid_for(index, self):
141
            self[index.row()] = value  # Will emit proper dataChanged signal
142
            return True
143
        elif self._is_index_valid_for(index, self._other_data):
144
            self._other_data[index.row()][role] = value
145
            self.dataChanged.emit(index, index)
146
            return True
147
        else:
148
            return False
149
150
    def setItemData(self, index, data):
151
        data = dict(data)
152
        with signal_blocking(self):
153
            for role, value in data.items():
154
                if role == Qt.EditRole and \
155
                        self._is_index_valid_for(index, self):
156
                    self[index.row()] = value
157
                elif self._is_index_valid_for(index, self._other_data):
158
                    self._other_data[index.row()][role] = value
159
160
        self.dataChanged.emit(index, index)
161
        return True
162
163
    def flags(self, index):
164
        if self._is_index_valid_for(index, self._other_data):
165
            return self._other_data[index.row()].get("flags", self._flags)
166
        else:
167
            return self._flags | Qt.ItemIsDropEnabled
168
169
170
    # noinspection PyMethodOverriding
171
    def insertRows(self, row, count, parent=QModelIndex()):
172
        """ Insert ``count`` rows at ``row``, the list fill be filled
173
        with ``None``
174
        """
175
        if not parent.isValid():
176
            self[row:row] = [None] * count
177
            return True
178
        else:
179
            return False
180
181
182
    # noinspection PyMethodOverriding
183
    def removeRows(self, row, count, parent=QModelIndex()):
184
        """Remove ``count`` rows starting at ``row``
185
        """
186
        if not parent.isValid():
187
            del self[row:row + count]
188
            return True
189
        else:
190
            return False
191
192
    def extend(self, iterable):
193
        list_ = list(iterable)
194
        self.beginInsertRows(QModelIndex(),
195
                             len(self), len(self) + len(list_) - 1)
196
        self._list.extend(list_)
197
        self._other_data.extend([_store() for _ in list_])
198
        self.endInsertRows()
199
200
    def append(self, item):
201
        self.extend([item])
202
203
    def insert(self, i, val):
204
        self.beginInsertRows(QModelIndex(), i, i)
205
        self._list.insert(i, val)
206
        self._other_data.insert(i, _store())
207
        self.endInsertRows()
208
209
    def remove(self, val):
210
        i = self._list.index(val)
211
        self.__delitem__(i)
212
213
    def pop(self, i):
214
        item = self._list[i]
215
        self.__delitem__(i)
216
        return item
217
218
    def __len__(self):
219
        return len(self._list)
220
221
    def __iter__(self):
222
        return iter(self._list)
223
224
    def __getitem__(self, i):
225
        return self._list[i]
226
227
    def __add__(self, iterable):
228
        new_list = PyListModel(list(self._list),
229
                               self.parent(),
230
                               flags=self._flags,
231
                               list_item_role=self.list_item_role,
232
                               supportedDropActions=self.supportedDropActions()
233
                               )
234
        new_list._other_data = list(self._other_data)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _other_data was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
235
        new_list.extend(iterable)
236
        return new_list
237
238
    def __iadd__(self, iterable):
239
        self.extend(iterable)
240
241
    def __delitem__(self, s):
242
        if isinstance(s, slice):
243
            start, stop, step = s.indices(len(self))
244
            start, stop, step = _as_contiguous_range(start, stop, step)
245
            self.beginRemoveRows(QModelIndex(), start, stop - 1)
246
        else:
247
            s = len(self) + s if s < 0 else s
248
            self.beginRemoveRows(QModelIndex(), s, s)
249
        del self._list[s]
250
        del self._other_data[s]
251
        self.endRemoveRows()
252
253
    def __setitem__(self, s, value):
254
        if isinstance(s, slice):
255
            start, stop, step = s.indices(len(self))
256
            start, stop, step = _as_contiguous_range(start, stop, step)
257
            self.__delitem__(slice(start, stop, step))
258
259
            if not isinstance(value, list):
260
                value = list(value)
261
            separators = [start + i for i, v in enumerate(value) if v is self.Separator]
262
            self.beginInsertRows(QModelIndex(), start, start + len(value) - 1)
263
            self._list[s] = value
264
            self._other_data[s] = (_store() for _ in value)
265
            for idx in separators:
266
                self._other_data[idx]['flags'] = Qt.NoItemFlags
267
                self._other_data[idx][Qt.AccessibleDescriptionRole] = 'separator'
268
            self.endInsertRows()
269
        else:
270
            s = len(self) + s if s < 0 else s
271
            self._list[s] = value
272
            self._other_data[s] = _store()
273
            self.dataChanged.emit(self.index(s), self.index(s))
274
275
    def reverse(self):
276
        self._list.reverse()
277
        self._other_data.reverse()
278
        self.dataChanged.emit(self.index(0), self.index(len(self) - 1))
279
280
    def sort(self, *args, **kwargs):
281
        indices = _argsort(self._list, *args, **kwargs)
282
        lst = [self._list[i] for i in indices]
283
        other = [self._other_data[i] for i in indices]
284
        for i, new_l, new_o in enumerate(zip(lst, other)):
285
            self._list[i] = new_l
286
            self._other_data[i] = new_o
287
        self.dataChanged.emit(self.index(0), self.index(len(self) - 1))
288
289
    def __repr__(self):
290
        return "PyListModel(%s)" % repr(self._list)
291
292
    def __bool__(self):
293
        return len(self) != 0
294
295
    def emitDataChanged(self, indexList):
296
        if isinstance(indexList, int):
297
            indexList = [indexList]
298
299
        #TODO: group indexes into ranges
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
300
        for ind in indexList:
301
            self.dataChanged.emit(self.index(ind), self.index(ind))
302
303
    ###########
304
    # Drag/drop
305
    ###########
306
307
    def supportedDropActions(self):
308
        return self._supportedDropActions
309
310
    def mimeTypes(self):
311
        return self.MIME_TYPES + list(QAbstractListModel.mimeTypes(self))
312
313
    def mimeData(self, indexlist):
314
        if len(indexlist) <= 0:
315
            return None
316
317
        items = [self[i.row()] for i in indexlist]
318
        mime = QAbstractListModel.mimeData(self, indexlist)
319
        data = pickle.dumps(vars)
320
        mime.set_data(self.MIME_TYPE, QByteArray(data))
321
        mime._items = items
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _items was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
322
        return mime
323
324
    def dropMimeData(self, mime, action, row, column, parent):
325
        if action == Qt.IgnoreAction:
326
            return True
327
328
        if not mime.hasFormat(self.MIME_TYPE):
329
            return False
330
331
        if hasattr(mime, "_vars"):
332
            vars_ = mime._vars
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _vars was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
Unused Code introduced by
The variable vars_ seems to be unused.
Loading history...
333
        else:
334
            desc = str(mime.data(self.MIME_TYPE))
335
            vars_ = pickle.loads(desc)
336
337
        return QAbstractListModel.dropMimeData(
338
            self, mime, action, row, column, parent)
339
340
341
class PyListModelTooltip(PyListModel):
342
    def __init__(self):
343
        super().__init__()
344
        self.tooltips = []
345
346
    def data(self, index, role=Qt.DisplayRole):
347
        if role == Qt.ToolTipRole:
348
            return self.tooltips[index.row()]
349
        else:
350
            return super().data(index, role)
351
352
353
class VariableListModel(PyListModel):
354
355
    MIME_TYPE = "application/x-Orange-VariableList"
356
357
    def data(self, index, role=Qt.DisplayRole):
358
        if self._is_index_valid_for(index, self):
359
            var = self[index.row()]
360
            if not isinstance(var, Variable):
361
                return super().data(index, role)
362
            elif role == Qt.DisplayRole:
363
                return var.name
364
            elif role == Qt.DecorationRole:
365
                return gui.attributeIconDict[var]
366
            elif role == Qt.ToolTipRole:
367
                return self.variable_tooltip(var)
368
            else:
369
                return PyListModel.data(self, index, role)
370
371
    def variable_tooltip(self, var):
372
        if var.is_discrete:
373
            return self.discrete_variable_tooltip(var)
374
        elif var.is_continuous:
375
            return self.continuous_variable_toltip(var)
376
        elif var.is_string:
377
            return self.string_variable_tooltip(var)
378
379
    def variable_labels_tooltip(self, var):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
380
        text = ""
381
        if var.attributes:
382
            items = [(safe_text(key), safe_text(value))
383
                     for key, value in var.attributes.items()]
384
            labels = list(map("%s = %s".__mod__, items))
385
            text += "<br/>Variable Labels:<br/>"
386
            text += "<br/>".join(labels)
387
        return text
388
389
    def discrete_variable_tooltip(self, var):
390
        text = "<b>%s</b><br/>Discrete with %i values: " %\
391
               (safe_text(var.name), len(var.values))
392
        text += ", ".join("%r" % safe_text(v) for v in var.values)
393
        text += self.variable_labels_tooltip(var)
394
        return text
395
396
    def continuous_variable_toltip(self, var):
397
        text = "<b>%s</b><br/>Continuous" % safe_text(var.name)
398
        text += self.variable_labels_tooltip(var)
399
        return text
400
401
    def string_variable_tooltip(self, var):
402
        text = "<b>%s</b><br/>String" % safe_text(var.name)
403
        text += self.variable_labels_tooltip(var)
404
        return text
405
406
    def python_variable_tooltip(self, var):
407
        text = "<b>%s</b><br/>Python" % safe_text(var.name)
408
        text += self.variable_labels_tooltip(var)
409
        return text
410
411
_html_replace = [("<", "&lt;"), (">", "&gt;")]
412
413
414
def safe_text(text):
415
    for old, new in _html_replace:
416
        text = text.replace(old, new)
417
    return text
418
419
420
class ListSingleSelectionModel(QItemSelectionModel):
421
    """ Item selection model for list item models with single selection.
422
423
    Defines signal:
424
        - selectedIndexChanged(QModelIndex)
425
426
    """
427
    selectedIndexChanged = Signal(QModelIndex)
428
429
    def __init__(self, model, parent=None):
430
        QItemSelectionModel.__init__(self, model, parent)
431
        self.selectionChanged.connect(self.onSelectionChanged)
432
433
    def onSelectionChanged(self, new, _):
434
        index = list(new.indexes())
435
        if index:
436
            index = index.pop()
437
        else:
438
            index = QModelIndex()
439
440
        self.selectedIndexChanged.emit(index)
441
442
    def selectedRow(self):
443
        """ Return QModelIndex of the selected row or invalid if no selection.
444
        """
445
        rows = self.selectedRows()
446
        if rows:
447
            return rows[0]
448
        else:
449
            return QModelIndex()
450
451
    def select(self, index, flags=QItemSelectionModel.ClearAndSelect):
452
        if isinstance(index, int):
453
            index = self.model().index(index)
454
        return QItemSelectionModel.select(self, index, flags)
455
456
457
def select_row(view, row):
458
    """
459
    Select a `row` in an item view.
460
    """
461
    selmodel = view.selectionModel()
462
    selmodel.select(view.model().index(row, 0),
463
                    QItemSelectionModel.ClearAndSelect |
464
                    QItemSelectionModel.Rows)
465
466
467
class ModelActionsWidget(QWidget):
468
    def __init__(self, actions=None, parent=None,
469
                 direction=QBoxLayout.LeftToRight):
470
        QWidget.__init__(self, parent)
471
        self.actions = []
472
        self.buttons = []
473
        layout = QBoxLayout(direction)
474
        layout.setContentsMargins(0, 0, 0, 0)
475
        self.setContentsMargins(0, 0, 0, 0)
476
        self.setLayout(layout)
477
        if actions is not None:
478
            for action in actions:
479
                self.addAction(action)
480
        self.setLayout(layout)
481
482
    def actionButton(self, action):
483
        if isinstance(action, QAction):
484
            button = QToolButton(self)
485
            button.setDefaultAction(action)
486
            return button
487
        elif isinstance(action, QAbstractButton):
488
            return action
489
490
    def insertAction(self, ind, action, *args):
491
        button = self.actionButton(action)
492
        self.layout().insertWidget(ind, button, *args)
493
        self.buttons.insert(ind, button)
494
        self.actions.insert(ind, action)
495
        return button
496
497
    def addAction(self, action, *args):
498
        return self.insertAction(-1, action, *args)
499
500
501
class TableModel(QAbstractTableModel):
502
    """
503
    An adapter for using Orange.data.Table within Qt's Item View Framework.
504
505
    :param Orange.data.Table sourcedata: Source data table.
506
    :param QObject parent:
507
    """
508
    #: Orange.data.Value for the index.
509
    ValueRole = gui.TableValueRole  # next(gui.OrangeUserRole)
510
    #: Orange.data.Value of the row's class.
511
    ClassValueRole = gui.TableClassValueRole  # next(gui.OrangeUserRole)
512
    #: Orange.data.Variable of the column.
513
    VariableRole = gui.TableVariable  # next(gui.OrangeUserRole)
514
    #: Basic statistics of the column
515
    VariableStatsRole = next(gui.OrangeUserRole)
516
    #: The column's role (position) in the domain.
517
    #: One of Attribute, ClassVar or Meta
518
    DomainRole = next(gui.OrangeUserRole)
519
520
    #: Column domain roles
521
    Attribute, ClassVar, Meta = range(3)
522
523
    #: Default background color for domain roles
524
    ColorForRole = {
525
        Attribute: None,
526
        ClassVar: QColor(160, 160, 160),
527
        Meta: QColor(220, 220, 200)
528
    }
529
530
    #: Standard column descriptor
531
    Column = namedtuple(
532
        "Column", ["var", "role", "background", "format"])
533
    #: Basket column descriptor (i.e. sparse X/Y/metas/ compressed into
534
    #: a single column).
535
    Basket = namedtuple(
536
        "Basket", ["vars", "role", "background", "density", "format"])
537
538
    def __init__(self, sourcedata, parent=None):
539
        super().__init__(parent)
540
        self.source = sourcedata
541
        self.domain = domain = sourcedata.domain
542
543
        self.X_density = sourcedata.X_density()
544
        self.Y_density = sourcedata.Y_density()
545
        self.M_density = sourcedata.metas_density()
546
547
        def format_sparse(vars, datagetter, instance):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in vars.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
548
            data = datagetter(instance)
549
            return ", ".join("{}={}".format(vars[i].name, vars[i].repr_val(v))
550
                             for i, v in zip(data.indices, data.data))
551
552
        def format_sparse_bool(vars, datagetter, instance):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in vars.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
553
            data = datagetter(instance)
554
            return ", ".join(vars[i].name for i in data.indices)
555
556
        def format_dense(var, instance):
557
            return str(instance[var])
558
559
        def make_basket_formater(vars, density, role):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in vars.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
560
            formater = (format_sparse if density == Storage.SPARSE
561
                        else format_sparse_bool)
562
            if role == TableModel.Attribute:
563
                getter = operator.attrgetter("sparse_x")
564
            elif role == TableModel.ClassVar:
565
                getter = operator.attrgetter("sparse_y")
566
            elif role == TableModel.Meta:
567
                getter = operator.attrgetter("sparse_meta")
568
            return partial(formater, vars, getter)
569
570
        def make_basket(vars, density, role):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in vars.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
571
            return TableModel.Basket(
572
                vars, TableModel.Attribute, self.ColorForRole[role], density,
573
                make_basket_formater(vars, density, role)
574
            )
575
576
        def make_column(var, role):
577
            return TableModel.Column(
578
                var, role, self.ColorForRole[role],
579
                partial(format_dense, var)
580
            )
581
582
        columns = []
583
584
        if self.X_density != Storage.DENSE:
585
            coldesc = make_basket(domain.attributes, self.X_density,
586
                                  TableModel.Attribute)
587
            columns.append(coldesc)
588
        else:
589
            columns += [make_column(var, TableModel.Attribute)
590
                        for var in domain.attributes]
591
592
        if self.Y_density != Storage.DENSE:
593
            coldesc = make_basket(domain.class_vars, self.Y_density,
594
                                  TableModel.ClassVar)
595
            columns.append(coldesc)
596
        else:
597
            columns += [make_column(var, TableModel.ClassVar)
598
                        for var in domain.class_vars]
599
600
        if self.M_density != Storage.DENSE:
601
            coldesc = make_basket(domain.metas, self.M_density,
602
                                  TableModel.Meta)
603
            columns.append(coldesc)
604
        else:
605
            columns += [make_column(var, TableModel.Meta)
606
                        for var in domain.metas]
607
608
        #: list of all domain variables (attrs + class_vars + metas)
609
        self.vars = domain.attributes + domain.class_vars + domain.metas
610
        self.columns = columns
611
612
        #: A list of all unique attribute labels (in all variables)
613
        self._labels = sorted(
614
            reduce(operator.ior,
615
                   [set(var.attributes) for var in self.vars],
616
                   set()))
617
618
        #@lru_cache(maxsize=1000)
619
        def row_instance(index):
620
            return self.source[int(index)]
621
        self._row_instance = row_instance
622
623
        # column basic statistics (VariableStatsRole), computed when
624
        # first needed.
625
        self.__stats = None
626
        self.__rowCount = sourcedata.approx_len()
627
        self.__columnCount = len(self.columns)
628
629
        if self.__rowCount > (2 ** 31 - 1):
630
            raise ValueError("len(sourcedata) > 2 ** 31 - 1")
631
632
        self.__sortColumn = -1
633
        self.__sortOrder = Qt.AscendingOrder
634
        # Indices sorting the source table
635
        self.__sortInd = None
636
        # The inverse of __sortInd
637
        self.__sortIndInv = None
638
639
    def sort(self, column, order):
640
        """
641
        Sort the data by `column` index into `order`
642
643
        To reset the sort order pass -1 as the column.
644
645
        :type column: int
646
        :type order: Qt.SortOrder
647
648
        Reimplemented from QAbstractItemModel.sort
649
650
        .. note::
651
            This only affects the model's data presentation, the
652
            underlying data table is left unmodified.
653
654
        """
655
        self.layoutAboutToBeChanged.emit()
656
657
        # Store persistent indices as well as their (actual) rows in the
658
        # source data table.
659
        persistent = self.persistentIndexList()
660
        persistent_rows = numpy.array([ind.row() for ind in persistent], int)
661
        if self.__sortInd is not None:
662
            persistent_rows = self.__sortInd[persistent_rows]
663
664
        self.__sortColumn = column
665
        self.__sortOrder = order
666
667
        if column < 0:
668
            indices = None
669
        else:
670
            keydata = self.columnSortKeyData(column, TableModel.ValueRole)
671
            if keydata is not None:
672
                if keydata.dtype == object:
673
                    indices = sorted(range(self.__rowCount),
674
                                     key=lambda i: str(keydata[i]))
675
                    indices = numpy.array(indices)
676
                else:
677
                    indices = numpy.argsort(keydata, kind="mergesort")
678
            else:
679
                indices = numpy.arange(0, self.__rowCount)
680
681
            if order == Qt.DescendingOrder:
682
                indices = indices[::-1]
683
684
            if self.__sortInd is not None:
685
                indices = self.__sortInd[indices]
686
687
        if indices is not None:
688
            self.__sortInd = indices
689
            self.__sortIndInv = numpy.argsort(indices)
690
        else:
691
            self.__sortInd = None
692
            self.__sortIndInv = None
693
694
        if self.__sortInd is not None:
695
            persistent_rows = self.__sortIndInv[persistent_rows]
696
697
        for pind, row in zip(persistent, persistent_rows):
698
            self.changePersistentIndex(pind, self.index(row, pind.column()))
699
        self.layoutChanged.emit()
700
701
    def columnSortKeyData(self, column, role):
702
        """
703
        Return a sequence of objects which can be used as `keys` for sorting.
704
705
        :param int column: Sort column.
706
        :param Qt.ItemRole role: Sort item role.
707
708
        """
709
        coldesc = self.columns[column]
710
        if isinstance(coldesc, TableModel.Column) \
711
                and role == TableModel.ValueRole:
712
            col_view, _ = self.source.get_column_view(coldesc.var)
713
            col_data = numpy.asarray(col_view)
714
            if self.__sortInd is not None:
715
                col_data = col_data[self.__sortInd]
716
            return col_data
717
        else:
718
            if self.__sortInd is not None:
719
                indices = self.__sortInd
720
            else:
721
                indices = range(self.rowCount())
722
            return numpy.asarray([self.index(i, column).data(role)
723
                                  for i in indices])
724
725
    def sortColumn(self):
726
        """
727
        The column currently used for sorting (-1 if no sorting is applied).
728
        """
729
        return self.__sortColumn
730
731
    def sortOrder(self):
732
        """
733
        The current sort order.
734
        """
735
        return self.__sortOrder
736
737
    def mapToTableRows(self, modelrows):
738
        """
739
        Return the row indices in the source table for the given model rows.
740
        """
741
        if self.__sortColumn < 0:
742
            return modelrows
743
        else:
744
            return self.__sortInd[modelrows].tolist()
745
746
    def mapFromTableRows(self, tablerows):
747
        """
748
        Return the row indices in the model for the given source table rows.
749
        """
750
        if self.__sortColumn < 0:
751
            return tablerows
752
        else:
753
            return self.__sortIndInv[tablerows].tolist()
754
755
    def data(self, index, role,
0 ignored issues
show
Bug Best Practice introduced by
The default value set() (builtins.set) might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
756
             # For optimizing out LOAD_GLOBAL byte code instructions in
757
             # the item role tests.
758
             _str=str,
759
             _Qt_DisplayRole=Qt.DisplayRole,
760
             _Qt_EditRole=Qt.EditRole,
761
             _Qt_BackgroundRole=Qt.BackgroundRole,
762
             _ValueRole=ValueRole,
763
             _ClassValueRole=ClassValueRole,
764
             _VariableRole=VariableRole,
765
             _DomainRole=DomainRole,
766
             _VariableStatsRole=VariableStatsRole,
767
             # Some cached local precomputed values.
768
             # All of the above roles we respond to
769
             _recognizedRoles=set([Qt.DisplayRole,
770
                                   Qt.EditRole,
771
                                   Qt.BackgroundRole,
772
                                   ValueRole,
773
                                   ClassValueRole,
774
                                   VariableRole,
775
                                   DomainRole,
776
                                   VariableStatsRole]),
777
             ):
778
        """
779
        Reimplemented from `QAbstractItemModel.data`
780
        """
781
        if role not in _recognizedRoles:
782
            return None
783
784
        row, col = index.row(), index.column()
785
        if  not 0 <= row <= self.__rowCount:
786
            return None
787
788
        if self.__sortInd is not None:
789
            row = self.__sortInd[row]
790
791
        instance = self._row_instance(row)
792
        coldesc = self.columns[col]
793
794
        if role == _Qt_DisplayRole:
795
            return coldesc.format(instance)
796
        elif role == _Qt_EditRole and isinstance(coldesc, TableModel.Column):
797
            return instance[coldesc.var]
798
        elif role == _Qt_BackgroundRole:
799
            return coldesc.background
800
            return self.color_for_role[coldesc.role]
0 ignored issues
show
Unused Code introduced by
This code does not seem to be reachable.
Loading history...
801
        elif role == _ValueRole and isinstance(coldesc, TableModel.Column):
802
            return instance[coldesc.var]
803
        elif role == _ClassValueRole:
804
            try:
805
                return instance.get_class()
806
            except TypeError:
807
                return None
808
        elif role == _VariableRole and isinstance(coldesc, TableModel.Column):
809
            return coldesc.var
810
        elif role == _DomainRole:
811
            return coldesc.role
812
        elif role == _VariableStatsRole:
813
            return self._stats_for_column(col)
814
        else:
815
            return None
816
817
    def setData(self, index, value, role):
818
        row, col = self.__sortIndInv[index.row()], index.column()
819
        if role == Qt.EditRole:
820
            try:
821
                self.source[row, col] = value
822
            except (TypeError, IndexError):
823
                return False
824
            else:
825
                self.dataChanged.emit(index, index)
826
                return True
827
        else:
828
            return False
829
830
    def parent(self, index):
0 ignored issues
show
Unused Code introduced by
The argument index seems to be unused.
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
831
        """Reimplemented from `QAbstractTableModel.parent`."""
832
        return QModelIndex()
833
834
    def rowCount(self, parent=QModelIndex()):
835
        """Reimplemented from `QAbstractTableModel.rowCount`."""
836
        return 0 if parent.isValid() else self.__rowCount
837
838
    def columnCount(self, parent=QModelIndex()):
839
        """Reimplemented from `QAbstractTableModel.columnCount`."""
840
        return 0 if parent.isValid() else self.__columnCount
841
842
    def headerData(self, section, orientation, role):
843
        """Reimplemented from `QAbstractTableModel.headerData`."""
844
        if orientation == Qt.Vertical:
845
            if role == Qt.DisplayRole:
846
                if self.__sortInd is not None:
847
                    return int(self.__sortInd[section] + 1)
848
                else:
849
                    return int(section + 1)
850
            else:
851
                return None
852
853
        coldesc = self.columns[section]
854
        if role == Qt.DisplayRole:
855
            if isinstance(coldesc, TableModel.Basket):
856
                return "{...}"
857
            else:
858
                return coldesc.var.name
859
        elif role == Qt.ToolTipRole:
860
            return self._tooltip(coldesc)
861
        elif role == TableModel.VariableRole \
862
                and isinstance(coldesc, TableModel.Column):
863
            return coldesc.var
864
        elif role == TableModel.VariableStatsRole:
865
            return self._stats_for_column(section)
866
        elif role == TableModel.DomainRole:
867
            return coldesc.role
868
        else:
869
            return None
870
871
    def _tooltip(self, coldesc):
872
        """
873
        Return an header tool tip text for an `column` descriptor.
874
        """
875
        if isinstance(coldesc, TableModel.Basket):
876
            return None
877
878
        labels = self._labels
879
        variable = coldesc.var
880
        pairs = [(escape(key), escape(str(variable.attributes[key])))
881
                 for key in labels if key in variable.attributes]
882
        tip = "<b>%s</b>" % escape(variable.name)
883
        tip = "<br/>".join([tip] + ["%s = %s" % pair for pair in pairs])
884
        return tip
885
886
    def _stats_for_column(self, column):
887
        """
888
        Return BasicStats for `column` index.
889
        """
890
        coldesc = self.columns[column]
891
        if isinstance(coldesc, TableModel.Basket):
892
            return None
893
894
        if self.__stats is None:
895
            self.__stats = datacaching.getCached(
896
                self.source, basic_stats.DomainBasicStats,
897
                (self.source, True)
898
            )
899
900
        return self.__stats[coldesc.var]
901