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.

Issues (4082)

Orange/widgets/gui.py (85 issues)

1
import math
2
import os
3
import re
4
import itertools
5
6
import pkg_resources
7
import numpy
0 ignored issues
show
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...
8
9
from PyQt4 import QtGui, QtCore, QtWebKit
0 ignored issues
show
The import PyQt4 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
from PyQt4.QtCore import Qt, pyqtSignal as Signal
0 ignored issues
show
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...
11
from PyQt4.QtGui import QCursor, QApplication
0 ignored issues
show
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...
12
13
import Orange.data
14
from Orange.widgets.utils import getdeepattr
15
from Orange.data import ContinuousVariable, StringVariable, DiscreteVariable, Variable
16
from Orange.widgets.utils import vartype
17
from Orange.widgets.utils.constants import CONTROLLED_ATTRIBUTES, ATTRIBUTE_CONTROLLERS
18
19
YesNo = NoYes = ("No", "Yes")
20
_enter_icon = None
21
__re_label = re.compile(r"(^|[^%])%\((?P<value>[a-zA-Z]\w*)\)")
22
23
24
OrangeUserRole = itertools.count(Qt.UserRole)
25
26
27
def resource_filename(path):
28
    """
29
    Return a resource filename (package data) for path.
30
    """
31
    return pkg_resources.resource_filename(__name__, path)
32
33
34
class TableWidget(QtGui.QTableWidget):
35
    """ An easy to use, row-oriented table widget """
36
37
    ROW_DATA_ROLE = QtCore.Qt.UserRole + 1
38
    ITEM_DATA_ROLE = ROW_DATA_ROLE + 1
39
40
    class TableWidgetNumericItem(QtGui.QTableWidgetItem):
41
        """TableWidgetItem that sorts numbers correctly!"""
42
        def __lt__(self, other):
43
            return (self.data(TableWidget.ITEM_DATA_ROLE) <
44
                    other.data(TableWidget.ITEM_DATA_ROLE))
45
46
    def selectionChanged(self, selected:[QtGui.QItemSelectionRange], deselected:[QtGui.QItemSelectionRange]):
0 ignored issues
show
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...
47
        """Override or monkey-patch this method to catch selection changes"""
48
        super().selectionChanged(selected, deselected)
49
50
    def __setattr__(self, attr, value):
51
        """
52
        The following selectionChanged magic ensures selectionChanged
53
        slot, when monkey-patched, always calls the super's selectionChanged
54
        first (--> avoids Qt quirks), and the user needs not care about that.
55
        """
56
        if attr == 'selectionChanged':
57
            func = value
58
            @QtCore.pyqtSlot(QtGui.QItemSelection, QtGui.QItemSelection)
59
            def _f(selected, deselected):
60
                super(self.__class__, self).selectionChanged(selected, deselected)
61
                func(selected, deselected)
62
            value = _f
63
        self.__dict__[attr] = value
64
65
    def _update_headers(func):
0 ignored issues
show
Coding Style Best Practice introduced by
Methods should have self as first argument.

It is a widespread convention and generally a good practice to name the first argument of methods self.

class SomeClass:
    def some_method(self):
        # ... do something
Loading history...
66
        """Decorator to update certain table features after method calls"""
67
        def _f(self, *args, **kwargs):
68
            func(self, *args, **kwargs)
69
            if self.col_labels is not None:
70
                self.setHorizontalHeaderLabels(self.col_labels)
71
            if self.row_labels is not None:
72
                self.setVerticalHeaderLabels(self.row_labels)
73
            if self.stretch_last_section:
74
                self.horizontalHeader().setStretchLastSection(True)
75
        return _f
76
77
    @_update_headers
78
    def __init__(self,
79
                 parent=None,
80
                 col_labels=None,
81
                 row_labels=None,
82
                 stretch_last_section=True,
83
                 multi_selection=False,
84
                 select_rows=False):
85
        """
86
        Parameters
87
        ----------
88
        parent: QObject
89
            Parent QObject. If parent has layout(), this widget is added to it.
90
        col_labels: list of str
91
            Labels or [] (sequential numbers) or None (no horizontal header)
92
        row_label: list_of_str
93
            Labels or [] (sequential numbers) or None (no vertical header)
94
        stretch_last_section: bool
95
        multi_selection: bool
96
            Single selection if False
97
        select_rows: bool
98
            If True, select whole rows instead of individual cells.
99
        """
100
        super().__init__(parent)
101
        self._column_filter = {}
102
        self.col_labels = col_labels
103
        self.row_labels = row_labels
104
        self.stretch_last_section = stretch_last_section
105
        try: parent.layout().addWidget(self)
106
        except (AttributeError, TypeError): pass
0 ignored issues
show
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
107
        if col_labels is None:
108
            self.horizontalHeader().setVisible(False)
109
        if row_labels is None:
110
            self.verticalHeader().setVisible(False)
111
        if multi_selection:
112
            self.setSelectionMode(self.MultiSelection)
113
        if select_rows:
114
            self.setSelectionBehavior(self.SelectRows)
115
        self.setHorizontalScrollMode(self.ScrollPerPixel)
116
        self.setVerticalScrollMode(self.ScrollPerPixel)
117
        self.setEditTriggers(self.NoEditTriggers)
118
        self.setAlternatingRowColors(True)
119
        self.setShowGrid(False)
120
        self.setSortingEnabled(True)
121
122
    @_update_headers
123
    def addRow(self, items:tuple, data=None):
124
        """
125
        Appends iterable of `items` as the next row, optionally setting row
126
        data to `data`. Each item of `items` can be a string or tuple
127
        (item_name, item_data) if individual, cell-data is required.
128
        """
129
        row_data = data
130
        row = self.rowCount()
131
        self.insertRow(row)
132
        col_count = max(len(items), self.columnCount())
133
        if col_count != self.columnCount():
134
            self.setColumnCount(col_count)
135
        for col, item_data in enumerate(items):
136
            if isinstance(item_data, str):
137
                name = item_data
138
            elif hasattr(item_data, '__iter__') and len(item_data) == 2:
139
                name, item_data = item_data
140
            elif isinstance(item_data, float):
141
                name = '{:.4f}'.format(item_data)
142
            else:
143
                name = str(item_data)
144
            if isinstance(item_data, (float, int, numpy.number)):
145
                item = self.TableWidgetNumericItem(name)
146
            else:
147
                item = QtGui.QTableWidgetItem(name)
148
            item.setData(self.ITEM_DATA_ROLE, item_data)
149
            if col in self._column_filter:
150
                item = self._column_filter[col](item) or item
151
            self.setItem(row, col, item)
152
        self.resizeColumnsToContents()
153
        self.resizeRowsToContents()
154
        if row_data is not None:
155
            self.setRowData(row, row_data)
156
157
    def rowData(self, row:int):
158
        return self.item(row, 0).data(self.ROW_DATA_ROLE)
159
160
    def setRowData(self, row:int, data):
161
        self.item(row, 0).setData(self.ROW_DATA_ROLE, data)
162
163
    def setColumnFilter(self, item_filter_func, columns:int or list):
164
        """
165
        Pass item(s) at column(s) through `item_filter_func` before
166
        insertion. Useful for setting specific columns to bold or similar.
167
        """
168
        try: iter(columns)
169
        except TypeError: columns = [columns]
170
        for i in columns:
171
            self._column_filter[i] = item_filter_func
172
173
    def clear(self):
174
        super().clear()
175
        self.setRowCount(0)
176
        self.setColumnCount(0)
177
178
    def selectFirstRow(self):
179
        if self.rowCount() > 0:
180
            self.selectRow(0)
181
182
    def selectRowsWhere(self, col, value, n_hits=-1,
183
                        flags=QtCore.Qt.MatchExactly, _select=True):
184
        """
185
        Select (also return) at most `n_hits` rows where column `col`
186
        has value (``data()``) `value`.
187
        """
188
        model = self.model()
189
        matches = model.match(model.index(0, col),
190
                              self.ITEM_DATA_ROLE,
191
                              value,
192
                              n_hits,
193
                              flags)
194
        model = self.selectionModel()
195
        selection_flag = model.Select if _select else model.Deselect
196
        for index in matches:
197
            if _select ^ model.isSelected(index):
198
                model.select(index, selection_flag | model.Rows)
199
        return matches
200
201
    def deselectRowsWhere(self, col, value, n_hits=-1,
202
                          flags=QtCore.Qt.MatchExactly):
203
        """
204
        Deselect (also return) at most `n_hits` rows where column `col`
205
        has value (``data()``) `value`.
206
        """
207
        return self.selectRowsWhere(col, value, n_hits, flags, False)
208
209
210
class WebviewWidget(QtWebKit.QWebView):
211
    """WebKit window in a window"""
212
    def __init__(self, parent=None, bridge=None, html=None, debug=None):
213
        """
214
        Parameters
215
        ----------
216
        parent: QObject
217
            Parent QObject. If parent has layout(), this widget is added to it.
218
        bridge: QObject
219
            The "bridge" object exposed as ``window.pybridge`` in JavaScript.
220
            Any bridge methods desired to be accessible from JS need to be
221
            decorated ``@QtCore.pyqtSlot(<*args>, result=<type>)``.
222
        html: str
223
            HTML content to set in the webview.
224
        debug: bool
225
            If True, enable context menu and webkit inspector.
226
        """
227
        super().__init__(parent)
228
        self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
229
                                             QtGui.QSizePolicy.Expanding))
230
        self._bridge = bridge
231
        try: parent.layout().addWidget(self)
232
        except (AttributeError, TypeError): pass
0 ignored issues
show
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
233
        settings = self.settings()
234
        settings.setAttribute(settings.LocalContentCanAccessFileUrls, True)
235
        if debug is None:
236
            import logging
237
            debug = logging.getLogger().level <= logging.DEBUG
238
        if debug:
239
            settings.setAttribute(settings.DeveloperExtrasEnabled, True)
240
        else:
241
            self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
242
        if html:
243
            self.setHtml(html)
244
245
    def setContent(self, data, mimetype, url=''):
246
        super().setContent(data, mimetype, QtCore.QUrl(url))
247
        if self._bridge:
248
            self.page().mainFrame().addToJavaScriptWindowObject('pybridge', self._bridge)
249
250
    def setHtml(self, html, url=''):
251
        self.setContent(html.encode('utf-8'), 'text/html', url)
252
253
    def sizeHint(self):
0 ignored issues
show
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...
254
        return QtCore.QSize(600, 500)
255
256
    def evalJS(self, javascript):
257
        self.page().mainFrame().evaluateJavaScript(javascript)
258
259
260
class ControlledAttributesDict(dict):
261
    def __init__(self, master):
262
        super().__init__()
263
        self.master = master
264
265
    def __setitem__(self, key, value):
266
        if key not in self:
267
            dict.__setitem__(self, key, [value])
268
        else:
269
            dict.__getitem__(self, key).append(value)
270
        set_controllers(self.master, key, self.master, "")
271
272
callbacks = lambda obj: getattr(obj, CONTROLLED_ATTRIBUTES, {})
273
subcontrollers = lambda obj: getattr(obj, ATTRIBUTE_CONTROLLERS, {})
274
275
276
def notify_changed(obj, name, value):
277
    if name in callbacks(obj):
278
        for callback in callbacks(obj)[name]:
279
            callback(value)
280
        return
281
282
    for controller, prefix in list(subcontrollers(obj)):
283
        if getdeepattr(controller, prefix, None) != obj:
284
            del subcontrollers(obj)[(controller, prefix)]
285
            continue
286
287
        full_name = prefix + "." + name
288
        if full_name in callbacks(controller):
289
            for callback in callbacks(controller)[full_name]:
290
                callback(value)
291
            continue
292
293
        prefix = full_name + "."
294
        prefix_length = len(prefix)
295
        for controlled in callbacks(controller):
296
            if controlled[:prefix_length] == prefix:
297
                set_controllers(value, controlled[prefix_length:], controller, full_name)
298
299
300
def set_controllers(obj, controlled_name, controller, prefix):
301
    while obj:
302
        if prefix:
303
            if hasattr(obj, ATTRIBUTE_CONTROLLERS):
304
                getattr(obj, ATTRIBUTE_CONTROLLERS)[(controller, prefix)] = True
305
            else:
306
                setattr(obj, ATTRIBUTE_CONTROLLERS, {(controller, prefix): True})
307
        parts = controlled_name.split(".", 1)
308
        if len(parts) < 2:
309
            break
310
        new_prefix, controlled_name = parts
311
        obj = getattr(obj, new_prefix, None)
312
        if prefix:
313
            prefix += '.'
314
        prefix += new_prefix
315
316
317
class OWComponent:
318
    def __init__(self, widget):
319
        setattr(self, CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self))
320
321
        if widget.settingsHandler:
322
            widget.settingsHandler.initialize(self)
323
324
    def __setattr__(self, key, value):
325
        super().__setattr__(key, value)
326
        notify_changed(self, key, value)
327
328
329
def miscellanea(control, box, parent,
330
                addToLayout=True, stretch=0, sizePolicy=None, addSpace=False,
331
                disabled=False, tooltip=None):
332
    """
333
    Helper function that sets various properties of the widget using a common
334
    set of arguments.
335
336
    The function
337
    - sets the `control`'s attribute `box`, if `box` is given and `control.box`
338
    is not yet set,
339
    - attaches a tool tip to the `control` if specified,
340
    - disables the `control`, if `disabled` is set to `True`,
341
    - adds the `box` to the `parent`'s layout unless `addToLayout` is set to
342
    `False`; the stretch factor can be specified,
343
    - adds the control into the box's layout if the box is given (regardless
344
    of `addToLayout`!)
345
    - sets the size policy for the box or the control, if the policy is given,
346
    - adds space in the `parent`'s layout after the `box` if `addSpace` is set
347
    and `addToLayout` is not `False`.
348
349
    If `box` is the same as `parent` it is set to `None`; this is convenient
350
    because of the way complex controls are inserted.
351
352
    :param control: the control, e.g. a `QCheckBox`
353
    :type control: PyQt4.QtGui.QWidget
354
    :param box: the box into which the widget was inserted
355
    :type box: PyQt4.QtGui.QWidget or None
356
    :param parent: the parent into whose layout the box or the control will be
357
        inserted
358
    :type parent: PyQt4.QtGui.QWidget
359
    :param addSpace: the amount of space to add after the widget
360
    :type addSpace: bool or int
361
    :param disabled: If set to `True`, the widget is initially disabled
362
    :type disabled: bool
363
    :param addToLayout: If set to `False` the widget is not added to the layout
364
    :type addToLayout: bool
365
    :param stretch: the stretch factor for this widget, used when adding to
366
        the layout (default: 0)
367
    :type stretch: int
368
    :param tooltip: tooltip that is attached to the widget
369
    :type tooltip: str or None
370
    :param sizePolicy: the size policy for the box or the control
371
    :type sizePolicy: PyQt4.QtQui.QSizePolicy
372
    """
373
    if disabled:
374
        # if disabled==False, do nothing; it can be already disabled
375
        control.setDisabled(disabled)
376
    if tooltip is not None:
377
        control.setToolTip(tooltip)
378
    if box is parent:
379
        box = None
380
    elif box and box is not control and not hasattr(control, "box"):
381
        control.box = box
382
    if box and box.layout() is not None and \
383
            isinstance(control, QtGui.QWidget) and \
384
            box.layout().indexOf(control) == -1:
385
        box.layout().addWidget(control)
386
    if sizePolicy is not None:
387
        (box or control).setSizePolicy(sizePolicy)
388
    if addToLayout and parent and parent.layout() is not None:
389
        parent.layout().addWidget(box or control, stretch)
390
        _addSpace(parent, addSpace)
391
392
393
def setLayout(widget, orientation):
394
    """
395
    Set the layout of the widget according to orientation. Argument
396
    `orientation` can be an instance of :obj:`~PyQt4.QtGui.QLayout`, in which
397
    case is it used as it is. If `orientation` is `'vertical'` or `True`,
398
    the layout is set to :obj:`~PyQt4.QtGui.QVBoxLayout`. If it is
399
    `'horizontal'` or `False`, it is set to :obj:`~PyQt4.QtGui.QVBoxLayout`.
400
401
    :param widget: the widget for which the layout is being set
402
    :type widget: PyQt4.QtGui.QWidget
403
    :param orientation: orientation for the layout
404
    :type orientation: str or bool or PyQt4.QtGui.QLayout
405
    """
406
    if isinstance(orientation, QtGui.QLayout):
407
        widget.setLayout(orientation)
408
    elif orientation == 'horizontal' or not orientation:
409
        widget.setLayout(QtGui.QHBoxLayout())
410
    else:
411
        widget.setLayout(QtGui.QVBoxLayout())
412
413
414
def _enterButton(parent, control, placeholder=True):
415
    """
416
    Utility function that returns a button with a symbol for "Enter" and
417
    optionally a placeholder to show when the enter button is hidden. Both
418
    are inserted into the parent's layout, if it has one. If placeholder is
419
    constructed it is shown and the button is hidden.
420
421
    The height of the button is the same as the height of the widget passed
422
    as argument `control`.
423
424
    :param parent: parent widget into which the button is inserted
425
    :type parent: PyQt4.QtGui.QWidget
426
    :param control: a widget for determining the height of the button
427
    :type control: PyQt4.QtGui.QWidget
428
    :param placeholder: a flag telling whether to construct a placeholder
429
        (default: True)
430
    :type placeholder: bool
431
    :return: a tuple with a button and a place holder (or `None`)
432
    :rtype: PyQt4.QtGui.QToolButton or tuple
433
    """
434
    global _enter_icon
435
    if not _enter_icon:
436
        _enter_icon = QtGui.QIcon(
437
            os.path.dirname(__file__) + "/icons/Dlg_enter.png")
438
    button = QtGui.QToolButton(parent)
0 ignored issues
show
Comprehensibility Bug introduced by
button is re-defining a name which is already available in the outer-scope (previously defined on line 1150).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
439
    height = control.sizeHint().height()
440
    button.setFixedSize(height, height)
441
    button.setIcon(_enter_icon)
442
    if parent.layout() is not None:
443
        parent.layout().addWidget(button)
444
    if placeholder:
445
        button.hide()
446
        holder = QtGui.QWidget(parent)
447
        holder.setFixedSize(height, height)
448
        if parent.layout() is not None:
449
            parent.layout().addWidget(holder)
450
    else:
451
        holder = None
452
    return button, holder
453
454
455
def _addSpace(widget, space):
456
    """
457
    A helper function that adds space into the widget, if requested.
458
    The function is called by functions that have the `addSpace` argument.
459
460
    :param widget: Widget into which to insert the space
461
    :type widget: PyQt4.QtGui.QWidget
462
    :param space: Amount of space to insert. If False, the function does
463
        nothing. If the argument is an `int`, the specified space is inserted.
464
        Otherwise, the default space is inserted by calling a :obj:`separator`.
465
    :type space: bool or int
466
    """
467
    if space:
468
        if type(space) == int:  # distinguish between int and bool!
469
            separator(widget, space, space)
470
        else:
471
            separator(widget)
472
473
474
def separator(widget, width=4, height=4):
475
    """
476
    Add a separator of the given size into the widget.
477
478
    :param widget: the widget into whose layout the separator is added
479
    :type widget: PyQt4.QtGui.QWidget
480
    :param width: width of the separator
481
    :type width: int
482
    :param height: height of the separator
483
    :type height: int
484
    :return: separator
485
    :rtype: PyQt4.QtGui.QWidget
486
    """
487
    sep = QtGui.QWidget(widget)
488
    if widget.layout() is not None:
489
        widget.layout().addWidget(sep)
490
    sep.setFixedSize(width, height)
491
    return sep
492
493
494
def rubber(widget):
495
    """
496
    Insert a stretch 100 into the widget's layout
497
    """
498
    widget.layout().addStretch(100)
499
500
501
def widgetBox(widget, box=None, orientation='vertical', margin=None, spacing=4,
502
              **misc):
503
    """
504
    Construct a box with vertical or horizontal layout, and optionally,
505
    a border with an optional label.
506
507
    If the widget has a frame, the space after the widget is added unless
508
    explicitly disabled.
509
510
    :param widget: the widget into which the box is inserted
511
    :type widget: PyQt4.QtGui.QWidget or None
512
    :param box: tells whether the widget has a border, and its label
513
    :type box: int or str or None
514
    :param orientation: orientation for the layout. If the argument is an
515
        instance of :obj:`~PyQt4.QtGui.QLayout`, it is used as a layout. If
516
        "horizontal" or false-ish, the layout is horizontal
517
        (:obj:`~PyQt4.QtGui.QHBoxLayout`), otherwise vertical
518
        (:obj:`~PyQt4.QtGui.QHBoxLayout`).
519
    :type orientation: str, int or :obj:`PyQt4.QtGui.QLayout`
520
    :param sizePolicy: The size policy for the widget (default: None)
521
    :type sizePolicy: :obj:`~PyQt4.QtGui.QSizePolicy`
522
    :param margin: The margin for the layout. Default is 7 if the widget has
523
        a border, and 0 if not.
524
    :type margin: int
525
    :param spacing: Spacing within the layout (default: 4)
526
    :type spacing: int
527
    :return: Constructed box
528
    :rtype: PyQt4.QtGui.QGroupBox or PyQt4.QtGui.QWidget
529
    """
530
    if box:
531
        b = QtGui.QGroupBox(widget)
532
        if isinstance(box, str):
533
            b.setTitle(" " + box.strip() + " ")
534
        if margin is None:
535
            margin = 7
536
    else:
537
        b = QtGui.QWidget(widget)
538
        b.setContentsMargins(0, 0, 0, 0)
539
        if margin is None:
540
            margin = 0
541
    setLayout(b, orientation)
542
    b.layout().setSpacing(spacing)
543
    b.layout().setMargin(margin)
544
    misc.setdefault('addSpace', bool(box))
545
    miscellanea(b, None, widget, **misc)
546
    return b
547
548
549
def indentedBox(widget, sep=20, orientation="vertical", **misc):
550
    """
551
    Creates an indented box. The function can also be used "on the fly"::
552
553
        gui.checkBox(gui.indentedBox(box), self, "spam", "Enable spam")
554
555
    To align the control with a check box, use :obj:`checkButtonOffsetHint`::
556
557
        gui.hSlider(gui.indentedBox(self.interBox), self, "intervals")
558
559
    :param widget: the widget into which the box is inserted
560
    :type widget: PyQt4.QtGui.QWidget
561
    :param sep: Indent size (default: 20)
562
    :type sep: int
563
    :param orientation: layout of the inserted box; see :obj:`widgetBox` for
564
        details
565
    :type orientation: str, int or PyQt4.QtGui.QLayout
566
    :return: Constructed box
567
    :rtype: PyQt4.QtGui.QGroupBox or PyQt4.QtGui.QWidget
568
    """
569
    outer = widgetBox(widget, orientation=False, spacing=0)
570
    separator(outer, sep, 0)
571
    indented = widgetBox(outer, orientation=orientation)
572
    miscellanea(indented, outer, widget, **misc)
573
    return indented
574
575
576
def widgetLabel(widget, label="", labelWidth=None, **misc):
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
577
    """
578
    Construct a simple, constant label.
579
580
    :param widget: the widget into which the box is inserted
581
    :type widget: PyQt4.QtGui.QWidget or None
582
    :param label: The text of the label (default: None)
583
    :type label: str
584
    :param labelWidth: The width of the label (default: None)
585
    :type labelWidth: int
586
    :return: Constructed label
587
    :rtype: PyQt4.QtGui.QLabel
588
    """
589
    lbl = QtGui.QLabel(label, widget)
590
    if labelWidth:
591
        lbl.setFixedSize(labelWidth, lbl.sizeHint().height())
592
    miscellanea(lbl, None, widget, **misc)
593
    return lbl
594
595
596
597
def label(widget, master, label, labelWidth=None, box=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
598
          orientation="vertical", **misc):
0 ignored issues
show
The argument orientation seems to be unused.
Loading history...
599
    """
600
    Construct a label that contains references to the master widget's
601
    attributes; when their values change, the label is updated.
602
603
    Argument :obj:`label` is a format string following Python's syntax
604
    (see the corresponding Python documentation): the label's content is
605
    rendered as `label % master.__dict__`. For instance, if the
606
    :obj:`label` is given as "There are %(mm)i monkeys", the value of
607
    `master.mm` (which must be an integer) will be inserted in place of
608
    `%(mm)i`.
609
610
    :param widget: the widget into which the box is inserted
611
    :type widget: PyQt4.QtGui.QWidget or None
612
    :param master: master widget
613
    :type master: OWWidget or OWComponent
614
    :param label: The text of the label, including attribute names
615
    :type label: str
616
    :param labelWidth: The width of the label (default: None)
617
    :type labelWidth: int
618
    :return: label
619
    :rtype: PyQt4.QtGui.QLabel
620
    """
621
    if box:
622
        b = widgetBox(widget, box, orientation=None, addToLayout=False)
623
    else:
624
        b = widget
625
626
    lbl = QtGui.QLabel("", b)
627
    reprint = CallFrontLabel(lbl, label, master)
628
    for mo in __re_label.finditer(label):
629
        getattr(master, CONTROLLED_ATTRIBUTES)[mo.group("value")] = reprint
630
    reprint()
631
    if labelWidth:
632
        lbl.setFixedSize(labelWidth, lbl.sizeHint().height())
633
    miscellanea(lbl, b, widget, **misc)
634
    return lbl
635
636
637
class SpinBoxWFocusOut(QtGui.QSpinBox):
638
    """
639
    A class derived from QtGui.QSpinBox, which postpones the synchronization
640
    of the control's value with the master's attribute until the user presses
641
    Enter or clicks an icon that appears beside the spin box when the value
642
    is changed.
643
644
    The class overloads :obj:`onChange` event handler to show the commit button,
645
    and :obj:`onEnter` to commit the change when enter is pressed.
646
647
    .. attribute:: enterButton
648
649
        A widget (usually an icon) that is shown when the value is changed.
650
651
    .. attribute:: placeHolder
652
653
        A placeholder which is shown when the button is hidden
654
655
    .. attribute:: inSetValue
656
657
        A flag that is set when the value is being changed through
658
        :obj:`setValue` to prevent the programmatic changes from showing the
659
        commit button.
660
    """
661
662
    def __init__(self, minv, maxv, step, parent=None):
663
        """
664
        Construct the object and set the range (`minv`, `maxv`) and the step.
665
        :param minv: Minimal value
666
        :type minv: int
667
        :param maxv: Maximal value
668
        :type maxv: int
669
        :param step: Step
670
        :type step: int
671
        :param parent: Parent widget
672
        :type parent: PyQt4.QtGui.QWidget
673
        """
674
        super().__init__(parent)
675
        self.setRange(minv, maxv)
676
        self.setSingleStep(step)
677
        self.inSetValue = False
678
        self.enterButton = None
679
        self.placeHolder = None
680
681
    def onChange(self, _):
682
        """
683
        Hides the place holder and shows the commit button unless
684
        :obj:`inSetValue` is set.
685
        """
686
        if not self.inSetValue:
687
            self.placeHolder.hide()
688
            self.enterButton.show()
689
690
    def onEnter(self):
691
        """
692
        If the commit button is visible, the overload event handler commits
693
        the change by calling the appropriate callbacks. It also hides the
694
        commit button and shows the placeHolder.
695
        """
696
        if self.enterButton.isVisible():
697
            self.enterButton.hide()
698
            self.placeHolder.show()
699
            if self.cback:
700
                self.cback(int(str(self.text())))
701
            if self.cfunc:
702
                self.cfunc()
703
704
    # doesn't work: it's probably LineEdit's focusOut that we should
705
    # (but can't) catch
706
    def focusOutEvent(self, *e):
707
        """
708
        This handler was intended to catch the focus out event and reintepret
709
        it as if enter was pressed. It does not work, though.
710
        """
711
        super().focusOutEvent(*e)
712
        if self.enterButton and self.enterButton.isVisible():
713
            self.onEnter()
714
715
    def setValue(self, value):
716
        """
717
        Set the :obj:`inSetValue` flag and call the inherited method.
718
        """
719
        self.inSetValue = True
720
        super().setValue(value)
721
        self.inSetValue = False
722
723
724
class DoubleSpinBoxWFocusOut(QtGui.QDoubleSpinBox):
725
    """
726
    Same as :obj:`SpinBoxWFocusOut`, except that it is derived from
727
    :obj:`~PyQt4.QtGui.QDoubleSpinBox`"""
728
    def __init__(self, minv, maxv, step, parent):
729
        super().__init__(parent)
730
        self.setDecimals(math.ceil(-math.log10(step)))
731
        self.setRange(minv, maxv)
732
        self.setSingleStep(step)
733
        self.inSetValue = False
734
        self.enterButton = None
735
        self.placeHolder = None
736
737
    def onChange(self, _):
738
        if not self.inSetValue:
739
            self.placeHolder.hide()
740
            self.enterButton.show()
741
742
    def onEnter(self):
743
        if self.enterButton.isVisible():
744
            self.enterButton.hide()
745
            self.placeHolder.show()
746
            if self.cback:
747
                self.cback(float(str(self.text()).replace(",", ".")))
748
            if self.cfunc:
749
                self.cfunc()
750
751
    # doesn't work: it's probably LineEdit's focusOut that we should
752
    # (and can't) catch
753
    def focusOutEvent(self, *e):
754
        super().focusOutEvent(*e)
755
        if self.enterButton and self.enterButton.isVisible():
756
            self.onEnter()
757
758
    def setValue(self, value):
759
        self.inSetValue = True
760
        super().setValue(value)
761
        self.inSetValue = False
762
763
764
def spin(widget, master, value, minv, maxv, step=1, box=None, label=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
765
         labelWidth=None, orientation=None, callback=None,
766
         controlWidth=None, callbackOnReturn=False, checked=None,
767
         checkCallback=None, posttext=None, disabled=False,
768
         alignment=Qt.AlignLeft, keyboardTracking=True,
769
         decimals=None, spinType=int, **misc):
770
    """
771
    A spinbox with lots of bells and whistles, such as a checkbox and various
772
    callbacks. It constructs a control of type :obj:`SpinBoxWFocusOut` or
773
    :obj:`DoubleSpinBoxWFocusOut`.
774
775
    :param widget: the widget into which the box is inserted
776
    :type widget: PyQt4.QtGui.QWidget or None
777
    :param master: master widget
778
    :type master: OWWidget or OWComponent
779
    :param value: the master's attribute with which the value is synchronized
780
    :type value:  str
781
    :param minv: minimal value
782
    :type minv: int
783
    :param maxv: maximal value
784
    :type maxv: int
785
    :param step: step (default: 1)
786
    :type step: int
787
    :param box: tells whether the widget has a border, and its label
788
    :type box: int or str or None
789
    :param label: label that is put in above or to the left of the spin box
790
    :type label: str
791
    :param labelWidth: optional label width (default: None)
792
    :type labelWidth: int
793
    :param orientation: tells whether to put the label above (`"vertical"` or
794
        `True`) or to the left (`"horizontal"` or `False`)
795
    :type orientation: int or bool or str
796
    :param callback: a function that is called when the value is entered; if
797
        :obj:`callbackOnReturn` is `True`, the function is called when the
798
        user commits the value by pressing Enter or clicking the icon
799
    :type callback: function
800
    :param controlWidth: the width of the spin box
801
    :type controlWidth: int
802
    :param callbackOnReturn: if `True`, the spin box has an associated icon
803
        that must be clicked to confirm the value (default: False)
804
    :type callbackOnReturn: bool
805
    :param checked: if not None, a check box is put in front of the spin box;
806
        when unchecked, the spin box is disabled. Argument `checked` gives the
807
        name of the master's attribute given whose value is synchronized with
808
        the check box's state (default: None).
809
    :type checked: str
810
    :param checkCallback: a callback function that is called when the check
811
        box's state is changed
812
    :type checkCallback: function
813
    :param posttext: a text that is put to the right of the spin box
814
    :type posttext: str
815
    :param alignment: alignment of the spin box (e.g. `QtCore.Qt.AlignLeft`)
816
    :type alignment: PyQt4.QtCore.Qt.Alignment
817
    :param keyboardTracking: If `True`, the valueChanged signal is emitted
818
        when the user is typing (default: True)
819
    :type keyboardTracking: bool
820
    :param spinType: determines whether to use QSpinBox (int) or
821
        QDoubleSpinBox (float)
822
    :type spinType: type
823
    :param decimals: number of decimals (if `spinType` is `float`)
824
    :type decimals: int
825
    :return: Tuple `(spin box, check box) if `checked` is `True`, otherwise
826
        the spin box
827
    :rtype: tuple or gui.SpinBoxWFocusOut
828
    """
829
830
    # b is the outermost box or the widget if there are no boxes;
831
    #    b is the widget that is inserted into the layout
832
    # bi is the box that contains the control or the checkbox and the control;
833
    #    bi can be the widget itself, if there are no boxes
834
    # cbox is the checkbox (or None)
835
    # sbox is the spinbox itself
836
    if box or label and not checked:
837
        b = widgetBox(widget, box, orientation, addToLayout=False)
838
        hasHBox = orientation == 'horizontal' or not orientation
839
    else:
840
        b = widget
841
        hasHBox = False
842
    if not hasHBox and (checked or callback and callbackOnReturn or posttext):
843
        bi = widgetBox(b, orientation=0, addToLayout=False)
844
    else:
845
        bi = b
846
847
    cbox = None
848
    if checked is not None:
849
        cbox = checkBox(bi, master, checked, label, labelWidth=labelWidth,
850
                        callback=checkCallback)
851
    elif label:
852
        b.label = widgetLabel(b, label, labelWidth)
853
    if posttext:
854
        widgetLabel(bi, posttext)
855
856
    isDouble = spinType == float
857
    sbox = bi.control = \
858
        (SpinBoxWFocusOut, DoubleSpinBoxWFocusOut)[isDouble](minv, maxv,
859
                                                             step, bi)
860
    if bi is not widget:
861
        bi.setDisabled(disabled)
862
    else:
863
        sbox.setDisabled(disabled)
864
865
    if decimals is not None:
866
        sbox.setDecimals(decimals)
867
    sbox.setAlignment(alignment)
868
    sbox.setKeyboardTracking(keyboardTracking)
869
    if controlWidth:
870
        sbox.setFixedWidth(controlWidth)
871
    if value:
872
        sbox.setValue(getdeepattr(master, value))
873
874
    cfront, sbox.cback, sbox.cfunc = connectControl(
0 ignored issues
show
The variable cfront seems to be unused.
Loading history...
875
        master, value, callback,
876
        not (callback and callbackOnReturn) and
877
        sbox.valueChanged[(int, float)[isDouble]],
878
        (CallFrontSpin, CallFrontDoubleSpin)[isDouble](sbox))
879
    if checked:
880
        cbox.disables = [sbox]
881
        cbox.makeConsistent()
882
    if callback and callbackOnReturn:
883
        sbox.enterButton, sbox.placeHolder = _enterButton(bi, sbox)
884
        sbox.valueChanged[str].connect(sbox.onChange)
885
        sbox.editingFinished.connect(sbox.onEnter)
886
        sbox.enterButton.clicked.connect(sbox.onEnter)
887
        if hasattr(sbox, "upButton"):
888
            sbox.upButton().clicked.connect(
889
                lambda c=sbox.editor(): c.setFocus())
890
            sbox.downButton().clicked.connect(
891
                lambda c=sbox.editor(): c.setFocus())
892
893
    miscellanea(sbox, b if b is not widget else bi, widget, **misc)
894
    if checked:
895
        if isDouble and b == widget:
896
            # TODO Backward compatilibity; try to find and eliminate
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
897
            sbox.control = b.control
898
            return sbox
899
        return cbox, sbox
900
    else:
901
        return sbox
902
903
904
905
# noinspection PyTypeChecker
906
def doubleSpin(widget, master, value, minv, maxv, step=1, box=None, label=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
907
               labelWidth=None, orientation=None, callback=None,
908
               controlWidth=None, callbackOnReturn=False, checked=None,
909
               checkCallback=None, posttext=None,
910
               alignment=Qt.AlignLeft, keyboardTracking=True,
911
               decimals=None, **misc):
912
    """
913
    Backward compatilibity function: calls :obj:`spin` with `spinType=float`.
914
    """
915
    return spin(widget, master, value, minv, maxv, step, box=box, label=label,
916
                labelWidth=labelWidth, orientation=orientation,
917
                callback=callback, controlWidth=controlWidth,
918
                callbackOnReturn=callbackOnReturn, checked=checked,
919
                checkCallback=checkCallback, posttext=posttext,
920
                alignment=alignment, keyboardTracking=keyboardTracking,
921
                decimals=decimals, spinType=float, **misc)
922
923
924
def checkBox(widget, master, value, label, box=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
925
             callback=None, getwidget=False, id_=None, labelWidth=None,
926
             disables=None, **misc):
927
    """
928
    A simple checkbox.
929
930
    :param widget: the widget into which the box is inserted
931
    :type widget: PyQt4.QtGui.QWidget or None
932
    :param master: master widget
933
    :type master: OWWidget or OWComponent
934
    :param value: the master's attribute with which the value is synchronized
935
    :type value:  str
936
    :param label: label
937
    :type label: str
938
    :param box: tells whether the widget has a border, and its label
939
    :type box: int or str or None
940
    :param callback: a function that is called when the check box state is
941
        changed
942
    :type callback: function
943
    :param getwidget: If set `True`, the callback function will get a keyword
944
        argument `widget` referencing the check box
945
    :type getwidget: bool
946
    :param id_: If present, the callback function will get a keyword argument
947
        `id` with this value
948
    :type id_: any
949
    :param labelWidth: the width of the label
950
    :type labelWidth: int
951
    :param disables: a list of widgets that are disabled if the check box is
952
        unchecked
953
    :type disables: list or PyQt4.QtGui.QWidget or None
954
    :return: constructed check box; if is is placed within a box, the box is
955
        return in the attribute `box`
956
    :rtype: PyQt4.QtGui.QCheckBox
957
    """
958
    if box:
959
        b = widgetBox(widget, box, orientation=None, addToLayout=False)
960
    else:
961
        b = widget
962
    cbox = QtGui.QCheckBox(label, b)
963
964
    if labelWidth:
965
        cbox.setFixedSize(labelWidth, cbox.sizeHint().height())
966
    cbox.setChecked(getdeepattr(master, value))
967
968
    connectControl(master, value, None, cbox.toggled[bool],
969
                   CallFrontCheckBox(cbox),
970
                   cfunc=callback and FunctionCallback(
971
                       master, callback, widget=cbox, getwidget=getwidget,
972
                       id=id_))
973
    if isinstance(disables, QtGui.QWidget):
974
        disables = [disables]
975
    cbox.disables = disables or []
976
    cbox.makeConsistent = Disabler(cbox, master, value)
977
    cbox.toggled[bool].connect(cbox.makeConsistent)
978
    cbox.makeConsistent(value)
979
    miscellanea(cbox, b, widget, **misc)
980
    return cbox
981
982
983
class LineEditWFocusOut(QtGui.QLineEdit):
984
    """
985
    A class derived from QtGui.QLineEdit, which postpones the synchronization
986
    of the control's value with the master's attribute until the user leaves
987
    the line edit, presses Enter or clicks an icon that appears beside the
988
    line edit when the value is changed.
989
990
    The class also allows specifying a callback function for focus-in event.
991
992
    .. attribute:: enterButton
993
994
        A widget (usually an icon) that is shown when the value is changed.
995
996
    .. attribute:: placeHolder
997
998
        A placeholder which is shown when the button is hidden
999
1000
    .. attribute:: inSetValue
1001
1002
        A flag that is set when the value is being changed through
1003
        :obj:`setValue` to prevent the programmatic changes from showing the
1004
        commit button.
1005
1006
    .. attribute:: callback
1007
1008
        Callback that is called when the change is confirmed
1009
1010
    .. attribute:: focusInCallback
1011
1012
        Callback that is called on the focus-in event
1013
    """
1014
1015
    def __init__(self, parent, callback, focusInCallback=None,
1016
                 placeholder=False):
1017
        super().__init__(parent)
1018
        if parent.layout() is not None:
1019
            parent.layout().addWidget(self)
1020
        self.callback = callback
1021
        self.focusInCallback = focusInCallback
1022
        self.enterButton, self.placeHolder = \
1023
            _enterButton(parent, self, placeholder)
1024
        self.enterButton.clicked.connect(self.returnPressedHandler)
1025
        self.textChanged[str].connect(self.markChanged)
1026
        self.returnPressed.connect(self.returnPressedHandler)
1027
1028
    def markChanged(self, *_):
1029
        if self.placeHolder:
1030
            self.placeHolder.hide()
1031
        self.enterButton.show()
1032
1033
    def markUnchanged(self, *_):
1034
        self.enterButton.hide()
1035
        if self.placeHolder:
1036
            self.placeHolder.show()
1037
1038
    def returnPressedHandler(self):
1039
        if self.enterButton.isVisible():
1040
            self.markUnchanged()
1041
            if hasattr(self, "cback") and self.cback:
1042
                self.cback(self.text())
1043
            if self.callback:
1044
                self.callback()
1045
1046
    def setText(self, t):
1047
        super().setText(t)
1048
        if self.enterButton:
1049
            self.markUnchanged()
1050
1051
    def focusOutEvent(self, *e):
1052
        super().focusOutEvent(*e)
1053
        self.returnPressedHandler()
1054
1055
    def focusInEvent(self, *e):
1056
        if self.focusInCallback:
1057
            self.focusInCallback()
1058
        return super().focusInEvent(*e)
1059
1060
1061
def lineEdit(widget, master, value, label=None, labelWidth=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1062
             orientation='vertical', box=None, callback=None,
1063
             valueType=str, validator=None, controlWidth=None,
1064
             callbackOnType=False, focusInCallback=None,
1065
             enterPlaceholder=False, **misc):
1066
    """
1067
    Insert a line edit.
1068
1069
    :param widget: the widget into which the box is inserted
1070
    :type widget: PyQt4.QtGui.QWidget or None
1071
    :param master: master widget
1072
    :type master: OWWidget or OWComponent
1073
    :param value: the master's attribute with which the value is synchronized
1074
    :type value:  str
1075
    :param label: label
1076
    :type label: str
1077
    :param labelWidth: the width of the label
1078
    :type labelWidth: int
1079
    :param orientation: tells whether to put the label above (`"vertical"` or
1080
        `True`) or to the left (`"horizontal"` or `False`)
1081
    :type orientation: int or bool or str
1082
    :param box: tells whether the widget has a border, and its label
1083
    :type box: int or str or None
1084
    :param callback: a function that is called when the check box state is
1085
        changed
1086
    :type callback: function
1087
    :param valueType: the type into which the entered string is converted
1088
        when synchronizing to `value`
1089
    :type valueType: type
1090
    :param validator: the validator for the input
1091
    :type validator: PyQt4.QtGui.QValidator
1092
    :param controlWidth: the width of the line edit
1093
    :type controlWidth: int
1094
    :param callbackOnType: if set to `True`, the callback is called at each
1095
        key press (default: `False`)
1096
    :type callbackOnType: bool
1097
    :param focusInCallback: a function that is called when the line edit
1098
        receives focus
1099
    :type focusInCallback: function
1100
    :param enterPlaceholder: if set to `True`, space of appropriate width is
1101
        left empty to the right for the icon that shows that the value is
1102
        changed but has not been committed yet
1103
    :type enterPlaceholder: bool
1104
    :rtype: PyQt4.QtGui.QLineEdit or a box
1105
    """
1106
    if box or label:
1107
        b = widgetBox(widget, box, orientation, addToLayout=False)
1108
        if label is not None:
1109
            widgetLabel(b, label, labelWidth)
1110
        hasHBox = orientation == 'horizontal' or not orientation
1111
    else:
1112
        b = widget
1113
        hasHBox = False
1114
1115
    baseClass = misc.pop("baseClass", None)
1116
    if baseClass:
1117
        ledit = baseClass(b)
1118
        ledit.enterButton = None
1119
        if b is not widget:
1120
            b.layout().addWidget(ledit)
1121
    elif focusInCallback or callback and not callbackOnType:
1122
        if not hasHBox:
1123
            outer = widgetBox(b, "", 0, addToLayout=(b is not widget))
1124
        else:
1125
            outer = b
1126
        ledit = LineEditWFocusOut(outer, callback, focusInCallback,
1127
                                  enterPlaceholder)
1128
    else:
1129
        ledit = QtGui.QLineEdit(b)
1130
        ledit.enterButton = None
1131
        if b is not widget:
1132
            b.layout().addWidget(ledit)
1133
1134
    if value:
1135
        ledit.setText(str(getdeepattr(master, value)))
1136
    if controlWidth:
1137
        ledit.setFixedWidth(controlWidth)
1138
    if validator:
1139
        ledit.setValidator(validator)
1140
    if value:
1141
        ledit.cback = connectControl(
0 ignored issues
show
The attribute cback was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1142
            master, value,
1143
            callbackOnType and callback, ledit.textChanged[str],
1144
            CallFrontLineEdit(ledit), fvcb=value and valueType)[1]
1145
1146
    miscellanea(ledit, b, widget, **misc)
1147
    return ledit
1148
1149
1150
def button(widget, master, label, callback=None, width=None, height=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1151
           toggleButton=False, value="", default=False, autoDefault=True,
1152
           buttonType=QtGui.QPushButton, **misc):
1153
    """
1154
    Insert a button (QPushButton, by default)
1155
1156
    :param widget: the widget into which the button is inserted
1157
    :type widget: PyQt4.QtGui.QWidget or None
1158
    :param master: master widget
1159
    :type master: OWWidget or OWComponent
1160
    :param label: label
1161
    :type label: str
1162
    :param callback: a function that is called when the button is pressed
1163
    :type callback: function
1164
    :param width: the width of the button
1165
    :type width: int
1166
    :param height: the height of the button
1167
    :type height: int
1168
    :param toggleButton: if set to `True`, the button is checkable, but it is
1169
        not synchronized with any attribute unless the `value` is given
1170
    :type toggleButton: bool
1171
    :param value: the master's attribute with which the value is synchronized
1172
        (the argument is optional; if present, it makes the button "checkable",
1173
        even if `toggleButton` is not set)
1174
    :type value: str
1175
    :param default: if `True` it makes the button the default button; this is
1176
        the button that is activated when the user presses Enter unless some
1177
        auto default button has current focus
1178
    :type default: bool
1179
    :param autoDefault: all buttons are auto default: they are activated if
1180
        they have focus (or are the next in the focus chain) when the user
1181
        presses enter. By setting `autoDefault` to `False`, the button is not
1182
        activated on pressing Return.
1183
    :type autoDefault: bool
1184
    :param buttonType: the button type (default: `QPushButton`)
1185
    :type buttonType: PyQt4.QtGui.QAbstractButton
1186
    :rtype: PyQt4.QtGui.QAbstractButton
1187
    """
1188
    button = buttonType(widget)
0 ignored issues
show
Comprehensibility Bug introduced by
button is re-defining a name which is already available in the outer-scope (previously defined on line 1150).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1189
    if label:
1190
        button.setText(label)
1191
    if width:
1192
        button.setFixedWidth(width)
1193
    if height:
1194
        button.setFixedHeight(height)
1195
    if toggleButton or value:
1196
        button.setCheckable(True)
1197
    if buttonType == QtGui.QPushButton:
1198
        button.setDefault(default)
1199
        button.setAutoDefault(autoDefault)
1200
1201
    if value:
1202
        button.setChecked(getdeepattr(master, value))
1203
        connectControl(
1204
            master, value, None, button.toggled[bool],
1205
            CallFrontButton(button),
1206
            cfunc=callback and FunctionCallback(master, callback,
1207
                                                widget=button))
1208
    elif callback:
1209
        button.clicked.connect(callback)
1210
1211
    miscellanea(button, None, widget, **misc)
1212
    return button
1213
1214
1215
def toolButton(widget, master, label="", callback=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1216
               width=None, height=None, tooltip=None):
1217
    """
1218
    Insert a tool button. Calls :obj:`button`
1219
1220
    :param widget: the widget into which the button is inserted
1221
    :type widget: PyQt4.QtGui.QWidget or None
1222
    :param master: master widget
1223
    :type master: OWWidget or OWComponent
1224
    :param label: label
1225
    :type label: str
1226
    :param callback: a function that is called when the button is pressed
1227
    :type callback: function
1228
    :param width: the width of the button
1229
    :type width: int
1230
    :param height: the height of the button
1231
    :type height: int
1232
    :rtype: PyQt4.QtGui.QToolButton
1233
    """
1234
    return button(widget, master, label, callback, width, height,
1235
                  buttonType=QtGui.QToolButton, tooltip=tooltip)
1236
1237
1238
def createAttributePixmap(char, background=Qt.black, color=Qt.white):
1239
    """
1240
    Create a QIcon with a given character. The icon is 13 pixels high and wide.
1241
1242
    :param char: The character that is printed in the icon
1243
    :type char: str
1244
    :param background: the background color (default: black)
1245
    :type background: PyQt4.QtGui.QColor
1246
    :param color: the character color (default: white)
1247
    :type color: PyQt4.QtGui.QColor
1248
    :rtype: PyQt4.QtGui.QIcon
1249
    """
1250
    pixmap = QtGui.QPixmap(13, 13)
1251
    pixmap.fill(QtGui.QColor(0, 0, 0, 0))
1252
    painter = QtGui.QPainter()
1253
    painter.begin(pixmap)
1254
    painter.setRenderHints(painter.Antialiasing | painter.TextAntialiasing |
1255
                           painter.SmoothPixmapTransform)
1256
    painter.setPen(background)
1257
    painter.setBrush(background)
1258
    rect = QtCore.QRectF(0, 0, 13, 13)
1259
    painter.drawRoundedRect(rect, 4, 4)
1260
    painter.setPen(color)
1261
    painter.drawText(2, 11, char)
1262
    painter.end()
1263
    return QtGui.QIcon(pixmap)
1264
1265
1266
class __AttributeIconDict(dict):
1267
    def __getitem__(self, key):
1268
        if not self:
1269
            for tpe, char, col in ((vartype(ContinuousVariable()),
1270
                                    "C", (202, 0, 32)),
1271
                                   (vartype(DiscreteVariable()),
1272
                                    "D", (26, 150, 65)),
1273
                                   (vartype(StringVariable()),
1274
                                    "S", (0, 0, 0)),
1275
                                   (-1, "?", (128, 128, 128))):
1276
                self[tpe] = createAttributePixmap(char, QtGui.QColor(*col))
1277
        if key not in self:
1278
            key = vartype(key) if isinstance(key, Variable) else -1
1279
        return super().__getitem__(key)
1280
1281
#: A dict that returns icons for different attribute types. The dict is
1282
#: constructed on first use since icons cannot be created before initializing
1283
#: the application.
1284
#:
1285
#: Accepted keys are variable type codes and instances
1286
#: of :obj:`Orange.data.variable`: `attributeIconDict[var]` will give the
1287
#: appropriate icon for variable `var` or a question mark if the type is not
1288
#: recognized
1289
attributeIconDict = __AttributeIconDict()
1290
1291
1292
def attributeItem(var):
1293
    """
1294
    Construct a pair (icon, name) for inserting a variable into a combo or
1295
    list box
1296
1297
    :param var: variable
1298
    :type var: Orange.data.Variable
1299
    :rtype: tuple with PyQt4.QtGui.QIcon and str
1300
    """
1301
    return attributeIconDict[var], var.name
1302
1303
1304
def listBox(widget, master, value=None, labels=None, box=None, callback=None,
1305
            selectionMode=QtGui.QListWidget.SingleSelection,
1306
            enableDragDrop=False, dragDropCallback=None,
1307
            dataValidityCallback=None, sizeHint=None, **misc):
1308
    """
1309
    Insert a list box.
1310
1311
    The value with which the box's value synchronizes (`master.<value>`)
1312
    is a list of indices of selected items.
1313
1314
    :param widget: the widget into which the box is inserted
1315
    :type widget: PyQt4.QtGui.QWidget or None
1316
    :param master: master widget
1317
    :type master: OWWidget or OWComponent
1318
    :param value: the name of the master's attribute with which the value is
1319
        synchronized (list of ints - indices of selected items)
1320
    :type value: str
1321
    :param labels: the name of the master's attribute with the list of items
1322
        (as strings or tuples with icon and string)
1323
    :type labels: str
1324
    :param box: tells whether the widget has a border, and its label
1325
    :type box: int or str or None
1326
    :param callback: a function that is called when the selection state is
1327
        changed
1328
    :type callback: function
1329
    :param selectionMode: selection mode - single, multiple etc
1330
    :type selectionMode: PyQt4.QtGui.QAbstractItemView.SelectionMode
1331
    :param enableDragDrop: flag telling whether drag and drop is available
1332
    :type enableDragDrop: bool
1333
    :param dragDropCallback: callback function on drop event
1334
    :type dragDropCallback: function
1335
    :param dataValidityCallback: function that check the validity on enter
1336
        and move event; it should return either `ev.accept()` or `ev.ignore()`.
1337
    :type dataValidityCallback: function
1338
    :param sizeHint: size hint
1339
    :type sizeHint: PyQt4.QtGui.QSize
1340
    :rtype: OrangeListBox
1341
    """
1342
    if box:
1343
        bg = widgetBox(widget, box,
1344
                       orientation="horizontal", addToLayout=False)
1345
    else:
1346
        bg = widget
1347
    lb = OrangeListBox(master, enableDragDrop, dragDropCallback,
1348
                       dataValidityCallback, sizeHint, bg)
1349
    lb.setSelectionMode(selectionMode)
1350
    lb.ogValue = value
0 ignored issues
show
The attribute ogValue was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1351
    lb.ogLabels = labels
0 ignored issues
show
The attribute ogLabels was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1352
    lb.ogMaster = master
0 ignored issues
show
The attribute ogMaster was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1353
1354
    if value is not None:
1355
        clist = getdeepattr(master, value)
1356
        if not isinstance(clist, ControlledList):
1357
            clist = ControlledList(clist, lb)
1358
            master.__setattr__(value, clist)
1359
    if labels is not None:
1360
        setattr(master, labels, getdeepattr(master, labels))
1361
        if hasattr(master, CONTROLLED_ATTRIBUTES):
1362
            getattr(master, CONTROLLED_ATTRIBUTES)[labels] = CallFrontListBoxLabels(lb)
1363
    if value is not None:
1364
        setattr(master, value, getdeepattr(master, value))
1365
    connectControl(master, value, callback, lb.itemSelectionChanged,
1366
                   CallFrontListBox(lb), CallBackListBox(lb, master))
1367
1368
    misc.setdefault('addSpace', True)
1369
    miscellanea(lb, bg, widget, **misc)
1370
    return lb
1371
1372
1373
# btnLabels is a list of either char strings or pixmaps
1374
def radioButtons(widget, master, value, btnLabels=(), tooltips=None,
1375
                      box=None, label=None, orientation='vertical',
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1376
                      callback=None, **misc):
1377
    """
1378
    Construct a button group and add radio buttons, if they are given.
1379
    The value with which the buttons synchronize is the index of selected
1380
    button.
1381
1382
    :param widget: the widget into which the box is inserted
1383
    :type widget: PyQt4.QtGui.QWidget or None
1384
    :param master: master widget
1385
    :type master: OWWidget or OWComponent
1386
    :param value: the master's attribute with which the value is synchronized
1387
    :type value:  str
1388
    :param btnLabels: a list of labels or icons for radio buttons
1389
    :type btnLabels: list of str or pixmaps
1390
    :param tooltips: a list of tool tips of the same length as btnLabels
1391
    :type tooltips: list of str
1392
    :param box: tells whether the widget has a border, and its label
1393
    :type box: int or str or None
1394
    :param label: a label that is inserted into the box
1395
    :type label: str
1396
    :param callback: a function that is called when the selection is changed
1397
    :type callback: function
1398
    :param orientation: orientation of the layout in the box
1399
    :type orientation: int or str or QLayout
1400
    :rtype: PyQt4.QtQui.QButtonGroup
1401
    """
1402
    bg = widgetBox(widget, box, orientation, addToLayout=False)
1403
    if not label is None:
1404
        widgetLabel(bg, label)
1405
1406
    rb = QtGui.QButtonGroup(bg)
1407
    if bg is not widget:
1408
        bg.group = rb
1409
    bg.buttons = []
1410
    bg.ogValue = value
1411
    bg.ogMaster = master
1412
    for i, lab in enumerate(btnLabels):
1413
        appendRadioButton(bg, lab, tooltip=tooltips and tooltips[i])
1414
    connectControl(master, value, callback, bg.group.buttonClicked[int],
1415
                   CallFrontRadioButtons(bg), CallBackRadioButton(bg, master))
1416
    misc.setdefault('addSpace', bool(box))
1417
    miscellanea(bg.group, bg, widget, **misc)
1418
    return bg
1419
1420
1421
radioButtonsInBox = radioButtons
1422
1423
def appendRadioButton(group, label, insertInto=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1424
                      disabled=False, tooltip=None, sizePolicy=None,
1425
                      addToLayout=True, stretch=0, addSpace=False):
1426
    """
1427
    Construct a radio button and add it to the group. The group must be
1428
    constructed with :obj:`radioButtonsInBox` since it adds additional
1429
    attributes need for the call backs.
1430
1431
    The radio button is inserted into `insertInto` or, if omitted, into the
1432
    button group. This is useful for more complex groups, like those that have
1433
    radio buttons in several groups, divided by labels and inside indented
1434
    boxes.
1435
1436
    :param group: the button group
1437
    :type group: PyQt4.QtCore.QButtonGroup
1438
    :param label: string label or a pixmap for the button
1439
    :type label: str or PyQt4.QtGui.QPixmap
1440
    :param insertInto: the widget into which the radio button is inserted
1441
    :type insertInto: PyQt4.QtGui.QWidget
1442
    :rtype: PyQt4.QtGui.QRadioButton
1443
    """
1444
    i = len(group.buttons)
1445
    if isinstance(label, str):
1446
        w = QtGui.QRadioButton(label)
1447
    else:
1448
        w = QtGui.QRadioButton(str(i))
1449
        w.setIcon(QtGui.QIcon(label))
1450
    if not hasattr(group, "buttons"):
1451
        group.buttons = []
1452
    group.buttons.append(w)
1453
    group.group.addButton(w)
1454
    w.setChecked(getdeepattr(group.ogMaster, group.ogValue) == i)
1455
1456
    # miscellanea for this case is weird, so we do it here
1457
    if disabled:
1458
        w.setDisabled(disabled)
1459
    if tooltip is not None:
1460
        w.setToolTip(tooltip)
1461
    if sizePolicy:
1462
        w.setSizePolicy(sizePolicy)
1463
    if addToLayout:
1464
        dest = insertInto or group
1465
        dest.layout().addWidget(w, stretch)
1466
        _addSpace(dest, addSpace)
1467
    return w
1468
1469
1470
def hSlider(widget, master, value, box=None, minValue=0, maxValue=10, step=1,
1471
            callback=None, label=None, labelFormat=" %d", ticks=False,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1472
            divideFactor=1.0, vertical=False, createLabel=True, width=None,
1473
            intOnly=True, **misc):
1474
    """
1475
    Construct a slider.
1476
1477
    :param widget: the widget into which the box is inserted
1478
    :type widget: PyQt4.QtGui.QWidget or None
1479
    :param master: master widget
1480
    :type master: OWWidget or OWComponent
1481
    :param value: the master's attribute with which the value is synchronized
1482
    :type value:  str
1483
    :param box: tells whether the widget has a border, and its label
1484
    :type box: int or str or None
1485
    :param label: a label that is inserted into the box
1486
    :type label: str
1487
    :param callback: a function that is called when the value is changed
1488
    :type callback: function
1489
1490
    :param minValue: minimal value
1491
    :type minValue: int or float
1492
    :param maxValue: maximal value
1493
    :type maxValue: int or float
1494
    :param step: step size
1495
    :type step: int or float
1496
    :param labelFormat: the label format; default is `" %d"`
1497
    :type labelFormat: str
1498
    :param ticks: if set to `True`, ticks are added below the slider
1499
    :type ticks: bool
1500
    :param divideFactor: a factor with which the displayed value is divided
1501
    :type divideFactor: float
1502
    :param vertical: if set to `True`, the slider is vertical
1503
    :type vertical: bool
1504
    :param createLabel: unless set to `False`, labels for minimal, maximal
1505
        and the current value are added to the widget
1506
    :type createLabel: bool
1507
    :param width: the width of the slider
1508
    :type width: int
1509
    :param intOnly: if `True`, the slider value is integer (the slider is
1510
        of type :obj:`PyQt4.QtGui.QSlider`) otherwise it is float
1511
        (:obj:`FloatSlider`, derived in turn from :obj:`PyQt4.QtQui.QSlider`).
1512
    :type intOnly: bool
1513
    :rtype: :obj:`PyQt4.QtGui.QSlider` or :obj:`FloatSlider`
1514
    """
1515
    sliderBox = widgetBox(widget, box, orientation="horizontal",
1516
                          addToLayout=False)
1517
    if label:
1518
        widgetLabel(sliderBox, label)
1519
    sliderOrient = Qt.Vertical if vertical else Qt.Horizontal
1520
    if intOnly:
1521
        slider = QtGui.QSlider(sliderOrient, sliderBox)
1522
        slider.setRange(minValue, maxValue)
1523
        if step:
1524
            slider.setSingleStep(step)
1525
            slider.setPageStep(step)
1526
            slider.setTickInterval(step)
1527
        signal = slider.valueChanged[int]
1528
    else:
1529
        slider = FloatSlider(sliderOrient, minValue, maxValue, step)
1530
        signal = slider.valueChangedFloat[float]
1531
    sliderBox.layout().addWidget(slider)
1532
    slider.setValue(getdeepattr(master, value))
1533
    if width:
1534
        slider.setFixedWidth(width)
1535
    if ticks:
1536
        slider.setTickPosition(QtGui.QSlider.TicksBelow)
1537
        slider.setTickInterval(ticks)
1538
1539
    if createLabel:
1540
        label = QtGui.QLabel(sliderBox)
1541
        sliderBox.layout().addWidget(label)
1542
        label.setText(labelFormat % minValue)
1543
        width1 = label.sizeHint().width()
1544
        label.setText(labelFormat % maxValue)
1545
        width2 = label.sizeHint().width()
1546
        label.setFixedSize(max(width1, width2), label.sizeHint().height())
1547
        txt = labelFormat % (getdeepattr(master, value) / divideFactor)
1548
        label.setText(txt)
1549
        label.setLbl = lambda x: \
1550
            label.setText(labelFormat % (x / divideFactor))
1551
        signal.connect(label.setLbl)
1552
1553
    connectControl(master, value, callback, signal, CallFrontHSlider(slider))
1554
1555
    miscellanea(slider, sliderBox, widget, **misc)
1556
    return slider
1557
1558
1559
def labeledSlider(widget, master, value, box=None,
1560
                 label=None, labels=(), labelFormat=" %d", ticks=False,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1561
                 callback=None, vertical=False, width=None, **misc):
1562
    """
1563
    Construct a slider with labels instead of numbers.
1564
1565
    :param widget: the widget into which the box is inserted
1566
    :type widget: PyQt4.QtGui.QWidget or None
1567
    :param master: master widget
1568
    :type master: OWWidget or OWComponent
1569
    :param value: the master's attribute with which the value is synchronized
1570
    :type value:  str
1571
    :param box: tells whether the widget has a border, and its label
1572
    :type box: int or str or None
1573
    :param label: a label that is inserted into the box
1574
    :type label: str
1575
    :param labels: labels shown at different slider positions
1576
    :type labels: tuple of str
1577
    :param callback: a function that is called when the value is changed
1578
    :type callback: function
1579
1580
    :param ticks: if set to `True`, ticks are added below the slider
1581
    :type ticks: bool
1582
    :param vertical: if set to `True`, the slider is vertical
1583
    :type vertical: bool
1584
    :param width: the width of the slider
1585
    :type width: int
1586
    :rtype: :obj:`PyQt4.QtGui.QSlider`
1587
    """
1588
    sliderBox = widgetBox(widget, box, orientation="horizontal",
1589
                          addToLayout=False)
1590
    if label:
1591
        widgetLabel(sliderBox, label)
1592
    sliderOrient = Qt.Vertical if vertical else Qt.Horizontal
1593
    slider = QtGui.QSlider(sliderOrient, sliderBox)
1594
    slider.ogValue = value
1595
    slider.setRange(0, len(labels) - 1)
1596
    slider.setSingleStep(1)
1597
    slider.setPageStep(1)
1598
    slider.setTickInterval(1)
1599
    sliderBox.layout().addWidget(slider)
1600
    slider.setValue(labels.index(getdeepattr(master, value)))
1601
    if width:
1602
        slider.setFixedWidth(width)
1603
    if ticks:
1604
        slider.setTickPosition(QtGui.QSlider.TicksBelow)
1605
        slider.setTickInterval(ticks)
1606
1607
    max_label_size = 0
1608
    slider.value_label = value_label = QtGui.QLabel(sliderBox)
1609
    value_label.setAlignment(Qt.AlignRight)
1610
    sliderBox.layout().addWidget(value_label)
1611
    for lb in labels:
1612
        value_label.setText(labelFormat % lb)
1613
        max_label_size = max(max_label_size, value_label.sizeHint().width())
1614
    value_label.setFixedSize(max_label_size, value_label.sizeHint().height())
1615
    value_label.setText(getdeepattr(master, value))
1616
    if isinstance(labelFormat, str):
1617
        value_label.set_label = lambda x: \
1618
            value_label.setText(labelFormat % x)
1619
    else:
1620
        value_label.set_label = lambda x: value_label.setText(labelFormat(x))
1621
    slider.valueChanged[int].connect(value_label.set_label)
1622
1623
    connectControl(master, value, callback, slider.valueChanged[int],
1624
                   CallFrontLabeledSlider(slider, labels),
1625
                   CallBackLabeledSlider(slider, master, labels))
1626
1627
    miscellanea(slider, sliderBox, widget, **misc)
1628
    return slider
1629
1630
1631
def valueSlider(widget, master, value, box=None, label=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1632
                values=(), labelFormat=" %d", ticks=False,
1633
                callback=None, vertical=False, width=None, **misc):
1634
    """
1635
    Construct a slider with different values.
1636
1637
    :param widget: the widget into which the box is inserted
1638
    :type widget: PyQt4.QtGui.QWidget or None
1639
    :param master: master widget
1640
    :type master: OWWidget or OWComponent
1641
    :param value: the master's attribute with which the value is synchronized
1642
    :type value:  str
1643
    :param box: tells whether the widget has a border, and its label
1644
    :type box: int or str or None
1645
    :param label: a label that is inserted into the box
1646
    :type label: str
1647
    :param values: values at different slider positions
1648
    :type values: list of int
1649
    :param labelFormat: label format; default is `" %d"`; can also be a function
1650
    :type labelFormat: str or func
1651
    :param callback: a function that is called when the value is changed
1652
    :type callback: function
1653
1654
    :param ticks: if set to `True`, ticks are added below the slider
1655
    :type ticks: bool
1656
    :param vertical: if set to `True`, the slider is vertical
1657
    :type vertical: bool
1658
    :param width: the width of the slider
1659
    :type width: int
1660
    :rtype: :obj:`PyQt4.QtGui.QSlider`
1661
    """
1662
    if isinstance(labelFormat, str):
1663
        labelFormat = lambda x, f=labelFormat: f(x)
1664
1665
    sliderBox = widgetBox(widget, box, orientation="horizontal",
1666
                          addToLayout=False)
1667
    if label:
1668
        widgetLabel(sliderBox, label)
1669
    slider_orient = Qt.Vertical if vertical else Qt.Horizontal
1670
    slider = QtGui.QSlider(slider_orient, sliderBox)
1671
    slider.ogValue = value
1672
    slider.setRange(0, len(values) - 1)
1673
    slider.setSingleStep(1)
1674
    slider.setPageStep(1)
1675
    slider.setTickInterval(1)
1676
    sliderBox.layout().addWidget(slider)
1677
    slider.setValue(values.index(getdeepattr(master, value)))
1678
    if width:
1679
        slider.setFixedWidth(width)
1680
    if ticks:
1681
        slider.setTickPosition(QtGui.QSlider.TicksBelow)
1682
        slider.setTickInterval(ticks)
1683
1684
    max_label_size = 0
1685
    slider.value_label = value_label = QtGui.QLabel(sliderBox)
1686
    value_label.setAlignment(Qt.AlignRight)
1687
    sliderBox.layout().addWidget(value_label)
1688
    for lb in values:
1689
        value_label.setText(labelFormat(lb))
1690
        max_label_size = max(max_label_size, value_label.sizeHint().width())
1691
    value_label.setFixedSize(max_label_size, value_label.sizeHint().height())
1692
    value_label.setText(labelFormat(getdeepattr(master, value)))
1693
    value_label.set_label = lambda x: value_label.setText(labelFormat(values[x]))
1694
    slider.valueChanged[int].connect(value_label.set_label)
1695
1696
    connectControl(master, value, callback, slider.valueChanged[int],
1697
                   CallFrontLabeledSlider(slider, values),
1698
                   CallBackLabeledSlider(slider, master, values))
1699
1700
    miscellanea(slider, sliderBox, widget, **misc)
1701
    return slider
1702
1703
1704
class OrangeComboBox(QtGui.QComboBox):
1705
    """
1706
    A QtGui.QComboBox subclass extened to support bounded contents width hint.
1707
    """
1708
    def __init__(self, parent=None, maximumContentsLength=-1, **kwargs):
1709
        super().__init__(parent, **kwargs)
1710
        self.__maximumContentsLength = maximumContentsLength
1711
1712
    def setMaximumContentsLength(self, length):
1713
        """
1714
        Set the maximum contents length hint.
1715
1716
        The hint specifies the upper bound on the `sizeHint` and
1717
        `minimumSizeHint` width specified in character length.
1718
        Set to 0 or negative value to disable.
1719
1720
        .. note::
1721
             This property does not affect the widget's `maximumSize`.
1722
             The widget can still grow depending in it's sizePolicy.
1723
1724
        Parameters
1725
        ----------
1726
        lenght : int
1727
            Maximum contents length hint.
1728
        """
1729
        if self.__maximumContentsLength != length:
1730
            self.__maximumContentsLength = length
1731
            self.updateGeometry()
1732
1733
    def maximumContentsLength(self):
1734
        """
1735
        Return the maximum contents length hint.
1736
        """
1737
        return self.__maximumContentsLength
1738
1739
    def sizeHint(self):
1740
        # reimplemented
1741
        sh = super().sizeHint()
1742
        if self.__maximumContentsLength > 0:
1743
            width = (self.fontMetrics().width("X") * self.__maximumContentsLength
1744
                     + self.iconSize().width() + 4)
1745
            sh = sh.boundedTo(QtCore.QSize(width, sh.height()))
1746
        return sh
1747
1748
    def minimumSizeHint(self):
1749
        # reimplemented
1750
        sh = super().minimumSizeHint()
1751
        if self.__maximumContentsLength > 0:
1752
            width = (self.fontMetrics().width("X") * self.__maximumContentsLength
1753
                     + self.iconSize().width() + 4)
1754
            sh = sh.boundedTo(QtCore.QSize(width, sh.height()))
1755
        return sh
1756
1757
1758
# TODO comboBox looks overly complicated:
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
1759
# - is the argument control2attributeDict needed? doesn't emptyString do the
1760
#    job?
1761
# - can valueType be anything else than str?
1762
# - sendSelectedValue is not a great name
1763
def comboBox(widget, master, value, box=None, label=None, labelWidth=None,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
1764
             orientation='vertical', items=(), callback=None,
1765
             sendSelectedValue=False, valueType=str,
1766
             control2attributeDict=None, emptyString=None, editable=False,
1767
             contentsLength=None, maximumContentsLength=25,
1768
             **misc):
1769
    """
1770
    Construct a combo box.
1771
1772
    The `value` attribute of the `master` contains either the index of the
1773
    selected row (if `sendSelected` is left at default, `False`) or a value
1774
    converted to `valueType` (`str` by default).
1775
1776
    Furthermore, the value is converted by looking up into dictionary
1777
    `control2attributeDict`.
1778
1779
    :param widget: the widget into which the box is inserted
1780
    :type widget: PyQt4.QtGui.QWidget or None
1781
    :param master: master widget
1782
    :type master: OWWidget or OWComponent
1783
    :param value: the master's attribute with which the value is synchronized
1784
    :type value:  str
1785
    :param box: tells whether the widget has a border, and its label
1786
    :type box: int or str or None
1787
    :param orientation: orientation of the layout in the box
1788
    :type orientation: str or int or bool
1789
    :param label: a label that is inserted into the box
1790
    :type label: str
1791
    :param labelWidth: the width of the label
1792
    :type labelWidth: int
1793
    :param callback: a function that is called when the value is changed
1794
    :type callback: function
1795
    :param items: items (optionally with data) that are put into the box
1796
    :type items: tuple of str or tuples
1797
    :param sendSelectedValue: flag telling whether to store/retrieve indices
1798
        or string values from `value`
1799
    :type sendSelectedValue: bool
1800
    :param valueType: the type into which the selected value is converted
1801
        if sentSelectedValue is `False`
1802
    :type valueType: type
1803
    :param control2attributeDict: a dictionary through which the value is
1804
        converted
1805
    :type control2attributeDict: dict or None
1806
    :param emptyString: the string value in the combo box that gets stored as
1807
        an empty string in `value`
1808
    :type emptyString: str
1809
    :param editable: a flag telling whether the combo is editable
1810
    :type editable: bool
1811
    :param int contentsLength: Contents character length to use as a
1812
        fixed size hint. When not None, equivalent to::
1813
1814
            combo.setSizeAdjustPolicy(
1815
                QComboBox.AdjustToMinimumContentsLengthWithIcon)
1816
            combo.setMinimumContentsLength(contentsLength)
1817
    :param int maximumContentsLength: Specifies the upper bound on the
1818
        `sizeHint` and `minimumSizeHint` width specified in character
1819
        length (default: 25, use 0 to disable)
1820
    :rtype: PyQt4.QtGui.QComboBox
1821
    """
1822
    if box or label:
1823
        hb = widgetBox(widget, box, orientation, addToLayout=False)
1824
        if label is not None:
1825
            widgetLabel(hb, label, labelWidth)
1826
    else:
1827
        hb = widget
1828
1829
    combo = OrangeComboBox(
1830
        hb, maximumContentsLength=maximumContentsLength,
1831
        editable=editable)
1832
1833
    if contentsLength is not None:
1834
        combo.setSizeAdjustPolicy(
1835
            QtGui.QComboBox.AdjustToMinimumContentsLengthWithIcon)
1836
        combo.setMinimumContentsLength(contentsLength)
1837
1838
    combo.box = hb
0 ignored issues
show
The attribute box was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1839
    for item in items:
1840
        if isinstance(item, (tuple, list)):
1841
            combo.addItem(*item)
1842
        else:
1843
            combo.addItem(str(item))
1844
1845
    if value:
1846
        cindex = getdeepattr(master, value)
1847
        if isinstance(cindex, str):
1848
            if items and cindex in items:
1849
                cindex = items.index(getdeepattr(master, value))
1850
            else:
1851
                cindex = 0
1852
        if cindex > combo.count() - 1:
1853
            cindex = 0
1854
        combo.setCurrentIndex(cindex)
1855
1856
        if sendSelectedValue:
1857
            if control2attributeDict is None:
1858
                control2attributeDict = {}
1859
            if emptyString:
1860
                control2attributeDict[emptyString] = ""
1861
            connectControl(
1862
                master, value, callback, combo.activated[str],
1863
                CallFrontComboBox(combo, valueType, control2attributeDict),
1864
                ValueCallbackCombo(master, value, valueType,
1865
                                   control2attributeDict))
1866
        else:
1867
            connectControl(
1868
                master, value, callback, combo.activated[int],
1869
                CallFrontComboBox(combo, None, control2attributeDict))
1870
    miscellanea(combo, hb, widget, **misc)
1871
    return combo
1872
1873
1874
class OrangeListBox(QtGui.QListWidget):
1875
    """
1876
    List box with drag and drop functionality. Function :obj:`listBox`
1877
    constructs instances of this class; do not use the class directly.
1878
1879
    .. attribute:: master
1880
1881
        The widget into which the listbox is inserted.
1882
1883
    .. attribute:: ogLabels
1884
1885
        The name of the master's attribute that holds the strings with items
1886
        in the list box.
1887
1888
    .. attribute:: ogValue
1889
1890
        The name of the master's attribute that holds the indices of selected
1891
        items.
1892
1893
    .. attribute:: enableDragDrop
1894
1895
        A flag telling whether drag-and-drop is enabled.
1896
1897
    .. attribute:: dragDropCallback
1898
1899
        A callback that is called at the end of drop event.
1900
1901
    .. attribute:: dataValidityCallback
1902
1903
        A callback that is called on dragEnter and dragMove events and returns
1904
        either `ev.accept()` or `ev.ignore()`.
1905
1906
    .. attribute:: defaultSizeHint
1907
1908
        The size returned by the `sizeHint` method.
1909
    """
1910
    def __init__(self, master, enableDragDrop=False, dragDropCallback=None,
1911
                 dataValidityCallback=None, sizeHint=None, *args):
1912
        """
1913
        :param master: the master widget
1914
        :type master: OWWidget or OWComponent
1915
        :param enableDragDrop: flag telling whether drag and drop is enabled
1916
        :type enableDragDrop: bool
1917
        :param dragDropCallback: callback for the end of drop event
1918
        :type dragDropCallback: function
1919
        :param dataValidityCallback: callback that accepts or ignores dragEnter
1920
            and dragMove events
1921
        :type dataValidityCallback: function with one argument (event)
1922
        :param sizeHint: size hint
1923
        :type sizeHint: PyQt4.QtGui.QSize
1924
        :param args: optional arguments for the inherited constructor
1925
        """
1926
        self.master = master
1927
        super().__init__(*args)
1928
        self.drop_callback = dragDropCallback
1929
        self.valid_data_callback = dataValidityCallback
1930
        if not sizeHint:
1931
            self.size_hint = QtCore.QSize(150, 100)
1932
        else:
1933
            self.size_hint = sizeHint
1934
        if enableDragDrop:
1935
            self.setDragEnabled(True)
1936
            self.setAcceptDrops(True)
1937
            self.setDropIndicatorShown(True)
1938
1939
    def sizeHint(self):
1940
        return self.size_hint
1941
1942
    def dragEnterEvent(self, ev):
1943
        super().dragEnterEvent(ev)
1944
        if self.valid_data_callback:
1945
            self.valid_data_callback(ev)
1946
        elif isinstance(ev.source(), OrangeListBox):
1947
            ev.setDropAction(Qt.MoveAction)
1948
            ev.accept()
1949
        else:
1950
            ev.ignore()
1951
1952
    def dropEvent(self, ev):
1953
        ev.setDropAction(Qt.MoveAction)
1954
        super().dropEvent(ev)
1955
1956
        items = self.update_master()
1957
        if ev.source() is not self:
1958
            ev.source().update_master(exclude=items)
1959
1960
        if self.drop_callback:
1961
            self.drop_callback()
1962
1963
    def update_master(self, exclude=()):
1964
        control_list = [self.item(i).data(Qt.UserRole) for i in range(self.count()) if self.item(i).data(Qt.UserRole) not in exclude]
0 ignored issues
show
This line is too long as per the coding-style (133/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1965
        if self.ogLabels:
1966
            master_list = getattr(self.master, self.ogLabels)
1967
1968
            if master_list != control_list:
1969
                setattr(self.master, self.ogLabels, control_list)
1970
        return control_list
1971
1972
    def updateGeometries(self):
1973
        # A workaround for a bug in Qt
1974
        # (see: http://bugreports.qt.nokia.com/browse/QTBUG-14412)
1975
        if getattr(self, "_updatingGeometriesNow", False):
1976
            return
1977
        self._updatingGeometriesNow = True
0 ignored issues
show
The attribute _updatingGeometriesNow was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1978
        try:
1979
            return super().updateGeometries()
1980
        finally:
1981
            self._updatingGeometriesNow = False
0 ignored issues
show
The attribute _updatingGeometriesNow was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1982
1983
1984
# TODO: SmallWidgetButton is used only in OWkNNOptimization.py. (Re)Move.
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
1985
# eliminated?
1986
class SmallWidgetButton(QtGui.QPushButton):
1987
    def __init__(self, widget, text="", pixmap=None, box=None,
1988
                 orientation='vertical', autoHideWidget=None, **misc):
1989
        #self.parent = parent
1990
        if pixmap is not None:
1991
            iconDir = os.path.join(os.path.dirname(__file__), "icons")
1992
            name = ""
1993
            if isinstance(pixmap, str):
1994
                if os.path.exists(pixmap):
1995
                    name = pixmap
1996
                elif os.path.exists(os.path.join(iconDir, pixmap)):
1997
                    name = os.path.join(iconDir, pixmap)
1998
            elif isinstance(pixmap, (QtGui.QPixmap, QtGui.QIcon)):
1999
                name = pixmap
2000
            name = name or os.path.join(iconDir, "arrow_down.png")
2001
            super().__init__(QtGui.QIcon(name), text, widget)
2002
        else:
2003
            super().__init__(text, widget)
2004
        if widget.layout() is not None:
2005
            widget.layout().addWidget(self)
2006
        # create autohide widget and set a layout
2007
        self.widget = self.autohideWidget = \
2008
            (autoHideWidget or AutoHideWidget)(None, Qt.Popup)
2009
        setLayout(self.widget, orientation)
2010
        if box:
2011
            self.widget = widgetBox(self.widget, box, orientation)
2012
        self.autohideWidget.hide()
2013
        miscellanea(self, self.widget, widget, **misc)
2014
2015
    def mousePressEvent(self, ev):
2016
        super().mousePressEvent(ev)
2017
        if self.autohideWidget.isVisible():
2018
            self.autohideWidget.hide()
2019
        else:
2020
            self.autohideWidget.move(
2021
                self.mapToGlobal(QtCore.QPoint(0, self.height())))
2022
            self.autohideWidget.show()
2023
2024
2025
class SmallWidgetLabel(QtGui.QLabel):
2026
    def __init__(self, widget, text="", pixmap=None, box=None,
2027
                 orientation='vertical', **misc):
2028
        super().__init__(widget)
2029
        if text:
2030
            self.setText("<font color=\"#C10004\">" + text + "</font>")
2031
        elif pixmap is not None:
2032
            iconDir = os.path.join(os.path.dirname(__file__), "icons")
2033
            name = ""
2034
            if isinstance(pixmap, str):
2035
                if os.path.exists(pixmap):
2036
                    name = pixmap
2037
                elif os.path.exists(os.path.join(iconDir, pixmap)):
2038
                    name = os.path.join(iconDir, pixmap)
2039
            elif isinstance(pixmap, (QtGui.QPixmap, QtGui.QIcon)):
2040
                name = pixmap
2041
            name = name or os.path.join(iconDir, "arrow_down.png")
2042
            self.setPixmap(QtGui.QPixmap(name))
2043
        self.autohideWidget = self.widget = AutoHideWidget(None, Qt.Popup)
2044
        setLayout(self.widget, orientation)
2045
        if box:
2046
            self.widget = widgetBox(self.widget, box, orientation)
2047
        self.autohideWidget.hide()
2048
        miscellanea(self, self.widget, widget, **misc)
2049
2050
    def mousePressEvent(self, ev):
2051
        super().mousePressEvent(ev)
2052
        if self.autohideWidget.isVisible():
2053
            self.autohideWidget.hide()
2054
        else:
2055
            self.autohideWidget.move(
2056
                self.mapToGlobal(QtCore.QPoint(0, self.height())))
2057
            self.autohideWidget.show()
2058
2059
2060
class AutoHideWidget(QtGui.QWidget):
2061
    def leaveEvent(self, _):
2062
        self.hide()
2063
2064
2065
# TODO Class SearchLineEdit: it doesn't seem to be used anywhere
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
2066
# see widget DataDomain
2067
class SearchLineEdit(QtGui.QLineEdit):
2068
    """
2069
    QLineEdit for quick searches
2070
    """
2071
    def __init__(self, t, searcher):
2072
        super().__init__(self, t)
2073
        self.searcher = searcher
2074
2075
    def keyPressEvent(self, e):
2076
        """
2077
        Handles keys up and down by selecting the previous and the next item
2078
        in the list, and the escape key, which hides the searcher.
2079
        """
2080
        k = e.key()
2081
        if k == Qt.Key_Down:
2082
            curItem = self.searcher.lb.currentItem()
2083
            if curItem + 1 < self.searcher.lb.count():
2084
                self.searcher.lb.setCurrentItem(curItem + 1)
2085
        elif k == Qt.Key_Up:
2086
            curItem = self.searcher.lb.currentItem()
2087
            if curItem:
2088
                self.searcher.lb.setCurrentItem(curItem - 1)
2089
        elif k == Qt.Key_Escape:
2090
            self.searcher.window.hide()
2091
        else:
2092
            return super().keyPressEvent(e)
2093
2094
2095
# TODO Class Searcher: it doesn't seem to be used anywhere
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
2096
# see widget DataDomain
2097
class Searcher:
2098
    """
2099
    The searcher class for :obj:`SearchLineEdit`.
2100
    """
2101
    def __init__(self, control, master):
2102
        self.control = control
2103
        self.master = master
2104
2105
    def __call__(self):
2106
        _s = QtGui.QStyle
2107
        self.window = t = QtGui.QFrame(
0 ignored issues
show
The attribute window was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
2108
            self.master,
2109
            _s.WStyle_Dialog + _s.WStyle_Tool + _s.WStyle_Customize +
2110
            _s.WStyle_NormalBorder)
2111
        QtGui.QVBoxLayout(t).setAutoAdd(1)
2112
        gs = self.master.mapToGlobal(QtCore.QPoint(0, 0))
2113
        gl = self.control.mapToGlobal(QtCore.QPoint(0, 0))
2114
        t.move(gl.x() - gs.x(), gl.y() - gs.y())
2115
        self.allItems = [self.control.text(i)
0 ignored issues
show
The attribute allItems was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
2116
                         for i in range(self.control.count())]
2117
        le = SearchLineEdit(t, self)
2118
        self.lb = QtGui.QListWidget(t)
0 ignored issues
show
The attribute lb was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
2119
        for i in self.allItems:
2120
            self.lb.insertItem(i)
2121
        t.setFixedSize(self.control.width(), 200)
2122
        t.show()
2123
        le.setFocus()
2124
        le.textChanged.connect(self.textChanged)
2125
        le.returnPressed.connect(self.returnPressed)
2126
        self.lb.itemClicked.connect(self.mouseClicked)
2127
2128
    def textChanged(self, s):
2129
        s = str(s)
2130
        self.lb.clear()
2131
        for i in self.allItems:
2132
            if s.lower() in i.lower():
2133
                self.lb.insertItem(i)
2134
2135
    def returnPressed(self):
2136
        if self.lb.count():
2137
            self.conclude(self.lb.text(max(0, self.lb.currentItem())))
2138
        else:
2139
            self.window.hide()
2140
2141
    def mouseClicked(self, item):
2142
        self.conclude(item.text())
2143
2144
    def conclude(self, value):
2145
        index = self.allItems.index(value)
2146
        self.control.setCurrentItem(index)
2147
        if self.control.cback:
2148
            if self.control.sendSelectedValue:
2149
                self.control.cback(value)
2150
            else:
2151
                self.control.cback(index)
2152
        if self.control.cfunc:
2153
            self.control.cfunc()
2154
        self.window.hide()
2155
2156
2157
# creates a widget box with a button in the top right edge that shows/hides all
2158
# widgets in the box and collapse the box to its minimum height
2159
# TODO collapsableWidgetBox is used only in OWMosaicDisplay.py; (re)move
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
2160
class collapsableWidgetBox(QtGui.QGroupBox):
2161
    def __init__(self, widget, box="", master=None, value="",
2162
                 orientation="vertical", callback=None):
2163
        super().__init__(widget)
2164
        self.setFlat(1)
2165
        setLayout(self, orientation)
2166
        if widget.layout() is not None:
2167
            widget.layout().addWidget(self)
2168
        if isinstance(box, str):
2169
            self.setTitle(" " + box.strip() + " ")
2170
        self.setCheckable(True)
2171
        self.master = master
2172
        self.value = value
2173
        self.callback = callback
2174
        self.clicked.connect(self.toggled)
2175
2176
    def toggled(self, _=0):
2177
        if self.value:
2178
            self.master.__setattr__(self.value, self.isChecked())
2179
            self.updateControls()
2180
        if self.callback is not None:
2181
            self.callback()
2182
2183
    def updateControls(self):
2184
        val = getdeepattr(self.master, self.value)
2185
        width = self.width()
2186
        self.setChecked(val)
2187
        self.setFlat(not val)
2188
        self.setMinimumSize(QtCore.QSize(width if not val else 0, 0))
2189
        for c in self.children():
2190
            if isinstance(c, QtGui.QLayout):
2191
                continue
2192
            if val:
2193
                c.show()
2194
            else:
2195
                c.hide()
2196
2197
2198
# creates an icon that allows you to show/hide the widgets in the widgets list
2199
# TODO Class widgetHider doesn't seem to be used anywhere; remove?
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
2200
class widgetHider(QtGui.QWidget):
2201
    def __init__(self, widget, master, value, _=(19, 19), widgets=None,
2202
                 tooltip=None):
2203
        super().__init__(widget)
2204
        if widget.layout() is not None:
2205
            widget.layout().addWidget(self)
2206
        self.value = value
2207
        self.master = master
2208
        if tooltip:
2209
            self.setToolTip(tooltip)
2210
        iconDir = os.path.join(os.path.dirname(__file__), "icons")
2211
        icon1 = os.path.join(iconDir, "arrow_down.png")
2212
        icon2 = os.path.join(iconDir, "arrow_up.png")
2213
        self.pixmaps = [QtGui.QPixmap(icon1), QtGui.QPixmap(icon2)]
2214
        self.setFixedSize(self.pixmaps[0].size())
2215
        self.disables = list(widgets or [])
2216
        self.makeConsistent = Disabler(self, master, value, type=HIDER)
2217
        if widgets:
2218
            self.setWidgets(widgets)
2219
2220
    def mousePressEvent(self, ev):
0 ignored issues
show
The argument ev seems to be unused.
Loading history...
2221
        self.master.__setattr__(self.value,
2222
                                not getdeepattr(self.master, self.value))
2223
        self.makeConsistent()
2224
2225
    def setWidgets(self, widgets):
2226
        self.disables = list(widgets)
2227
        self.makeConsistent()
2228
2229
    def paintEvent(self, ev):
2230
        super().paintEvent(ev)
2231
        if self.pixmaps:
2232
            pix = self.pixmaps[getdeepattr(self.master, self.value)]
2233
            painter = QtGui.QPainter(self)
2234
            painter.drawPixmap(0, 0, pix)
2235
2236
2237
##############################################################################
2238
# callback handlers
2239
2240
2241
def auto_commit(widget, master, value, label, auto_label=None, box=True,
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
2242
                checkbox_label=None, orientation=None, **misc):
2243
    """
2244
    Add a commit button with auto-commit check box.
2245
2246
    The widget must have a commit method and a setting that stores whether
2247
    auto-commit is on.
2248
2249
    The function replaces the commit method with a new commit method that
2250
    checks whether auto-commit is on. If it is, it passes the call to the
2251
    original commit, otherwise it sets the dirty flag.
2252
2253
    The checkbox controls the auto-commit. When auto-commit is switched on, the
2254
    checkbox callback checks whether the dirty flag is on and calls the original
2255
    commit.
2256
2257
    Important! Do not connect any signals to the commit before calling
2258
    auto_commit.
2259
2260
    :param widget: the widget into which the box with the button is inserted
2261
    :type widget: PyQt4.QtGui.QWidget or None
2262
    :param value: the master's attribute which stores whether the auto-commit
2263
        is on
2264
    :type value:  str
2265
    :param master: master widget
2266
    :type master: OWWidget or OWComponent
2267
    :param label: The button label
2268
    :type label: str
2269
    :param label: The label used when auto-commit is on; default is
2270
        `"Auto " + label`
2271
    :type label: str
2272
    :param box: tells whether the widget has a border, and its label
2273
    :type box: int or str or None
2274
    :return: the box
2275
    """
2276
    def u():
2277
        if getattr(master, value):
2278
            btn.setText(auto_label)
2279
            btn.setEnabled(False)
2280
            if dirty:
2281
                do_commit()
2282
        else:
2283
            btn.setText(label)
2284
            btn.setEnabled(True)
2285
2286
    def commit():
2287
        nonlocal dirty
2288
        if getattr(master, value):
2289
            do_commit()
2290
        else:
2291
            dirty = True
2292
2293
    def do_commit():
2294
        nonlocal dirty
2295
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
2296
        master.unconditional_commit()
2297
        QApplication.restoreOverrideCursor()
2298
        dirty = False
2299
2300
    dirty = False
2301
    master.unconditional_commit = master.commit
2302
    if not auto_label:
2303
        if checkbox_label:
2304
            auto_label = label
2305
        else:
2306
            auto_label = "Auto " + label.lower() + " is on"
2307
    if isinstance(box, QtGui.QWidget):
2308
        b = box
2309
    else:
2310
        if orientation is None:
2311
            orientation = bool(checkbox_label)
2312
        b = widgetBox(widget, box=box, orientation=orientation,
2313
                      addToLayout=False)
2314
        b.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum)
2315
2316
    b.checkbox = cb = checkBox(b, master, value, checkbox_label or " ",
2317
                               callback=u, tooltip=auto_label)
2318
    cb.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
2319
    b.button = btn = button(b, master, label, callback=do_commit)
2320
    if not checkbox_label:
2321
        btn.setSizePolicy(QtGui.QSizePolicy.Expanding,
2322
                          QtGui.QSizePolicy.Preferred)
2323
    u()
2324
    master.commit = commit
2325
    miscellanea(b, widget, widget,
2326
                addToLayout=not isinstance(box, QtGui.QWidget), **misc)
2327
    return b
2328
2329
2330
class ControlledList(list):
2331
    """
2332
    A class derived from a list that is connected to a
2333
    :obj:`PyQt4.QtGui.QListBox`: the list contains indices of items that are
2334
    selected in the list box. Changing the list content changes the
2335
    selection in the list box.
2336
    """
2337
    def __init__(self, content, listBox=None):
0 ignored issues
show
Comprehensibility Bug introduced by
listBox is re-defining a name which is already available in the outer-scope (previously defined on line 1304).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
2338
        super().__init__(content)
2339
        self.listBox = listBox
2340
2341
    def __reduce__(self):
2342
        # cannot pickle self.listBox, but can't discard it
2343
        # (ControlledList may live on)
2344
        import copyreg
2345
        return copyreg._reconstructor, (list, list, ()), None, self.__iter__()
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _reconstructor 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...
2346
2347
    # TODO ControllgedList.item2name is probably never used
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
2348
    def item2name(self, item):
2349
        item = self.listBox.labels[item]
2350
        if type(item) is tuple:
2351
            return item[1]
2352
        else:
2353
            return item
2354
2355
    def __setitem__(self, index, item):
2356
        if isinstance(index, int):
2357
            self.listBox.item(self[index]).setSelected(0)
2358
            item.setSelected(1)
2359
        else:
2360
            for i in self[index]:
2361
                self.listBox.item(i).setSelected(0)
2362
            for i in item:
2363
                self.listBox.item(i).setSelected(1)
2364
        super().__setitem__(index, item)
2365
2366
    def __delitem__(self, index):
2367
        if isinstance(index, int):
2368
            self.listBox.item(self[index]).setSelected(0)
2369
        else:
2370
            for i in self[index]:
2371
                self.listBox.item(i).setSelected(0)
2372
        super().__delitem__(index)
2373
2374
    def append(self, item):
2375
        super().append(item)
2376
        item.setSelected(1)
2377
2378
    def extend(self, items):
2379
        super().extend(items)
2380
        for i in items:
2381
            self.listBox.item(i).setSelected(1)
2382
2383
    def insert(self, index, item):
2384
        item.setSelected(1)
2385
        super().insert(index, item)
2386
2387
    def pop(self, index=-1):
2388
        i = super().pop(index)
2389
        self.listBox.item(i).setSelected(0)
2390
2391
    def remove(self, item):
2392
        item.setSelected(0)
2393
        super().remove(item)
2394
2395
2396
def connectControl(master, value, f, signal,
2397
                   cfront, cback=None, cfunc=None, fvcb=None):
2398
    cback = cback or value and ValueCallback(master, value, fvcb)
2399
    if cback:
2400
        if signal:
2401
            signal.connect(cback)
2402
        cback.opposite = cfront
2403
        if value and cfront and hasattr(master, CONTROLLED_ATTRIBUTES):
2404
            getattr(master, CONTROLLED_ATTRIBUTES)[value] = cfront
2405
    cfunc = cfunc or f and FunctionCallback(master, f)
2406
    if cfunc:
2407
        if signal:
2408
            signal.connect(cfunc)
2409
        cfront.opposite = tuple(filter(None, (cback, cfunc)))
2410
    return cfront, cback, cfunc
2411
2412
2413
class ControlledCallback:
2414
    def __init__(self, widget, attribute, f=None):
2415
        self.widget = widget
2416
        self.attribute = attribute
2417
        self.f = f
2418
        self.disabled = 0
2419
        if isinstance(widget, dict):
2420
            return  # we can't assign attributes to dict
2421
        if not hasattr(widget, "callbackDeposit"):
2422
            widget.callbackDeposit = []
2423
        widget.callbackDeposit.append(self)
2424
2425
    def acyclic_setattr(self, value):
2426
        if self.disabled:
2427
            return
2428
        if self.f:
2429
            if self.f in (int, float) and (
2430
                    not value or isinstance(value, str) and value in "+-"):
2431
                value = self.f(0)
2432
            else:
2433
                value = self.f(value)
2434
        opposite = getattr(self, "opposite", None)
2435
        if opposite:
2436
            try:
2437
                opposite.disabled += 1
2438
                if type(self.widget) is dict:
2439
                    self.widget[self.attribute] = value
2440
                else:
2441
                    setattr(self.widget, self.attribute, value)
2442
            finally:
2443
                opposite.disabled -= 1
2444
        else:
2445
            if isinstance(self.widget, dict):
2446
                self.widget[self.attribute] = value
2447
            else:
2448
                setattr(self.widget, self.attribute, value)
2449
2450
2451
class ValueCallback(ControlledCallback):
2452
    # noinspection PyBroadException
2453
    def __call__(self, value):
2454
        if value is None:
2455
            return
2456
        try:
2457
            self.acyclic_setattr(value)
2458
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
2459
            print("gui.ValueCallback: %s" % value)
2460
            import traceback
2461
            import sys
2462
            traceback.print_exception(*sys.exc_info())
2463
2464
2465
class ValueCallbackCombo(ValueCallback):
2466
    def __init__(self, widget, attribute, f=None, control2attributeDict=None):
2467
        super().__init__(widget, attribute, f)
2468
        self.control2attributeDict = control2attributeDict or {}
2469
2470
    def __call__(self, value):
2471
        value = str(value)
2472
        return super().__call__(self.control2attributeDict.get(value, value))
2473
2474
2475
class ValueCallbackLineEdit(ControlledCallback):
2476
    def __init__(self, control, widget, attribute, f=None):
2477
        ControlledCallback.__init__(self, widget, attribute, f)
2478
        self.control = control
2479
2480
    # noinspection PyBroadException
2481
    def __call__(self, value):
2482
        if value is None:
2483
            return
2484
        try:
2485
            pos = self.control.cursorPosition()
2486
            self.acyclic_setattr(value)
2487
            self.control.setCursorPosition(pos)
2488
        except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
2489
            print("invalid value ", value, type(value))
2490
            import traceback
2491
            import sys
2492
            traceback.print_exception(*sys.exc_info())
2493
2494
2495
class SetLabelCallback:
2496
    def __init__(self, widget, label, format="%5.2f", f=None):
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
Bug Best Practice introduced by
This seems to re-define the built-in format.

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

Loading history...
2497
        self.widget = widget
2498
        self.label = label
2499
        self.format = format
2500
        self.f = f
2501
        if hasattr(widget, "callbackDeposit"):
2502
            widget.callbackDeposit.append(self)
2503
        self.disabled = 0
2504
2505
    def __call__(self, value):
2506
        if not self.disabled and value is not None:
2507
            if self.f:
2508
                value = self.f(value)
2509
            self.label.setText(self.format % value)
2510
2511
2512
class FunctionCallback:
2513
    def __init__(self, master, f, widget=None, id=None, getwidget=False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

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

Loading history...
2514
        self.master = master
2515
        self.widget = widget
2516
        self.f = f
2517
        self.id = id
2518
        self.getwidget = getwidget
2519
        if hasattr(master, "callbackDeposit"):
2520
            master.callbackDeposit.append(self)
2521
        self.disabled = 0
2522
2523
    def __call__(self, *value):
2524
        if not self.disabled and value is not None:
2525
            kwds = {}
2526
            if self.id is not None:
2527
                kwds['id'] = self.id
2528
            if self.getwidget:
2529
                kwds['widget'] = self.widget
2530
            if isinstance(self.f, list):
2531
                for f in self.f:
2532
                    f(**kwds)
2533
            else:
2534
                self.f(**kwds)
2535
2536
2537
class CallBackListBox:
2538
    def __init__(self, control, widget):
2539
        self.control = control
2540
        self.widget = widget
2541
        self.disabled = 0
2542
2543
    def __call__(self, *_):  # triggered by selectionChange()
2544
        if not self.disabled and self.control.ogValue is not None:
2545
            clist = getdeepattr(self.widget, self.control.ogValue)
2546
             # skip the overloaded method to avoid a cycle
2547
            list.__delitem__(clist, slice(0, len(clist)))
2548
            control = self.control
2549
            for i in range(control.count()):
2550
                if control.item(i).isSelected():
2551
                    list.append(clist, i)
2552
            self.widget.__setattr__(self.control.ogValue, clist)
2553
2554
2555
class CallBackRadioButton:
2556
    def __init__(self, control, widget):
2557
        self.control = control
2558
        self.widget = widget
2559
        self.disabled = False
2560
2561
    def __call__(self, *_):  # triggered by toggled()
2562
        if not self.disabled and self.control.ogValue is not None:
2563
            arr = [butt.isChecked() for butt in self.control.buttons]
2564
            self.widget.__setattr__(self.control.ogValue, arr.index(1))
2565
2566
2567
class CallBackLabeledSlider:
2568
    def __init__(self, control, widget, lookup):
2569
        self.control = control
2570
        self.widget = widget
2571
        self.lookup = lookup
2572
        self.disabled = False
2573
2574
    def __call__(self, *_):
2575
        if not self.disabled and self.control.ogValue is not None:
2576
            self.widget.__setattr__(self.control.ogValue,
2577
                                    self.lookup[self.control.value()])
2578
2579
2580
##############################################################################
2581
# call fronts (change of the attribute value changes the related control)
2582
2583
2584
class ControlledCallFront:
2585
    def __init__(self, control):
2586
        self.control = control
2587
        self.disabled = 0
2588
2589
    def action(self, *_):
2590
        pass
2591
2592
    def __call__(self, *args):
2593
        if not self.disabled:
2594
            opposite = getattr(self, "opposite", None)
2595
            if opposite:
2596
                try:
2597
                    for op in opposite:
2598
                        op.disabled += 1
2599
                    self.action(*args)
2600
                finally:
2601
                    for op in opposite:
2602
                        op.disabled -= 1
0 ignored issues
show
The loop variable op might not be defined here.
Loading history...
2603
            else:
2604
                self.action(*args)
2605
2606
2607
class CallFrontSpin(ControlledCallFront):
2608
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2609
        if value is not None:
2610
            self.control.setValue(value)
2611
2612
2613
class CallFrontDoubleSpin(ControlledCallFront):
2614
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2615
        if value is not None:
2616
            self.control.setValue(value)
2617
2618
2619
class CallFrontCheckBox(ControlledCallFront):
2620
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2621
        if value is not None:
2622
            values = [Qt.Unchecked, Qt.Checked, Qt.PartiallyChecked]
2623
            self.control.setCheckState(values[value])
2624
2625
2626
class CallFrontButton(ControlledCallFront):
2627
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2628
        if value is not None:
2629
            self.control.setChecked(bool(value))
2630
2631
2632
class CallFrontComboBox(ControlledCallFront):
2633
    def __init__(self, control, valType=None, control2attributeDict=None):
2634
        super().__init__(control)
2635
        self.valType = valType
2636
        if control2attributeDict is None:
2637
            self.attribute2controlDict = {}
2638
        else:
2639
            self.attribute2controlDict = \
2640
                {y: x for x, y in control2attributeDict.items()}
2641
2642
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2643
        if value is not None:
2644
            value = self.attribute2controlDict.get(value, value)
2645
            if self.valType:
2646
                for i in range(self.control.count()):
2647
                    if self.valType(str(self.control.itemText(i))) == value:
2648
                        self.control.setCurrentIndex(i)
2649
                        return
2650
                values = ""
2651
                for i in range(self.control.count()):
2652
                    values += str(self.control.itemText(i)) + \
2653
                        (i < self.control.count() - 1 and ", " or ".")
2654
                print("unable to set %s to value '%s'. Possible values are %s"
2655
                      % (self.control, value, values))
2656
            else:
2657
                if value < self.control.count():
2658
                    self.control.setCurrentIndex(value)
2659
2660
2661
class CallFrontHSlider(ControlledCallFront):
2662
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2663
        if value is not None:
2664
            self.control.setValue(value)
2665
2666
2667
class CallFrontLabeledSlider(ControlledCallFront):
2668
    def __init__(self, control, lookup):
2669
        super().__init__(control)
2670
        self.lookup = lookup
2671
2672
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2673
        if value is not None:
2674
            self.control.setValue(self.lookup.index(value))
2675
2676
2677
class CallFrontLogSlider(ControlledCallFront):
2678
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2679
        if value is not None:
2680
            if value < 1e-30:
2681
                print("unable to set %s to %s (value too small)" %
2682
                      (self.control, value))
2683
            else:
2684
                self.control.setValue(math.log10(value))
2685
2686
2687
class CallFrontLineEdit(ControlledCallFront):
2688
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2689
        self.control.setText(str(value))
2690
2691
2692
class CallFrontRadioButtons(ControlledCallFront):
2693
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2694
        if value < 0 or value >= len(self.control.buttons):
2695
            value = 0
2696
        self.control.buttons[value].setChecked(1)
2697
2698
2699
class CallFrontListBox(ControlledCallFront):
2700
    def action(self, value):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2701
        if value is not None:
2702
            if not isinstance(value, ControlledList):
2703
                setattr(self.control.ogMaster, self.control.ogValue,
2704
                        ControlledList(value, self.control))
2705
            for i in range(self.control.count()):
2706
                shouldBe = i in value
2707
                if shouldBe != self.control.item(i).isSelected():
2708
                    self.control.item(i).setSelected(shouldBe)
2709
2710
2711
class CallFrontListBoxLabels(ControlledCallFront):
2712
    unknownType = None
2713
2714
    def action(self, values):
0 ignored issues
show
Arguments number differs from overridden 'action' method
Loading history...
2715
        self.control.clear()
2716
        if values:
2717
            for value in values:
2718
                if isinstance(value, tuple):
2719
                    text, icon = value
2720
                    if isinstance(icon, int):
2721
                        item = QtGui.QListWidgetItem(attributeIconDict[icon], text)
2722
                    else:
2723
                        item = QtGui.QListWidgetItem(icon, text)
2724
                elif isinstance(value, Variable):
2725
                    item = QtGui.QListWidgetItem(*attributeItem(value))
2726
                else:
2727
                    item = QtGui.QListWidgetItem(value)
2728
2729
                item.setData(Qt.UserRole, value)
2730
                self.control.addItem(item)
2731
2732
2733
class CallFrontLabel:
2734
    def __init__(self, control, label, master):
0 ignored issues
show
Comprehensibility Bug introduced by
label is re-defining a name which is already available in the outer-scope (previously defined on line 597).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
2735
        self.control = control
2736
        self.label = label
2737
        self.master = master
2738
2739
    def __call__(self, *_):
2740
        self.control.setText(self.label % self.master.__dict__)
2741
2742
##############################################################################
2743
## Disabler is a call-back class for check box that can disable/enable other
2744
## widgets according to state (checked/unchecked, enabled/disable) of the
2745
## given check box
2746
##
2747
## Tricky: if self.propagateState is True (default), then if check box is
2748
## disabled the related widgets will be disabled (even if the checkbox is
2749
## checked). If self.propagateState is False, the related widgets will be
2750
## disabled/enabled if check box is checked/clear, disregarding whether the
2751
## check box itself is enabled or not. (If you don't understand, see the
2752
## code :-)
2753
DISABLER = 1
2754
HIDER = 2
2755
2756
2757
# noinspection PyShadowingBuiltins
2758
class Disabler:
2759
    def __init__(self, widget, master, valueName, propagateState=True,
2760
                 type=DISABLER):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in type.

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

Loading history...
2761
        self.widget = widget
2762
        self.master = master
2763
        self.valueName = valueName
2764
        self.propagateState = propagateState
2765
        self.type = type
2766
2767
    def __call__(self, *value):
2768
        currState = self.widget.isEnabled()
2769
        if currState or not self.propagateState:
2770
            if len(value):
2771
                disabled = not value[0]
2772
            else:
2773
                disabled = not getdeepattr(self.master, self.valueName)
2774
        else:
2775
            disabled = 1
2776
        for w in self.widget.disables:
2777
            if type(w) is tuple:
2778
                if isinstance(w[0], int):
2779
                    i = 1
2780
                    if w[0] == -1:
2781
                        disabled = not disabled
2782
                else:
2783
                    i = 0
2784
                if self.type == DISABLER:
2785
                    w[i].setDisabled(disabled)
2786
                elif self.type == HIDER:
2787
                    if disabled:
2788
                        w[i].hide()
2789
                    else:
2790
                        w[i].show()
2791
                if hasattr(w[i], "makeConsistent"):
2792
                    w[i].makeConsistent()
2793
            else:
2794
                if self.type == DISABLER:
2795
                    w.setDisabled(disabled)
2796
                elif self.type == HIDER:
2797
                    if disabled:
2798
                        w.hide()
2799
                    else:
2800
                        w.show()
2801
2802
##############################################################################
2803
# some table related widgets
2804
2805
2806
# noinspection PyShadowingBuiltins
2807
class tableItem(QtGui.QTableWidgetItem):
2808
    def __init__(self, table, x, y, text, editType=None, backColor=None,
0 ignored issues
show
Comprehensibility Bug introduced by
table is re-defining a name which is already available in the outer-scope (previously defined on line 3216).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
2809
                 icon=None, type=QtGui.QTableWidgetItem.Type):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in type.

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

Loading history...
2810
        super().__init__(type)
2811
        if icon:
2812
            self.setIcon(QtGui.QIcon(icon))
2813
        if editType is not None:
2814
            self.setFlags(editType)
2815
        else:
2816
            self.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable |
2817
                          Qt.ItemIsSelectable)
2818
        if backColor is not None:
2819
            self.setBackground(QtGui.QBrush(backColor))
2820
        # we add it this way so that text can also be int and sorting will be
2821
        # done properly (as integers and not as text)
2822
        self.setData(Qt.DisplayRole, text)
2823
        table.setItem(x, y, self)
2824
2825
2826
TableValueRole = next(OrangeUserRole)  # Role to retrieve orange.Value
2827
TableClassValueRole = next(OrangeUserRole)  # Retrieve class value for the row
2828
TableDistribution = next(OrangeUserRole)  # Retrieve distribution of the column
2829
TableVariable = next(OrangeUserRole)  # Role to retrieve the column's variable
2830
2831
BarRatioRole = next(OrangeUserRole)  # Ratio for drawing distribution bars
2832
BarBrushRole = next(OrangeUserRole)  # Brush for distribution bar
2833
2834
SortOrderRole = next(OrangeUserRole)  # Used for sorting
2835
2836
2837
class TableBarItem(QtGui.QItemDelegate):
2838
    BarRole = next(OrangeUserRole)
2839
    ColorRole = next(OrangeUserRole)
2840
2841
    def __init__(self, parent=None, color=QtGui.QColor(255, 170, 127),
2842
                 color_schema=None):
2843
        """
2844
        :param QObject parent: Parent object.
2845
        :param QColor color: Default color of the distribution bar.
2846
        :param color_schema:
2847
            If not None it must be an instance of
2848
            :class:`OWColorPalette.ColorPaletteGenerator` (note: this
2849
            parameter, if set, overrides the ``color``)
2850
        :type color_schema: :class:`OWColorPalette.ColorPaletteGenerator`
2851
        """
2852
        super().__init__(parent)
2853
        self.color = color
2854
        self.color_schema = color_schema
2855
2856
    def paint(self, painter, option, index):
2857
        painter.save()
2858
        self.drawBackground(painter, option, index)
2859
        ratio = index.data(TableBarItem.BarRole)
2860
        if isinstance(ratio, float):
2861
            if math.isnan(ratio):
2862
                ratio = None
2863
2864
        color = self.color
2865
        if self.color_schema is not None and ratio is not None:
2866
            class_ = index.data(TableClassValueRole)
2867
            if isinstance(class_, Orange.data.Value) and \
2868
                    class_.variable.is_discrete and \
2869
                    not math.isnan(class_):
2870
                color = self.color_schema[int(class_)]
2871
2872
        if ratio is not None:
2873
            painter.save()
2874
            painter.setPen(QtGui.QPen(QtGui.QBrush(color), 5,
2875
                                      Qt.SolidLine, Qt.RoundCap))
2876
            rect = option.rect.adjusted(3, 0, -3, -5)
2877
            x, y = rect.x(), rect.y() + rect.height()
2878
            painter.drawLine(x, y, x + rect.width() * ratio, y)
2879
            painter.restore()
2880
            text_rect = option.rect.adjusted(0, 0, 0, -3)
2881
        else:
2882
            text_rect = option.rect
2883
        text = index.data(Qt.DisplayRole)
2884
        self.drawDisplay(painter, option, text_rect, text)
2885
        painter.restore()
2886
2887
2888
class BarItemDelegate(QtGui.QStyledItemDelegate):
2889
    def __init__(self, parent, brush=QtGui.QBrush(QtGui.QColor(255, 170, 127)),
2890
                 scale=(0.0, 1.0)):
2891
        super().__init__(parent)
2892
        self.brush = brush
2893
        self.scale = scale
2894
2895
    def paint(self, painter, option, index):
2896
        if option.widget is not None:
2897
            style = option.widget.style()
2898
        else:
2899
            style = QtGui.QApplication.style()
2900
2901
        style.drawPrimitive(
2902
            QtGui.QStyle.PE_PanelItemViewRow, option, painter,
2903
            option.widget)
2904
        style.drawPrimitive(
2905
            QtGui.QStyle.PE_PanelItemViewItem, option, painter,
2906
            option.widget)
2907
2908
        rect = option.rect
2909
        val = index.data(Qt.DisplayRole)
2910
        if isinstance(val, float):
2911
            minv, maxv = self.scale
2912
            val = (val - minv) / (maxv - minv)
2913
            painter.save()
2914
            if option.state & QtGui.QStyle.State_Selected:
2915
                painter.setOpacity(0.75)
2916
            painter.setBrush(self.brush)
2917
            painter.drawRect(
2918
                rect.adjusted(1, 1, - rect.width() * (1.0 - val) - 2, -2))
2919
            painter.restore()
2920
2921
2922
class IndicatorItemDelegate(QtGui.QStyledItemDelegate):
2923
    IndicatorRole = next(OrangeUserRole)
2924
2925
    def __init__(self, parent, role=IndicatorRole, indicatorSize=2):
2926
        super().__init__(parent)
2927
        self.role = role
2928
        self.indicatorSize = indicatorSize
2929
2930
    def paint(self, painter, option, index):
2931
        super().paint(painter, option, index)
2932
        rect = option.rect
2933
        indicator = index.data(self.role)
2934
2935
        if indicator:
2936
            painter.save()
2937
            painter.setRenderHints(QtGui.QPainter.Antialiasing)
2938
            painter.setBrush(QtGui.QBrush(Qt.black))
2939
            painter.drawEllipse(rect.center(),
2940
                                self.indicatorSize, self.indicatorSize)
2941
            painter.restore()
2942
2943
2944
class LinkStyledItemDelegate(QtGui.QStyledItemDelegate):
2945
    LinkRole = next(OrangeUserRole)
2946
2947
    def __init__(self, parent):
2948
        super().__init__(parent)
2949
        self.mousePressState = QtCore.QModelIndex(), QtCore.QPoint()
2950
        parent.entered.connect(self.onEntered)
2951
2952
    def sizeHint(self, option, index):
0 ignored issues
show
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...
2953
        size = super().sizeHint(option, index)
2954
        return QtCore.QSize(size.width(), max(size.height(), 20))
2955
2956
    def linkRect(self, option, index):
2957
        if option.widget is not None:
2958
            style = option.widget.style()
2959
        else:
2960
            style = QtGui.QApplication.style()
2961
2962
        text = self.displayText(index.data(Qt.DisplayRole),
2963
                                QtCore.QLocale.system())
2964
        self.initStyleOption(option, index)
2965
        textRect = style.subElementRect(
2966
            QtGui.QStyle.SE_ItemViewItemText, option, option.widget)
2967
2968
        if not textRect.isValid():
2969
            textRect = option.rect
2970
        margin = style.pixelMetric(
2971
            QtGui.QStyle.PM_FocusFrameHMargin, option, option.widget) + 1
2972
        textRect = textRect.adjusted(margin, 0, -margin, 0)
2973
        font = index.data(Qt.FontRole)
2974
        if not isinstance(font, QtGui.QFont):
2975
            font = option.font
2976
2977
        metrics = QtGui.QFontMetrics(font)
2978
        elideText = metrics.elidedText(text, option.textElideMode,
2979
                                       textRect.width())
2980
        return metrics.boundingRect(textRect, option.displayAlignment,
2981
                                    elideText)
2982
2983
    def editorEvent(self, event, model, option, index):
2984
        if event.type() == QtCore.QEvent.MouseButtonPress and \
2985
                self.linkRect(option, index).contains(event.pos()):
2986
            self.mousePressState = (QtCore.QPersistentModelIndex(index),
2987
                                    QtCore.QPoint(event.pos()))
2988
2989
        elif event.type() == QtCore.QEvent.MouseButtonRelease:
2990
            link = index.data(LinkRole)
2991
            if not isinstance(link, str):
2992
                link = None
2993
2994
            pressedIndex, pressPos = self.mousePressState
2995
            if pressedIndex == index and \
2996
                    (pressPos - event.pos()).manhattanLength() < 5 and \
2997
                    link is not None:
2998
                import webbrowser
2999
                webbrowser.open(link)
3000
            self.mousePressState = QtCore.QModelIndex(), event.pos()
3001
3002
        elif event.type() == QtCore.QEvent.MouseMove:
3003
            link = index.data(LinkRole)
3004
            if not isinstance(link, str):
3005
                link = None
3006
3007
            if link is not None and \
3008
                    self.linkRect(option, index).contains(event.pos()):
3009
                self.parent().viewport().setCursor(Qt.PointingHandCursor)
3010
            else:
3011
                self.parent().viewport().setCursor(Qt.ArrowCursor)
3012
3013
        return super().editorEvent(event, model, option, index)
3014
3015
    def onEntered(self, index):
3016
        link = index.data(LinkRole)
3017
        if not isinstance(link, str):
3018
            link = None
3019
        if link is None:
3020
            self.parent().viewport().setCursor(Qt.ArrowCursor)
3021
3022
    def paint(self, painter, option, index):
3023
        link = index.data(LinkRole)
3024
        if not isinstance(link, str):
3025
            link = None
3026
3027
        if link is not None:
3028
            if option.widget is not None:
3029
                style = option.widget.style()
3030
            else:
3031
                style = QtGui.QApplication.style()
3032
            style.drawPrimitive(
3033
                QtGui.QStyle.PE_PanelItemViewRow, option, painter,
3034
                option.widget)
3035
            style.drawPrimitive(
3036
                QtGui.QStyle.PE_PanelItemViewItem, option, painter,
3037
                option.widget)
3038
3039
            text = self.displayText(index.data(Qt.DisplayRole),
3040
                                    QtCore.QLocale.system())
3041
            textRect = style.subElementRect(
3042
                QtGui.QStyle.SE_ItemViewItemText, option, option.widget)
3043
            if not textRect.isValid():
3044
                textRect = option.rect
3045
            margin = style.pixelMetric(
3046
                QtGui.QStyle.PM_FocusFrameHMargin, option, option.widget) + 1
3047
            textRect = textRect.adjusted(margin, 0, -margin, 0)
3048
            elideText = QtGui.QFontMetrics(option.font).elidedText(
3049
                text, option.textElideMode, textRect.width())
3050
            painter.save()
3051
            font = index.data(Qt.FontRole)
3052
            if not isinstance(font, QtGui.QFont):
3053
                font = option.font
3054
            painter.setFont(font)
3055
            painter.setPen(QtGui.QPen(Qt.blue))
3056
            painter.drawText(textRect, option.displayAlignment, elideText)
3057
            painter.restore()
3058
        else:
3059
            super().paint(painter, option, index)
3060
3061
3062
LinkRole = LinkStyledItemDelegate.LinkRole
3063
3064
3065
class ColoredBarItemDelegate(QtGui.QStyledItemDelegate):
3066
    """ Item delegate that can also draws a distribution bar
3067
    """
3068
    def __init__(self, parent=None, decimals=3, color=Qt.red):
3069
        super().__init__(parent)
3070
        self.decimals = decimals
3071
        self.float_fmt = "%%.%if" % decimals
3072
        self.color = QtGui.QColor(color)
3073
3074
    def displayText(self, value, locale):
0 ignored issues
show
The argument locale seems to be unused.
Loading history...
3075
        if isinstance(value, float):
3076
            return self.float_fmt % value
3077
        elif isinstance(value, str):
3078
            return value
3079
        elif value is None:
3080
            return "NA"
3081
        else:
3082
            return str(value)
3083
3084
    def sizeHint(self, option, index):
3085
        font = self.get_font(option, index)
3086
        metrics = QtGui.QFontMetrics(font)
3087
        height = metrics.lineSpacing() + 8  # 4 pixel margin
3088
        width = metrics.width(self.displayText(index.data(Qt.DisplayRole),
3089
                                               QtCore.QLocale())) + 8
3090
        return QtCore.QSize(width, height)
3091
3092
    def paint(self, painter, option, index):
3093
        self.initStyleOption(option, index)
3094
        text = self.displayText(index.data(Qt.DisplayRole), QtCore.QLocale())
3095
        ratio, have_ratio = self.get_bar_ratio(option, index)
3096
3097
        rect = option.rect
3098
        if have_ratio:
3099
            # The text is raised 3 pixels above the bar.
3100
            # TODO: Style dependent margins?
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
3101
            text_rect = rect.adjusted(4, 1, -4, -4)
3102
        else:
3103
            text_rect = rect.adjusted(4, 4, -4, -4)
3104
3105
        painter.save()
3106
        font = self.get_font(option, index)
3107
        painter.setFont(font)
3108
3109
        if option.widget is not None:
3110
            style = option.widget.style()
3111
        else:
3112
            style = QtGui.QApplication.style()
3113
3114
        style.drawPrimitive(
3115
            QtGui.QStyle.PE_PanelItemViewRow, option, painter,
3116
            option.widget)
3117
        style.drawPrimitive(
3118
            QtGui.QStyle.PE_PanelItemViewItem, option, painter,
3119
            option.widget)
3120
3121
        # TODO: Check ForegroundRole.
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
3122
        if option.state & QtGui.QStyle.State_Selected:
3123
            color = option.palette.highlightedText().color()
3124
        else:
3125
            color = option.palette.text().color()
3126
        painter.setPen(QtGui.QPen(color))
3127
3128
        align = self.get_text_align(option, index)
3129
3130
        metrics = QtGui.QFontMetrics(font)
3131
        elide_text = metrics.elidedText(
3132
            text, option.textElideMode, text_rect.width())
3133
        painter.drawText(text_rect, align, elide_text)
3134
3135
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
3136
        if have_ratio:
3137
            brush = self.get_bar_brush(option, index)
3138
3139
            painter.setBrush(brush)
3140
            painter.setPen(QtGui.QPen(brush, 1))
3141
            bar_rect = QtCore.QRect(text_rect)
3142
            bar_rect.setTop(bar_rect.bottom() - 1)
3143
            bar_rect.setBottom(bar_rect.bottom() + 1)
3144
            w = text_rect.width()
3145
            bar_rect.setWidth(max(0, min(w * ratio, w)))
3146
            painter.drawRoundedRect(bar_rect, 2, 2)
3147
        painter.restore()
3148
3149
    def get_font(self, option, index):
0 ignored issues
show
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...
3150
        font = index.data(Qt.FontRole)
3151
        if not isinstance(font, QtGui.QFont):
3152
            font = option.font
3153
        return font
3154
3155
    def get_text_align(self, _, index):
0 ignored issues
show
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...
3156
        align = index.data(Qt.TextAlignmentRole)
3157
        if not isinstance(align, int):
3158
            align = Qt.AlignLeft | Qt.AlignVCenter
3159
3160
        return align
3161
3162
    def get_bar_ratio(self, _, index):
0 ignored issues
show
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...
3163
        ratio = index.data(BarRatioRole)
3164
        return ratio, isinstance(ratio, float)
3165
3166
    def get_bar_brush(self, _, index):
3167
        bar_brush = index.data(BarBrushRole)
3168
        if not isinstance(bar_brush, (QtGui.QColor, QtGui.QBrush)):
3169
            bar_brush = self.color
3170
        return QtGui.QBrush(bar_brush)
3171
3172
##############################################################################
3173
# progress bar management
3174
3175
3176
class ProgressBar:
3177
    def __init__(self, widget, iterations):
3178
        self.iter = iterations
3179
        self.widget = widget
3180
        self.count = 0
3181
        self.widget.progressBarInit()
3182
3183
    def advance(self, count=1):
3184
        self.count += count
3185
        self.widget.progressBarSet(int(self.count * 100 / max(1, self.iter)))
3186
3187
    def finish(self):
3188
        self.widget.progressBarFinished()
3189
3190
3191
3192
##############################################################################
3193
3194
def tabWidget(widget):
3195
    w = QtGui.QTabWidget(widget)
3196
    if widget.layout() is not None:
3197
        widget.layout().addWidget(w)
3198
    return w
3199
3200
3201
def createTabPage(tabWidget, name, widgetToAdd=None, canScroll=False):
0 ignored issues
show
Comprehensibility Bug introduced by
tabWidget is re-defining a name which is already available in the outer-scope (previously defined on line 3194).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
3202
    if widgetToAdd is None:
3203
        widgetToAdd = widgetBox(tabWidget, addToLayout=0, margin=4)
3204
    if canScroll:
3205
        scrollArea = QtGui.QScrollArea()
3206
        tabWidget.addTab(scrollArea, name)
3207
        scrollArea.setWidget(widgetToAdd)
3208
        scrollArea.setWidgetResizable(1)
3209
        scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
3210
        scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
3211
    else:
3212
        tabWidget.addTab(widgetToAdd, name)
3213
    return widgetToAdd
3214
3215
3216
def table(widget, rows=0, columns=0, selectionMode=-1, addToLayout=True):
3217
    w = QtGui.QTableWidget(rows, columns, widget)
3218
    if widget and addToLayout and widget.layout() is not None:
3219
        widget.layout().addWidget(w)
3220
    if selectionMode != -1:
3221
        w.setSelectionMode(selectionMode)
3222
    w.setHorizontalScrollMode(QtGui.QTableWidget.ScrollPerPixel)
3223
    w.horizontalHeader().setMovable(True)
3224
    return w
3225
3226
3227
class VisibleHeaderSectionContextEventFilter(QtCore.QObject):
3228
    def __init__(self, parent, itemView=None):
3229
        super().__init__(parent)
3230
        self.itemView = itemView
3231
3232
    def eventFilter(self, view, event):
3233
        if not isinstance(event, QtGui.QContextMenuEvent):
3234
            return False
3235
3236
        model = view.model()
3237
        headers = [(view.isSectionHidden(i),
3238
                    model.headerData(i, view.orientation(), Qt.DisplayRole)
3239
                    ) for i in range(view.count())]
3240
        menu = QtGui.QMenu("Visible headers", view)
3241
3242
        for i, (checked, name) in enumerate(headers):
3243
            action = QtGui.QAction(name, menu)
3244
            action.setCheckable(True)
3245
            action.setChecked(not checked)
3246
            menu.addAction(action)
3247
3248
            def toogleHidden(b, section=i):
3249
                view.setSectionHidden(section, not b)
3250
                if not b:
3251
                    return
3252
                if self.itemView:
3253
                    self.itemView.resizeColumnToContents(section)
3254
                else:
3255
                    view.resizeSection(section,
3256
                                       max(view.sectionSizeHint(section), 10))
3257
3258
            action.toggled.connect(toogleHidden)
3259
        menu.exec_(event.globalPos())
3260
        return True
3261
3262
3263
def checkButtonOffsetHint(button, style=None):
0 ignored issues
show
Comprehensibility Bug introduced by
button is re-defining a name which is already available in the outer-scope (previously defined on line 1150).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
3264
    option = QtGui.QStyleOptionButton()
3265
    option.initFrom(button)
3266
    if style is None:
3267
        style = button.style()
3268
    if isinstance(button, QtGui.QCheckBox):
3269
        pm_spacing = QtGui.QStyle.PM_CheckBoxLabelSpacing
3270
        pm_indicator_width = QtGui.QStyle.PM_IndicatorWidth
3271
    else:
3272
        pm_spacing = QtGui.QStyle.PM_RadioButtonLabelSpacing
3273
        pm_indicator_width = QtGui.QStyle.PM_ExclusiveIndicatorWidth
3274
    space = style.pixelMetric(pm_spacing, option, button)
3275
    width = style.pixelMetric(pm_indicator_width, option, button)
3276
    # TODO: add other styles (Maybe load corrections from .cfg file?)
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
3277
    style_correction = {"macintosh (aqua)": -2, "macintosh(aqua)": -2,
3278
                        "plastique": 1, "cde": 1, "motif": 1}
3279
    return space + width + \
3280
        style_correction.get(QtGui.qApp.style().objectName().lower(), 0)
3281
3282
3283
def toolButtonSizeHint(button=None, style=None):
0 ignored issues
show
Comprehensibility Bug introduced by
button is re-defining a name which is already available in the outer-scope (previously defined on line 1150).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
3284
    if button is None and style is None:
3285
        style = QtGui.qApp.style()
3286
    elif style is None:
3287
        style = button.style()
3288
3289
    button_size = \
3290
        style.pixelMetric(QtGui.QStyle.PM_SmallIconSize) + \
3291
        style.pixelMetric(QtGui.QStyle.PM_ButtonMargin)
3292
    return button_size
3293
3294
3295
class FloatSlider(QtGui.QSlider):
3296
    valueChangedFloat = Signal(float)
3297
3298
    def __init__(self, orientation, min_value, max_value, step, parent=None):
3299
        super().__init__(orientation, parent)
3300
        self.setScale(min_value, max_value, step)
3301
        self.valueChanged[int].connect(self.sendValue)
3302
3303
    def update(self):
3304
        self.setSingleStep(1)
3305
        if self.min_value != self.max_value:
3306
            self.setEnabled(True)
3307
            self.setMinimum(int(self.min_value / self.step))
3308
            self.setMaximum(int(self.max_value / self.step))
3309
        else:
3310
            self.setEnabled(False)
3311
3312
    def sendValue(self, slider_value):
3313
        value = min(max(slider_value * self.step, self.min_value),
3314
                    self.max_value)
3315
        self.valueChangedFloat.emit(value)
3316
3317
    def setValue(self, value):
3318
        super().setValue(value // self.step)
3319
3320
    def setScale(self, minValue, maxValue, step=0):
3321
        if minValue >= maxValue:
3322
            ## It would be more logical to disable the slider in this case
3323
            ## (self.setEnabled(False))
3324
            ## However, we do nothing to keep consistency with Qwt
3325
            # TODO If it's related to Qwt, remove it
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
3326
            return
3327
        if step <= 0 or step > (maxValue - minValue):
3328
            if isinstance(maxValue, int) and isinstance(minValue, int):
3329
                step = 1
3330
            else:
3331
                step = float(minValue - maxValue) / 100.0
3332
        self.min_value = float(minValue)
3333
        self.max_value = float(maxValue)
3334
        self.step = step
3335
        self.update()
3336
3337
    def setRange(self, minValue, maxValue, step=1.0):
3338
        # For compatibility with qwtSlider
3339
        # TODO If it's related to Qwt, remove it
0 ignored issues
show
TODO and FIXME comments should generally be avoided.
Loading history...
3340
        self.setScale(minValue, maxValue, step)
3341