PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/Orange/widgets/gui.py

https://gitlab.com/zaverichintan/orange3
Python | 1343 lines | 1313 code | 17 blank | 13 comment | 15 complexity | 98003d3fc03dc6ee09a3852768c9a0e5 MD5 | raw file
  1. import math
  2. import os
  3. import re
  4. import itertools
  5. from types import LambdaType
  6. import pkg_resources
  7. import numpy
  8. from PyQt4 import QtGui, QtCore, QtWebKit
  9. from PyQt4.QtCore import Qt, pyqtSignal as Signal
  10. from PyQt4.QtGui import QCursor, QApplication
  11. import Orange.data
  12. from Orange.widgets.utils import getdeepattr
  13. from Orange.data import \
  14. ContinuousVariable, StringVariable, TimeVariable, DiscreteVariable, Variable
  15. from Orange.widgets.utils import vartype
  16. from Orange.widgets.utils.constants import CONTROLLED_ATTRIBUTES, ATTRIBUTE_CONTROLLERS
  17. from Orange.util import namegen
  18. YesNo = NoYes = ("No", "Yes")
  19. _enter_icon = None
  20. __re_label = re.compile(r"(^|[^%])%\((?P<value>[a-zA-Z]\w*)\)")
  21. OrangeUserRole = itertools.count(Qt.UserRole)
  22. LAMBDA_NAME = namegen('_lambda_')
  23. def resource_filename(path):
  24. """
  25. Return a resource filename (package data) for path.
  26. """
  27. return pkg_resources.resource_filename(__name__, path)
  28. class TableWidget(QtGui.QTableWidget):
  29. """ An easy to use, row-oriented table widget """
  30. ROW_DATA_ROLE = QtCore.Qt.UserRole + 1
  31. ITEM_DATA_ROLE = ROW_DATA_ROLE + 1
  32. class TableWidgetNumericItem(QtGui.QTableWidgetItem):
  33. """TableWidgetItem that sorts numbers correctly!"""
  34. def __lt__(self, other):
  35. return (self.data(TableWidget.ITEM_DATA_ROLE) <
  36. other.data(TableWidget.ITEM_DATA_ROLE))
  37. def selectionChanged(self, selected:[QtGui.QItemSelectionRange], deselected:[QtGui.QItemSelectionRange]):
  38. """Override or monkey-patch this method to catch selection changes"""
  39. super().selectionChanged(selected, deselected)
  40. def __setattr__(self, attr, value):
  41. """
  42. The following selectionChanged magic ensures selectionChanged
  43. slot, when monkey-patched, always calls the super's selectionChanged
  44. first (--> avoids Qt quirks), and the user needs not care about that.
  45. """
  46. if attr == 'selectionChanged':
  47. func = value
  48. @QtCore.pyqtSlot(QtGui.QItemSelection, QtGui.QItemSelection)
  49. def _f(selected, deselected):
  50. super(self.__class__, self).selectionChanged(selected, deselected)
  51. func(selected, deselected)
  52. value = _f
  53. self.__dict__[attr] = value
  54. def _update_headers(func):
  55. """Decorator to update certain table features after method calls"""
  56. def _f(self, *args, **kwargs):
  57. func(self, *args, **kwargs)
  58. if self.col_labels is not None:
  59. self.setHorizontalHeaderLabels(self.col_labels)
  60. if self.row_labels is not None:
  61. self.setVerticalHeaderLabels(self.row_labels)
  62. if self.stretch_last_section:
  63. self.horizontalHeader().setStretchLastSection(True)
  64. return _f
  65. @_update_headers
  66. def __init__(self,
  67. parent=None,
  68. col_labels=None,
  69. row_labels=None,
  70. stretch_last_section=True,
  71. multi_selection=False,
  72. select_rows=False):
  73. """
  74. Parameters
  75. ----------
  76. parent: QObject
  77. Parent QObject. If parent has layout(), this widget is added to it.
  78. col_labels: list of str
  79. Labels or [] (sequential numbers) or None (no horizontal header)
  80. row_label: list_of_str
  81. Labels or [] (sequential numbers) or None (no vertical header)
  82. stretch_last_section: bool
  83. multi_selection: bool
  84. Single selection if False
  85. select_rows: bool
  86. If True, select whole rows instead of individual cells.
  87. """
  88. super().__init__(parent)
  89. self._column_filter = {}
  90. self.col_labels = col_labels
  91. self.row_labels = row_labels
  92. self.stretch_last_section = stretch_last_section
  93. try: parent.layout().addWidget(self)
  94. except (AttributeError, TypeError): pass
  95. if col_labels is None:
  96. self.horizontalHeader().setVisible(False)
  97. if row_labels is None:
  98. self.verticalHeader().setVisible(False)
  99. if multi_selection:
  100. self.setSelectionMode(self.MultiSelection)
  101. if select_rows:
  102. self.setSelectionBehavior(self.SelectRows)
  103. self.setHorizontalScrollMode(self.ScrollPerPixel)
  104. self.setVerticalScrollMode(self.ScrollPerPixel)
  105. self.setEditTriggers(self.NoEditTriggers)
  106. self.setAlternatingRowColors(True)
  107. self.setShowGrid(False)
  108. self.setSortingEnabled(True)
  109. @_update_headers
  110. def addRow(self, items:tuple, data=None):
  111. """
  112. Appends iterable of `items` as the next row, optionally setting row
  113. data to `data`. Each item of `items` can be a string or tuple
  114. (item_name, item_data) if individual, cell-data is required.
  115. """
  116. row_data = data
  117. row = self.rowCount()
  118. self.insertRow(row)
  119. col_count = max(len(items), self.columnCount())
  120. if col_count != self.columnCount():
  121. self.setColumnCount(col_count)
  122. for col, item_data in enumerate(items):
  123. if isinstance(item_data, str):
  124. name = item_data
  125. elif hasattr(item_data, '__iter__') and len(item_data) == 2:
  126. name, item_data = item_data
  127. elif isinstance(item_data, float):
  128. name = '{:.4f}'.format(item_data)
  129. else:
  130. name = str(item_data)
  131. if isinstance(item_data, (float, int, numpy.number)):
  132. item = self.TableWidgetNumericItem(name)
  133. else:
  134. item = QtGui.QTableWidgetItem(name)
  135. item.setData(self.ITEM_DATA_ROLE, item_data)
  136. if col in self._column_filter:
  137. item = self._column_filter[col](item) or item
  138. self.setItem(row, col, item)
  139. self.resizeColumnsToContents()
  140. self.resizeRowsToContents()
  141. if row_data is not None:
  142. self.setRowData(row, row_data)
  143. def rowData(self, row:int):
  144. return self.item(row, 0).data(self.ROW_DATA_ROLE)
  145. def setRowData(self, row:int, data):
  146. self.item(row, 0).setData(self.ROW_DATA_ROLE, data)
  147. def setColumnFilter(self, item_filter_func, columns:int or list):
  148. """
  149. Pass item(s) at column(s) through `item_filter_func` before
  150. insertion. Useful for setting specific columns to bold or similar.
  151. """
  152. try: iter(columns)
  153. except TypeError: columns = [columns]
  154. for i in columns:
  155. self._column_filter[i] = item_filter_func
  156. def clear(self):
  157. super().clear()
  158. self.setRowCount(0)
  159. self.setColumnCount(0)
  160. def selectFirstRow(self):
  161. if self.rowCount() > 0:
  162. self.selectRow(0)
  163. def selectRowsWhere(self, col, value, n_hits=-1,
  164. flags=QtCore.Qt.MatchExactly, _select=True):
  165. """
  166. Select (also return) at most `n_hits` rows where column `col`
  167. has value (``data()``) `value`.
  168. """
  169. model = self.model()
  170. matches = model.match(model.index(0, col),
  171. self.ITEM_DATA_ROLE,
  172. value,
  173. n_hits,
  174. flags)
  175. model = self.selectionModel()
  176. selection_flag = model.Select if _select else model.Deselect
  177. for index in matches:
  178. if _select ^ model.isSelected(index):
  179. model.select(index, selection_flag | model.Rows)
  180. return matches
  181. def deselectRowsWhere(self, col, value, n_hits=-1,
  182. flags=QtCore.Qt.MatchExactly):
  183. """
  184. Deselect (also return) at most `n_hits` rows where column `col`
  185. has value (``data()``) `value`.
  186. """
  187. return self.selectRowsWhere(col, value, n_hits, flags, False)
  188. class WebviewWidget(QtWebKit.QWebView):
  189. """WebKit window in a window"""
  190. def __init__(self, parent=None, bridge=None, html=None, debug=None):
  191. """
  192. Parameters
  193. ----------
  194. parent: QObject
  195. Parent QObject. If parent has layout(), this widget is added to it.
  196. bridge: QObject
  197. The "bridge" object exposed as ``window.pybridge`` in JavaScript.
  198. Any bridge methods desired to be accessible from JS need to be
  199. decorated ``@QtCore.pyqtSlot(<*args>, result=<type>)``.
  200. html: str
  201. HTML content to set in the webview.
  202. debug: bool
  203. If True, enable context menu and webkit inspector.
  204. """
  205. super().__init__(parent)
  206. self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
  207. QtGui.QSizePolicy.Expanding))
  208. self._bridge = bridge
  209. try: parent.layout().addWidget(self)
  210. except (AttributeError, TypeError): pass
  211. settings = self.settings()
  212. settings.setAttribute(settings.LocalContentCanAccessFileUrls, True)
  213. if debug is None:
  214. import logging
  215. debug = logging.getLogger().level <= logging.DEBUG
  216. if debug:
  217. settings.setAttribute(settings.DeveloperExtrasEnabled, True)
  218. else:
  219. self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
  220. if html:
  221. self.setHtml(html)
  222. def setContent(self, data, mimetype, url=''):
  223. super().setContent(data, mimetype, QtCore.QUrl(url))
  224. if self._bridge:
  225. self.page().mainFrame().addToJavaScriptWindowObject('pybridge', self._bridge)
  226. def setHtml(self, html, url=''):
  227. self.setContent(html.encode('utf-8'), 'text/html', url)
  228. def sizeHint(self):
  229. return QtCore.QSize(600, 500)
  230. def evalJS(self, javascript):
  231. self.page().mainFrame().evaluateJavaScript(javascript)
  232. class ControlledAttributesDict(dict):
  233. def __init__(self, master):
  234. super().__init__()
  235. self.master = master
  236. def __setitem__(self, key, value):
  237. if key not in self:
  238. dict.__setitem__(self, key, [value])
  239. else:
  240. dict.__getitem__(self, key).append(value)
  241. set_controllers(self.master, key, self.master, "")
  242. callbacks = lambda obj: getattr(obj, CONTROLLED_ATTRIBUTES, {})
  243. subcontrollers = lambda obj: getattr(obj, ATTRIBUTE_CONTROLLERS, {})
  244. def notify_changed(obj, name, value):
  245. if name in callbacks(obj):
  246. for callback in callbacks(obj)[name]:
  247. callback(value)
  248. return
  249. for controller, prefix in list(subcontrollers(obj)):
  250. if getdeepattr(controller, prefix, None) != obj:
  251. del subcontrollers(obj)[(controller, prefix)]
  252. continue
  253. full_name = prefix + "." + name
  254. if full_name in callbacks(controller):
  255. for callback in callbacks(controller)[full_name]:
  256. callback(value)
  257. continue
  258. prefix = full_name + "."
  259. prefix_length = len(prefix)
  260. for controlled in callbacks(controller):
  261. if controlled[:prefix_length] == prefix:
  262. set_controllers(value, controlled[prefix_length:], controller, full_name)
  263. def set_controllers(obj, controlled_name, controller, prefix):
  264. while obj:
  265. if prefix:
  266. if hasattr(obj, ATTRIBUTE_CONTROLLERS):
  267. getattr(obj, ATTRIBUTE_CONTROLLERS)[(controller, prefix)] = True
  268. else:
  269. setattr(obj, ATTRIBUTE_CONTROLLERS, {(controller, prefix): True})
  270. parts = controlled_name.split(".", 1)
  271. if len(parts) < 2:
  272. break
  273. new_prefix, controlled_name = parts
  274. obj = getattr(obj, new_prefix, None)
  275. if prefix:
  276. prefix += '.'
  277. prefix += new_prefix
  278. class OWComponent:
  279. def __init__(self, widget):
  280. setattr(self, CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self))
  281. if widget.settingsHandler:
  282. widget.settingsHandler.initialize(self)
  283. def __setattr__(self, key, value):
  284. super().__setattr__(key, value)
  285. notify_changed(self, key, value)
  286. def miscellanea(control, box, parent,
  287. addToLayout=True, stretch=0, sizePolicy=None, addSpace=False,
  288. disabled=False, tooltip=None):
  289. """
  290. Helper function that sets various properties of the widget using a common
  291. set of arguments.
  292. The function
  293. - sets the `control`'s attribute `box`, if `box` is given and `control.box`
  294. is not yet set,
  295. - attaches a tool tip to the `control` if specified,
  296. - disables the `control`, if `disabled` is set to `True`,
  297. - adds the `box` to the `parent`'s layout unless `addToLayout` is set to
  298. `False`; the stretch factor can be specified,
  299. - adds the control into the box's layout if the box is given (regardless
  300. of `addToLayout`!)
  301. - sets the size policy for the box or the control, if the policy is given,
  302. - adds space in the `parent`'s layout after the `box` if `addSpace` is set
  303. and `addToLayout` is not `False`.
  304. If `box` is the same as `parent` it is set to `None`; this is convenient
  305. because of the way complex controls are inserted.
  306. :param control: the control, e.g. a `QCheckBox`
  307. :type control: PyQt4.QtGui.QWidget
  308. :param box: the box into which the widget was inserted
  309. :type box: PyQt4.QtGui.QWidget or None
  310. :param parent: the parent into whose layout the box or the control will be
  311. inserted
  312. :type parent: PyQt4.QtGui.QWidget
  313. :param addSpace: the amount of space to add after the widget
  314. :type addSpace: bool or int
  315. :param disabled: If set to `True`, the widget is initially disabled
  316. :type disabled: bool
  317. :param addToLayout: If set to `False` the widget is not added to the layout
  318. :type addToLayout: bool
  319. :param stretch: the stretch factor for this widget, used when adding to
  320. the layout (default: 0)
  321. :type stretch: int
  322. :param tooltip: tooltip that is attached to the widget
  323. :type tooltip: str or None
  324. :param sizePolicy: the size policy for the box or the control
  325. :type sizePolicy: PyQt4.QtQui.QSizePolicy
  326. """
  327. if disabled:
  328. # if disabled==False, do nothing; it can be already disabled
  329. control.setDisabled(disabled)
  330. if tooltip is not None:
  331. control.setToolTip(tooltip)
  332. if box is parent:
  333. box = None
  334. elif box and box is not control and not hasattr(control, "box"):
  335. control.box = box
  336. if box and box.layout() is not None and \
  337. isinstance(control, QtGui.QWidget) and \
  338. box.layout().indexOf(control) == -1:
  339. box.layout().addWidget(control)
  340. if sizePolicy is not None:
  341. (box or control).setSizePolicy(sizePolicy)
  342. if addToLayout and parent and parent.layout() is not None:
  343. parent.layout().addWidget(box or control, stretch)
  344. _addSpace(parent, addSpace)
  345. def setLayout(widget, orientation):
  346. """
  347. Set the layout of the widget according to orientation. Argument
  348. `orientation` can be an instance of :obj:`~PyQt4.QtGui.QLayout`, in which
  349. case is it used as it is. If `orientation` is `'vertical'` or `True`,
  350. the layout is set to :obj:`~PyQt4.QtGui.QVBoxLayout`. If it is
  351. `'horizontal'` or `False`, it is set to :obj:`~PyQt4.QtGui.QVBoxLayout`.
  352. :param widget: the widget for which the layout is being set
  353. :type widget: PyQt4.QtGui.QWidget
  354. :param orientation: orientation for the layout
  355. :type orientation: str or bool or PyQt4.QtGui.QLayout
  356. """
  357. if isinstance(orientation, QtGui.QLayout):
  358. widget.setLayout(orientation)
  359. elif orientation == 'horizontal' or not orientation:
  360. widget.setLayout(QtGui.QHBoxLayout())
  361. else:
  362. widget.setLayout(QtGui.QVBoxLayout())
  363. def _enterButton(parent, control, placeholder=True):
  364. """
  365. Utility function that returns a button with a symbol for "Enter" and
  366. optionally a placeholder to show when the enter button is hidden. Both
  367. are inserted into the parent's layout, if it has one. If placeholder is
  368. constructed it is shown and the button is hidden.
  369. The height of the button is the same as the height of the widget passed
  370. as argument `control`.
  371. :param parent: parent widget into which the button is inserted
  372. :type parent: PyQt4.QtGui.QWidget
  373. :param control: a widget for determining the height of the button
  374. :type control: PyQt4.QtGui.QWidget
  375. :param placeholder: a flag telling whether to construct a placeholder
  376. (default: True)
  377. :type placeholder: bool
  378. :return: a tuple with a button and a place holder (or `None`)
  379. :rtype: PyQt4.QtGui.QToolButton or tuple
  380. """
  381. global _enter_icon
  382. if not _enter_icon:
  383. _enter_icon = QtGui.QIcon(
  384. os.path.dirname(__file__) + "/icons/Dlg_enter.png")
  385. button = QtGui.QPushButton(parent)
  386. button.setAutoDefault(True)
  387. button.setDefault(True)
  388. height = control.sizeHint().height()
  389. button.setFixedSize(height, height)
  390. button.setIcon(_enter_icon)
  391. if parent.layout() is not None:
  392. parent.layout().addWidget(button)
  393. if placeholder:
  394. button.hide()
  395. holder = QtGui.QWidget(parent)
  396. holder.setFixedSize(height, height)
  397. if parent.layout() is not None:
  398. parent.layout().addWidget(holder)
  399. else:
  400. holder = None
  401. return button, holder
  402. def _addSpace(widget, space):
  403. """
  404. A helper function that adds space into the widget, if requested.
  405. The function is called by functions that have the `addSpace` argument.
  406. :param widget: Widget into which to insert the space
  407. :type widget: PyQt4.QtGui.QWidget
  408. :param space: Amount of space to insert. If False, the function does
  409. nothing. If the argument is an `int`, the specified space is inserted.
  410. Otherwise, the default space is inserted by calling a :obj:`separator`.
  411. :type space: bool or int
  412. """
  413. if space:
  414. if type(space) == int: # distinguish between int and bool!
  415. separator(widget, space, space)
  416. else:
  417. separator(widget)
  418. def separator(widget, width=4, height=4):
  419. """
  420. Add a separator of the given size into the widget.
  421. :param widget: the widget into whose layout the separator is added
  422. :type widget: PyQt4.QtGui.QWidget
  423. :param width: width of the separator
  424. :type width: int
  425. :param height: height of the separator
  426. :type height: int
  427. :return: separator
  428. :rtype: PyQt4.QtGui.QWidget
  429. """
  430. sep = QtGui.QWidget(widget)
  431. if widget.layout() is not None:
  432. widget.layout().addWidget(sep)
  433. sep.setFixedSize(width, height)
  434. return sep
  435. def rubber(widget):
  436. """
  437. Insert a stretch 100 into the widget's layout
  438. """
  439. widget.layout().addStretch(100)
  440. def widgetBox(widget, box=None, orientation='vertical', margin=None, spacing=4,
  441. **misc):
  442. """
  443. Construct a box with vertical or horizontal layout, and optionally,
  444. a border with an optional label.
  445. If the widget has a frame, the space after the widget is added unless
  446. explicitly disabled.
  447. :param widget: the widget into which the box is inserted
  448. :type widget: PyQt4.QtGui.QWidget or None
  449. :param box: tells whether the widget has a border, and its label
  450. :type box: int or str or None
  451. :param orientation: orientation for the layout. If the argument is an
  452. instance of :obj:`~PyQt4.QtGui.QLayout`, it is used as a layout. If
  453. "horizontal" or false-ish, the layout is horizontal
  454. (:obj:`~PyQt4.QtGui.QHBoxLayout`), otherwise vertical
  455. (:obj:`~PyQt4.QtGui.QHBoxLayout`).
  456. :type orientation: str, int or :obj:`PyQt4.QtGui.QLayout`
  457. :param sizePolicy: The size policy for the widget (default: None)
  458. :type sizePolicy: :obj:`~PyQt4.QtGui.QSizePolicy`
  459. :param margin: The margin for the layout. Default is 7 if the widget has
  460. a border, and 0 if not.
  461. :type margin: int
  462. :param spacing: Spacing within the layout (default: 4)
  463. :type spacing: int
  464. :return: Constructed box
  465. :rtype: PyQt4.QtGui.QGroupBox or PyQt4.QtGui.QWidget
  466. """
  467. if box:
  468. b = QtGui.QGroupBox(widget)
  469. if isinstance(box, str):
  470. b.setTitle(" " + box.strip() + " ")
  471. if margin is None:
  472. margin = 7
  473. else:
  474. b = QtGui.QWidget(widget)
  475. b.setContentsMargins(0, 0, 0, 0)
  476. if margin is None:
  477. margin = 0
  478. setLayout(b, orientation)
  479. b.layout().setSpacing(spacing)
  480. b.layout().setMargin(margin)
  481. misc.setdefault('addSpace', bool(box))
  482. miscellanea(b, None, widget, **misc)
  483. return b
  484. def hBox(*args, **kwargs):
  485. return widgetBox(orientation="horizontal", *args, **kwargs)
  486. def vBox(*args, **kwargs):
  487. return widgetBox(orientation="vertical", *args, **kwargs)
  488. def indentedBox(widget, sep=20, orientation="vertical", **misc):
  489. """
  490. Creates an indented box. The function can also be used "on the fly"::
  491. gui.checkBox(gui.indentedBox(box), self, "spam", "Enable spam")
  492. To align the control with a check box, use :obj:`checkButtonOffsetHint`::
  493. gui.hSlider(gui.indentedBox(self.interBox), self, "intervals")
  494. :param widget: the widget into which the box is inserted
  495. :type widget: PyQt4.QtGui.QWidget
  496. :param sep: Indent size (default: 20)
  497. :type sep: int
  498. :param orientation: layout of the inserted box; see :obj:`widgetBox` for
  499. details
  500. :type orientation: str, int or PyQt4.QtGui.QLayout
  501. :return: Constructed box
  502. :rtype: PyQt4.QtGui.QGroupBox or PyQt4.QtGui.QWidget
  503. """
  504. outer = widgetBox(widget, orientation=False, spacing=0)
  505. separator(outer, sep, 0)
  506. indented = widgetBox(outer, orientation=orientation)
  507. miscellanea(indented, outer, widget, **misc)
  508. return indented
  509. def widgetLabel(widget, label="", labelWidth=None, **misc):
  510. """
  511. Construct a simple, constant label.
  512. :param widget: the widget into which the box is inserted
  513. :type widget: PyQt4.QtGui.QWidget or None
  514. :param label: The text of the label (default: None)
  515. :type label: str
  516. :param labelWidth: The width of the label (default: None)
  517. :type labelWidth: int
  518. :return: Constructed label
  519. :rtype: PyQt4.QtGui.QLabel
  520. """
  521. lbl = QtGui.QLabel(label, widget)
  522. if labelWidth:
  523. lbl.setFixedSize(labelWidth, lbl.sizeHint().height())
  524. miscellanea(lbl, None, widget, **misc)
  525. return lbl
  526. def label(widget, master, label, labelWidth=None, box=None,
  527. orientation="vertical", **misc):
  528. """
  529. Construct a label that contains references to the master widget's
  530. attributes; when their values change, the label is updated.
  531. Argument :obj:`label` is a format string following Python's syntax
  532. (see the corresponding Python documentation): the label's content is
  533. rendered as `label % master.__dict__`. For instance, if the
  534. :obj:`label` is given as "There are %(mm)i monkeys", the value of
  535. `master.mm` (which must be an integer) will be inserted in place of
  536. `%(mm)i`.
  537. :param widget: the widget into which the box is inserted
  538. :type widget: PyQt4.QtGui.QWidget or None
  539. :param master: master widget
  540. :type master: OWWidget or OWComponent
  541. :param label: The text of the label, including attribute names
  542. :type label: str
  543. :param labelWidth: The width of the label (default: None)
  544. :type labelWidth: int
  545. :return: label
  546. :rtype: PyQt4.QtGui.QLabel
  547. """
  548. if box:
  549. b = widgetBox(widget, box, orientation=None, addToLayout=False)
  550. else:
  551. b = widget
  552. lbl = QtGui.QLabel("", b)
  553. reprint = CallFrontLabel(lbl, label, master)
  554. for mo in __re_label.finditer(label):
  555. getattr(master, CONTROLLED_ATTRIBUTES)[mo.group("value")] = reprint
  556. reprint()
  557. if labelWidth:
  558. lbl.setFixedSize(labelWidth, lbl.sizeHint().height())
  559. miscellanea(lbl, b, widget, **misc)
  560. return lbl
  561. class SpinBoxWFocusOut(QtGui.QSpinBox):
  562. """
  563. A class derived from QtGui.QSpinBox, which postpones the synchronization
  564. of the control's value with the master's attribute until the user presses
  565. Enter or clicks an icon that appears beside the spin box when the value
  566. is changed.
  567. The class overloads :obj:`onChange` event handler to show the commit button,
  568. and :obj:`onEnter` to commit the change when enter is pressed.
  569. .. attribute:: enterButton
  570. A widget (usually an icon) that is shown when the value is changed.
  571. .. attribute:: placeHolder
  572. A placeholder which is shown when the button is hidden
  573. .. attribute:: inSetValue
  574. A flag that is set when the value is being changed through
  575. :obj:`setValue` to prevent the programmatic changes from showing the
  576. commit button.
  577. """
  578. def __init__(self, minv, maxv, step, parent=None):
  579. """
  580. Construct the object and set the range (`minv`, `maxv`) and the step.
  581. :param minv: Minimal value
  582. :type minv: int
  583. :param maxv: Maximal value
  584. :type maxv: int
  585. :param step: Step
  586. :type step: int
  587. :param parent: Parent widget
  588. :type parent: PyQt4.QtGui.QWidget
  589. """
  590. super().__init__(parent)
  591. self.setRange(minv, maxv)
  592. self.setSingleStep(step)
  593. self.inSetValue = False
  594. self.enterButton = None
  595. self.placeHolder = None
  596. def onChange(self, _):
  597. """
  598. Hides the place holder and shows the commit button unless
  599. :obj:`inSetValue` is set.
  600. """
  601. if not self.inSetValue:
  602. self.placeHolder.hide()
  603. self.enterButton.show()
  604. def onEnter(self):
  605. """
  606. If the commit button is visible, the overload event handler commits
  607. the change by calling the appropriate callbacks. It also hides the
  608. commit button and shows the placeHolder.
  609. """
  610. if self.enterButton.isVisible():
  611. self.enterButton.hide()
  612. self.placeHolder.show()
  613. if self.cback:
  614. self.cback(int(str(self.text())))
  615. if self.cfunc:
  616. self.cfunc()
  617. # doesn't work: it's probably LineEdit's focusOut that we should
  618. # (but can't) catch
  619. def focusOutEvent(self, *e):
  620. """
  621. This handler was intended to catch the focus out event and reintepret
  622. it as if enter was pressed. It does not work, though.
  623. """
  624. super().focusOutEvent(*e)
  625. if self.enterButton and self.enterButton.isVisible():
  626. self.onEnter()
  627. def setValue(self, value):
  628. """
  629. Set the :obj:`inSetValue` flag and call the inherited method.
  630. """
  631. self.inSetValue = True
  632. super().setValue(value)
  633. self.inSetValue = False
  634. class DoubleSpinBoxWFocusOut(QtGui.QDoubleSpinBox):
  635. """
  636. Same as :obj:`SpinBoxWFocusOut`, except that it is derived from
  637. :obj:`~PyQt4.QtGui.QDoubleSpinBox`"""
  638. def __init__(self, minv, maxv, step, parent):
  639. super().__init__(parent)
  640. self.setDecimals(math.ceil(-math.log10(step)))
  641. self.setRange(minv, maxv)
  642. self.setSingleStep(step)
  643. self.inSetValue = False
  644. self.enterButton = None
  645. self.placeHolder = None
  646. def onChange(self, _):
  647. if not self.inSetValue:
  648. self.placeHolder.hide()
  649. self.enterButton.show()
  650. def onEnter(self):
  651. if self.enterButton.isVisible():
  652. self.enterButton.hide()
  653. self.placeHolder.show()
  654. if self.cback:
  655. self.cback(float(str(self.text()).replace(",", ".")))
  656. if self.cfunc:
  657. self.cfunc()
  658. # doesn't work: it's probably LineEdit's focusOut that we should
  659. # (and can't) catch
  660. def focusOutEvent(self, *e):
  661. super().focusOutEvent(*e)
  662. if self.enterButton and self.enterButton.isVisible():
  663. self.onEnter()
  664. def setValue(self, value):
  665. self.inSetValue = True
  666. super().setValue(value)
  667. self.inSetValue = False
  668. def spin(widget, master, value, minv, maxv, step=1, box=None, label=None,
  669. labelWidth=None, orientation=None, callback=None,
  670. controlWidth=None, callbackOnReturn=False, checked=None,
  671. checkCallback=None, posttext=None, disabled=False,
  672. alignment=Qt.AlignLeft, keyboardTracking=True,
  673. decimals=None, spinType=int, **misc):
  674. """
  675. A spinbox with lots of bells and whistles, such as a checkbox and various
  676. callbacks. It constructs a control of type :obj:`SpinBoxWFocusOut` or
  677. :obj:`DoubleSpinBoxWFocusOut`.
  678. :param widget: the widget into which the box is inserted
  679. :type widget: PyQt4.QtGui.QWidget or None
  680. :param master: master widget
  681. :type master: OWWidget or OWComponent
  682. :param value: the master's attribute with which the value is synchronized
  683. :type value: str
  684. :param minv: minimal value
  685. :type minv: int
  686. :param maxv: maximal value
  687. :type maxv: int
  688. :param step: step (default: 1)
  689. :type step: int
  690. :param box: tells whether the widget has a border, and its label
  691. :type box: int or str or None
  692. :param label: label that is put in above or to the left of the spin box
  693. :type label: str
  694. :param labelWidth: optional label width (default: None)
  695. :type labelWidth: int
  696. :param orientation: tells whether to put the label above (`"vertical"` or
  697. `True`) or to the left (`"horizontal"` or `False`)
  698. :type orientation: int or bool or str
  699. :param callback: a function that is called when the value is entered; if
  700. :obj:`callbackOnReturn` is `True`, the function is called when the
  701. user commits the value by pressing Enter or clicking the icon
  702. :type callback: function
  703. :param controlWidth: the width of the spin box
  704. :type controlWidth: int
  705. :param callbackOnReturn: if `True`, the spin box has an associated icon
  706. that must be clicked to confirm the value (default: False)
  707. :type callbackOnReturn: bool
  708. :param checked: if not None, a check box is put in front of the spin box;
  709. when unchecked, the spin box is disabled. Argument `checked` gives the
  710. name of the master's attribute given whose value is synchronized with
  711. the check box's state (default: None).
  712. :type checked: str
  713. :param checkCallback: a callback function that is called when the check
  714. box's state is changed
  715. :type checkCallback: function
  716. :param posttext: a text that is put to the right of the spin box
  717. :type posttext: str
  718. :param alignment: alignment of the spin box (e.g. `QtCore.Qt.AlignLeft`)
  719. :type alignment: PyQt4.QtCore.Qt.Alignment
  720. :param keyboardTracking: If `True`, the valueChanged signal is emitted
  721. when the user is typing (default: True)
  722. :type keyboardTracking: bool
  723. :param spinType: determines whether to use QSpinBox (int) or
  724. QDoubleSpinBox (float)
  725. :type spinType: type
  726. :param decimals: number of decimals (if `spinType` is `float`)
  727. :type decimals: int
  728. :return: Tuple `(spin box, check box) if `checked` is `True`, otherwise
  729. the spin box
  730. :rtype: tuple or gui.SpinBoxWFocusOut
  731. """
  732. # b is the outermost box or the widget if there are no boxes;
  733. # b is the widget that is inserted into the layout
  734. # bi is the box that contains the control or the checkbox and the control;
  735. # bi can be the widget itself, if there are no boxes
  736. # cbox is the checkbox (or None)
  737. # sbox is the spinbox itself
  738. if box or label and not checked:
  739. b = widgetBox(widget, box, orientation, addToLayout=False)
  740. hasHBox = orientation == 'horizontal' or not orientation
  741. else:
  742. b = widget
  743. hasHBox = False
  744. if not hasHBox and (checked or callback and callbackOnReturn or posttext):
  745. bi = widgetBox(b, orientation=0, addToLayout=False)
  746. else:
  747. bi = b
  748. cbox = None
  749. if checked is not None:
  750. cbox = checkBox(bi, master, checked, label, labelWidth=labelWidth,
  751. callback=checkCallback)
  752. elif label:
  753. b.label = widgetLabel(b, label, labelWidth)
  754. if posttext:
  755. widgetLabel(bi, posttext)
  756. isDouble = spinType == float
  757. sbox = bi.control = \
  758. (SpinBoxWFocusOut, DoubleSpinBoxWFocusOut)[isDouble](minv, maxv,
  759. step, bi)
  760. if bi is not widget:
  761. bi.setDisabled(disabled)
  762. else:
  763. sbox.setDisabled(disabled)
  764. if decimals is not None:
  765. sbox.setDecimals(decimals)
  766. sbox.setAlignment(alignment)
  767. sbox.setKeyboardTracking(keyboardTracking)
  768. if controlWidth:
  769. sbox.setFixedWidth(controlWidth)
  770. if value:
  771. sbox.setValue(getdeepattr(master, value))
  772. cfront, sbox.cback, sbox.cfunc = connectControl(
  773. master, value, callback,
  774. not (callback and callbackOnReturn) and
  775. sbox.valueChanged[(int, float)[isDouble]],
  776. (CallFrontSpin, CallFrontDoubleSpin)[isDouble](sbox))
  777. if checked:
  778. cbox.disables = [sbox]
  779. cbox.makeConsistent()
  780. if callback and callbackOnReturn:
  781. sbox.enterButton, sbox.placeHolder = _enterButton(bi, sbox)
  782. sbox.valueChanged[str].connect(sbox.onChange)
  783. sbox.editingFinished.connect(sbox.onEnter)
  784. sbox.enterButton.clicked.connect(sbox.onEnter)
  785. if hasattr(sbox, "upButton"):
  786. sbox.upButton().clicked.connect(
  787. lambda c=sbox.editor(): c.setFocus())
  788. sbox.downButton().clicked.connect(
  789. lambda c=sbox.editor(): c.setFocus())
  790. miscellanea(sbox, b if b is not widget else bi, widget, **misc)
  791. if checked:
  792. if isDouble and b == widget:
  793. # TODO Backward compatilibity; try to find and eliminate
  794. sbox.control = b.control
  795. return sbox
  796. return cbox, sbox
  797. else:
  798. return sbox
  799. # noinspection PyTypeChecker
  800. def doubleSpin(widget, master, value, minv, maxv, step=1, box=None, label=None,
  801. labelWidth=None, orientation=None, callback=None,
  802. controlWidth=None, callbackOnReturn=False, checked=None,
  803. checkCallback=None, posttext=None,
  804. alignment=Qt.AlignLeft, keyboardTracking=True,
  805. decimals=None, **misc):
  806. """
  807. Backward compatilibity function: calls :obj:`spin` with `spinType=float`.
  808. """
  809. return spin(widget, master, value, minv, maxv, step, box=box, label=label,
  810. labelWidth=labelWidth, orientation=orientation,
  811. callback=callback, controlWidth=controlWidth,
  812. callbackOnReturn=callbackOnReturn, checked=checked,
  813. checkCallback=checkCallback, posttext=posttext,
  814. alignment=alignment, keyboardTracking=keyboardTracking,
  815. decimals=decimals, spinType=float, **misc)
  816. def checkBox(widget, master, value, label, box=None,
  817. callback=None, getwidget=False, id_=None, labelWidth=None,
  818. disables=None, **misc):
  819. """
  820. A simple checkbox.
  821. :param widget: the widget into which the box is inserted
  822. :type widget: PyQt4.QtGui.QWidget or None
  823. :param master: master widget
  824. :type master: OWWidget or OWComponent
  825. :param value: the master's attribute with which the value is synchronized
  826. :type value: str
  827. :param label: label
  828. :type label: str
  829. :param box: tells whether the widget has a border, and its label
  830. :type box: int or str or None
  831. :param callback: a function that is called when the check box state is
  832. changed
  833. :type callback: function
  834. :param getwidget: If set `True`, the callback function will get a keyword
  835. argument `widget` referencing the check box
  836. :type getwidget: bool
  837. :param id_: If present, the callback function will get a keyword argument
  838. `id` with this value
  839. :type id_: any
  840. :param labelWidth: the width of the label
  841. :type labelWidth: int
  842. :param disables: a list of widgets that are disabled if the check box is
  843. unchecked
  844. :type disables: list or PyQt4.QtGui.QWidget or None
  845. :return: constructed check box; if is is placed within a box, the box is
  846. return in the attribute `box`
  847. :rtype: PyQt4.QtGui.QCheckBox
  848. """
  849. if box:
  850. b = widgetBox(widget, box, orientation=None, addToLayout=False)
  851. else:
  852. b = widget
  853. cbox = QtGui.QCheckBox(label, b)
  854. if labelWidth:
  855. cbox.setFixedSize(labelWidth, cbox.sizeHint().height())
  856. cbox.setChecked(getdeepattr(master, value))
  857. connectControl(master, value, None, cbox.toggled[bool],
  858. CallFrontCheckBox(cbox),
  859. cfunc=callback and FunctionCallback(
  860. master, callback, widget=cbox, getwidget=getwidget,
  861. id=id_))
  862. if isinstance(disables, QtGui.QWidget):
  863. disables = [disables]
  864. cbox.disables = disables or []
  865. cbox.makeConsistent = Disabler(cbox, master, value)
  866. cbox.toggled[bool].connect(cbox.makeConsistent)
  867. cbox.makeConsistent(value)
  868. miscellanea(cbox, b, widget, **misc)
  869. return cbox
  870. class LineEditWFocusOut(QtGui.QLineEdit):
  871. """
  872. A class derived from QtGui.QLineEdit, which postpones the synchronization
  873. of the control's value with the master's attribute until the user leaves
  874. the line edit, presses Enter or clicks an icon that appears beside the
  875. line edit when the value is changed.
  876. The class also allows specifying a callback function for focus-in event.
  877. .. attribute:: enterButton
  878. A widget (usually an icon) that is shown when the value is changed.
  879. .. attribute:: placeHolder
  880. A placeholder which is shown when the button is hidden
  881. .. attribute:: inSetValue
  882. A flag that is set when the value is being changed through
  883. :obj:`setValue` to prevent the programmatic changes from showing the
  884. commit button.
  885. .. attribute:: callback
  886. Callback that is called when the change is confirmed
  887. .. attribute:: focusInCallback
  888. Callback that is called on the focus-in event
  889. """
  890. def __init__(self, parent, callback, focusInCallback=None,
  891. placeholder=False):
  892. super().__init__(parent)
  893. if parent.layout() is not None:
  894. parent.layout().addWidget(self)
  895. self.callback = callback
  896. self.focusInCallback = focusInCallback
  897. self.enterButton, self.placeHolder = \
  898. _enterButton(parent, self, placeholder)
  899. self.enterButton.clicked.connect(self.returnPressedHandler)
  900. self.textChanged[str].connect(self.markChanged)
  901. self.returnPressed.connect(self.returnPressedHandler)
  902. def markChanged(self, *_):
  903. if self.placeHolder:
  904. self.placeHolder.hide()
  905. self.enterButton.show()
  906. def markUnchanged(self, *_):
  907. self.enterButton.hide()
  908. if self.placeHolder:
  909. self.placeHolder.show()
  910. def returnPressedHandler(self):
  911. if self.enterButton.isVisible():
  912. self.markUnchanged()
  913. if hasattr(self, "cback") and self.cback:
  914. self.cback(self.text())
  915. if self.callback:
  916. self.callback()
  917. def setText(self, t):
  918. super().setText(t)
  919. if self.enterButton:
  920. self.markUnchanged()
  921. def focusOutEvent(self, *e):
  922. super().focusOutEvent(*e)
  923. self.returnPressedHandler()
  924. def focusInEvent(self, *e):
  925. if self.focusInCallback:
  926. self.focusInCallback()
  927. return super().focusInEvent(*e)
  928. def lineEdit(widget, master, value, label=None, labelWidth=None,
  929. orientation='vertical', box=None, callback=None,
  930. valueType=str, validator=None, controlWidth=None,
  931. callbackOnType=False, focusInCallback=None,
  932. enterPlaceholder=False, **misc):
  933. """
  934. Insert a line edit.
  935. :param widget: the widget into which the box is inserted
  936. :type widget: PyQt4.QtGui.QWidget or None
  937. :param master: master widget
  938. :type master: OWWidget or OWComponent
  939. :param value: the master's attribute with which the value is synchronized
  940. :type value: str
  941. :param label: label
  942. :type label: str
  943. :param labelWidth: the width of the label
  944. :type labelWidth: int
  945. :param orientation: tells whether to put the label above (`"vertical"` or
  946. `True`) or to the left (`"horizontal"` or `False`)
  947. :type orientation: int or bool or str
  948. :param box: tells whether the widget has a border, and its label
  949. :type box: int or str or None
  950. :param callback: a function that is called when the check box state is
  951. changed
  952. :type callback: function
  953. :param valueType: the type into which the entered string is converted
  954. when synchronizing to `value`
  955. :type valueType: type
  956. :param validator: the validator for the input
  957. :type validator: PyQt4.QtGui.QValidator
  958. :param controlWidth: the width of the line edit
  959. :type controlWidth: int
  960. :param callbackOnType: if set to `True`, the callback is called at each
  961. key press (default: `False`)
  962. :type callbackOnType: bool
  963. :param focusInCallback: a function that is called when the line edit
  964. receives focus
  965. :type focusInCallback: function
  966. :param enterPlaceholder: if set to `True`, space of appropriate width is
  967. left empty to the right for the icon that shows that the value is
  968. changed but has not been committed yet
  969. :type enterPlaceholder: bool
  970. :rtype: PyQt4.QtGui.QLineEdit or a box
  971. """
  972. if box or label:
  973. b = widgetBox(widget, box, orientation, addToLayout=False)
  974. if label is not None:
  975. widgetLabel(b, label, labelWidth)
  976. hasHBox = orientation == 'horizontal' or not orientation
  977. else:
  978. b = widget
  979. hasHBox = False
  980. baseClass = misc.pop("baseClass", None)
  981. if baseClass:
  982. ledit = baseClass(b)
  983. ledit.enterButton = None
  984. if b is not widget:
  985. b.layout().addWidget(ledit)
  986. elif focusInCallback or callback and not callbackOnType:
  987. if not hasHBox:
  988. outer = widgetBox(b, "", 0, addToLayout=(b is not widget))
  989. else:
  990. outer = b
  991. ledit = LineEditWFocusOut(outer, callback, focusInCallback,
  992. enterPlaceholder)
  993. else:
  994. ledit = QtGui.QLineEdit(b)
  995. ledit.enterButton = None
  996. if b is not widget:
  997. b.layout().addWidget(ledit)
  998. if value:
  999. ledit.setText(str(getdeepattr(master, value)))
  1000. if controlWidth:
  1001. ledit.setFixedWidth(controlWidth)
  1002. if validator:
  1003. ledit.setValidator(validator)
  1004. if value:
  1005. ledit.cback = connectControl(
  1006. master, value,
  1007. callbackOnType and callback, ledit.textChanged[str],
  1008. CallFrontLineEdit(ledit), fvcb=value and valueType)[1]
  1009. miscellanea(ledit, b, widget, **misc)
  1010. return ledit
  1011. def button(widget, master, label, callback=None, width=None, height=None,
  1012. toggleButton=False, value="", default=False, autoDefault=True,
  1013. buttonType=QtGui.QPushButton, **misc):
  1014. """
  1015. Insert a button (QPushButton, by default)
  1016. :param widget: the widget into which the button is inserted
  1017. :type widget: PyQt4.QtGui.QWidget or None
  1018. :param master: master widget
  1019. :type master: OWWidget or OWComponent
  1020. :param label: label
  1021. :type label: str
  1022. :param callback: a function that is called when the button is pressed
  1023. :type callback: function
  1024. :param width: the width of the button
  1025. :type width: int
  1026. :param height: the height of the button
  1027. :type height: int
  1028. :param toggleButton: if set to `True`, the button is checkable, but it is
  1029. not synchronized with any attribute unless the `value` is given
  1030. :type toggleButton: bool
  1031. :param value: the master's attribute with which the value is synchronized
  1032. (the argument is optional; if present, it makes the button "checkable",
  1033. even if `toggleButton` is not set)
  1034. :type value: str
  1035. :param default: if `True` it makes the button the default button; this is
  1036. the button that is activated when the user presses Enter unless some
  1037. auto default button has current focus
  1038. :type default: bool
  1039. :param autoDefault: all buttons are auto default: they are activated if
  1040. they have focus (or are the next in the focus chain) when the user
  1041. presses enter. By setting `autoDefault` to `False`, the button is not
  1042. activated on pressing Return.
  1043. :type autoDefault: bool
  1044. :param buttonType: the button type (default: `QPushButton`)
  1045. :type buttonType: PyQt4.QtGui.QAbstractButton
  1046. :rtype: PyQt4.QtGui.QAbstractButton
  1047. """
  1048. button = buttonType(widget)
  1049. if label:
  1050. button.setText(label)
  1051. if width:
  1052. button.setFixedWidth(width)
  1053. if height:
  1054. button.setFixedHeight(height)
  1055. if toggleButton or value:
  1056. button.setCheckable(True)
  1057. if buttonType == QtGui.QPushButton:
  1058. button.setDefault(default)
  1059. button.setAutoDefault(autoDefault)
  1060. if value:
  1061. button.setChecked(getdeepattr(master, value))
  1062. connectControl(
  1063. master, value, None, button.toggled[bool],
  1064. CallFrontButton(button),
  1065. cfunc=callback and FunctionCallback(master, callback,
  1066. widget=button))
  1067. elif callback:
  1068. button.clicked.connect(callback)
  1069. miscellanea(button, None, widget, **misc)
  1070. return button
  1071. def toolButton(widget, master, label="", callback=None,
  1072. width=None, height=None, tooltip=None):
  1073. """
  1074. Insert a tool button. Calls :obj:`button`
  1075. :param widget: the widget into which the button is inserted
  1076. :type widget: PyQt4.QtGui.QWidget or None
  1077. :param master: master widget
  1078. :type master: OWWidget or OWComponent
  1079. :param label: label
  1080. :type label: str
  1081. :param callback: a function that is called when the button is pressed
  1082. :type callback: function
  1083. :param width: the width of the button
  1084. :type width: int
  1085. :param height: the height of the button
  1086. :type height: int
  1087. :rtype: PyQt4.QtGui.QToolButton
  1088. """
  1089. return button(widget, master, label, callback, width, height,
  1090. buttonType=QtGui.QToolButton, tooltip=tooltip)
  1091. def createAttributePixmap(char, background=Qt.black, color=Qt.white):
  1092. """
  1093. Create a QIcon with a given character. The icon is 13 pixels high and wide.
  1094. :param char: The character that is printed in the icon
  1095. :type char: str
  1096. :param background: the background color (default: black)
  1097. :type background: PyQt4.QtGui.QColor
  1098. :param color: the character color (default: white)
  1099. :type color: PyQt4.QtGui.QColor
  1100. :rtype: PyQt4.QtGui.QIcon
  1101. """
  1102. pixmap = QtGui.QPixmap(13, 13)
  1103. pixmap.fill(QtGui.QColor(0, 0, 0, 0))
  1104. painter = QtGui.QPainter()
  1105. painter.begin(pixmap)
  1106. painter.setRenderHints(painter.Antialiasing | painter.TextAntialiasing |
  1107. painter.SmoothPixmapTransform)
  1108. painter.setPen(background)
  1109. painter.setBrush(background)
  1110. rect = QtCore.QRectF(0, 0, 13, 13)
  1111. painter.drawRoundedRect(rect, 4, 4)
  1112. painter.setPen(color)
  1113. painter.drawText(2, 11, char)
  1114. painter.end()
  1115. return QtGui.QIcon(pixmap)
  1116. class __AttributeIconDict(dict):
  1117. def __getitem__(self, key):
  1118. if not self:
  1119. for tpe, char, col in ((vartype(ContinuousVariable()),
  1120. "C", (202, 0, 32)),
  1121. (vartype(DiscreteVariable()),
  1122. "D", (26, 150, 65)),
  1123. (vartype(StringVariable()),
  1124. "S", (0, 0, 0)),
  1125. (vartype(TimeVariable()),
  1126. "T", (68, 170, 255)),
  1127. (-1, "?", (128, 128, 128))):
  1128. self[tpe] = createAttributePixmap(char, QtGui.QColor(*col))
  1129. if key not in self:
  1130. key = vartype(key) if isinstance(key, Variable) else -1
  1131. return super().__getitem__(key)
  1132. #: A dict that returns icons for different attribute types. The dict is
  1133. #: constructed on first use since icons cannot be created before initializing
  1134. #: the application.
  1135. #:
  1136. #: Accepted keys are variable type codes and instances
  1137. #: of :obj:`Orange.data.variable`: `attributeIconDict[var]` will give the
  1138. #: appropriate icon for variable `var` or a question mark if the type is not
  1139. #: recognized
  1140. attributeIconDict = __AttributeIconDict()
  1141. def attributeItem(var):
  1142. """
  1143. Construct a pair (icon, name) for inserting a variable into a combo or
  1144. list box
  1145. :param var: variable
  1146. :type var: Orange.data.Variable
  1147. :rtype: tuple with PyQt4.QtGui.QIcon and str
  1148. """
  1149. return attributeIconDict[var], var.name
  1150. def listBox(widget, master, value=None, labels=None, box=None, callback=None,
  1151. selectionMode=QtGui.QListWidget.SingleSelection,
  1152. enableDragDrop=False, dragDropCallback=None,
  1153. dataValidityCallback=None, sizeHint=None, **misc):
  1154. """
  1155. Insert a list box.
  1156. The value with which the box's value synchronizes (`master.<value>`)
  1157. is a list of indices of selected items.
  1158. :param widget: the widget into which the box is inserted
  1159. :type widget: PyQt4.QtGui.QWidget or None
  1160. :param master: master widget
  1161. :type master: OWWidget or OWComponent
  1162. :param value: the name of the master's attribute with which the value is
  1163. synchronized (list of ints - indices of selected items)
  1164. :type value: str
  1165. :param labels: the name of the master's attribute with the list of items
  1166. (as strings or tuples with icon and string)
  1167. :type labels: str
  1168. :param box: tells whether the widget has a border, and its label
  1169. :type box: int or str or None
  1170. :param callback: a f