From 6d1b8a25117096839eab27548a6a33af62717b87 Mon Sep 17 00:00:00 2001 From: PrimozGodec Date: Fri, 5 May 2023 10:36:49 +0200 Subject: [PATCH 01/62] Bump to version 4.22.0-dev0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d07c4e633..bdeb8c252 100755 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ from setuptools import setup, find_packages, Command NAME = 'orange-widget-base' -VERSION = '4.21.0' -ISRELEASED = True +VERSION = '4.22.0' +ISRELEASED = False # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From 3ed62275d43452b5f8d7e317c259c069e8771257 Mon Sep 17 00:00:00 2001 From: janezd Date: Wed, 31 May 2023 20:33:51 +0200 Subject: [PATCH 02/62] File dialog: Add as much path as necessary to make names unambiguous --- orangewidget/utils/filedialogs.py | 37 +++++++- orangewidget/utils/tests/test_filedialogs.py | 97 +++++++++++++++++++- 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/orangewidget/utils/filedialogs.py b/orangewidget/utils/filedialogs.py index 976ba5731..5abfc8e23 100644 --- a/orangewidget/utils/filedialogs.py +++ b/orangewidget/utils/filedialogs.py @@ -1,3 +1,8 @@ +from collections import Counter +from itertools import count + +import re + import os import sys import typing @@ -64,6 +69,30 @@ def fix_extension(ext, format, suggested_ext, suggested_format): fix_extension.CANCEL = 2 # type: ignore +def unambiguous_paths(fullpaths, minlevel=1): + assert minlevel > 0 + + # split paths using str.split(..., os.path.sep); + # os.path.split only splits into head and tail + sep = re.escape(os.path.sep) + if os.path.altsep is not None: + sep = f"{sep}|{re.escape(os.path.altsep)}" + splitpaths = [re.split(sep, path) for path in fullpaths] + + to_check = list(range(len(fullpaths))) + paths = [os.path.join(*path[-minlevel:]) for path in splitpaths] + level = minlevel + + while to_check: + counts = Counter(paths[i] for i in to_check) + to_check = [i for i in to_check if counts[paths[i]] > 1 + and len(splitpaths[i]) > level] + level += 1 + for i in to_check: + paths[i] = os.path.join(splitpaths[i][-level], paths[i]) + return paths + + def format_filter(writer): # type: (Format) -> str return '{} (*{})'.format(writer.DESCRIPTION, ' *'.join(writer.EXTENSIONS)) @@ -422,9 +451,13 @@ def set_file_list(self): if not self.recent_paths: self.file_combo.addItem("(none)") self.file_combo.model().item(0).setEnabled(False) + self.file_combo.setToolTip("") else: - for i, recent in enumerate(self.recent_paths): - self.file_combo.addItem(recent.basename) + self.file_combo.setToolTip(self.recent_paths[0].abspath) + paths = unambiguous_paths( + [recent.abspath for recent in self.recent_paths], minlevel=2) + for i, recent, path in zip(count(), self.recent_paths, paths): + self.file_combo.addItem(path) self.file_combo.model().item(i).setToolTip(recent.abspath) if not os.path.exists(recent.abspath): self.file_combo.setItemData(i, QBrush(Qt.red), diff --git a/orangewidget/utils/tests/test_filedialogs.py b/orangewidget/utils/tests/test_filedialogs.py index b8db311f2..aca2a29e5 100644 --- a/orangewidget/utils/tests/test_filedialogs.py +++ b/orangewidget/utils/tests/test_filedialogs.py @@ -1,9 +1,104 @@ +import sys + import os import unittest from unittest.mock import Mock from tempfile import NamedTemporaryFile -from orangewidget.utils.filedialogs import RecentPath, open_filename_dialog +from orangewidget.utils.filedialogs import RecentPath, open_filename_dialog, \ + unambiguous_paths + + +class TestUtils(unittest.TestCase): + def test_unambiguous_paths(self): + paths = [ + "asd.txt", + "abc/def/ghi.txt", + "abc/def/jkl.txt", + "abc/xyz/jkl.txt", + "abc/xyz/rty/qwe.txt", + "abd/xyz/rty/qwe.txt", + "abe/xyz/rty/qwe.txt", + ] + + paths = [t.replace("/", os.path.sep, 1) for t in paths] + + def test(exp, **kwargs): + self.assertEqual(unambiguous_paths(paths, **kwargs), + [t.replace("/", os.path.sep) for t in exp]) + + test(["asd.txt", + "ghi.txt", + "def/jkl.txt", + "xyz/jkl.txt", + "abc/xyz/rty/qwe.txt", + "abd/xyz/rty/qwe.txt", + "abe/xyz/rty/qwe.txt"]) + + test(["asd.txt", + "def/ghi.txt", + "def/jkl.txt", + "xyz/jkl.txt", + "abc/xyz/rty/qwe.txt", + "abd/xyz/rty/qwe.txt", + "abe/xyz/rty/qwe.txt"], minlevel=2) + + test(["asd.txt", + "abc/def/ghi.txt", + "abc/def/jkl.txt", + "abc/xyz/jkl.txt", + "abc/xyz/rty/qwe.txt", + "abd/xyz/rty/qwe.txt", + "abe/xyz/rty/qwe.txt"], minlevel=3) + + test(["asd.txt", + "abc/def/ghi.txt", + "abc/def/jkl.txt", + "abc/xyz/jkl.txt", + "abc/xyz/rty/qwe.txt", + "abd/xyz/rty/qwe.txt", + "abe/xyz/rty/qwe.txt"], minlevel=4) + + # For simplicity, omit this test on Windows; if it works on Posix paths, + # it works on Windows, too. + if os.path.sep == "/": + t = ["abc/def/ghi.txt", "abc/def/ghi.txt"] + self.assertEqual(unambiguous_paths(t), t) + + if sys.platform == "win32": + ptch1 = ptch2 = ptch3 = lambda x: x + else: + # This test is intended for Windows, but for easier testing of a test + # on non-Windows machine, we patch it to make it work on others + ptch1 = unittest.mock.patch("os.path.sep", "/") + ptch2 = unittest.mock.patch("os.path.altsep", "\\") + ptch3 = unittest.mock.patch( + "os.path.join", + lambda *args, oj=os.path.join: oj(*args).replace("/", "\\")) + @ptch1 + @ptch2 + @ptch3 + def test_unambiguous_paths_windows(self): + paths = ["C:\\Documents/Newsletters\\Summer2018.pdf", + "D:\\Documents/Newsletters\\Summer2018.pdf"] + self.assertEqual(unambiguous_paths(paths), + ["C:\\Documents\\Newsletters\\Summer2018.pdf", + "D:\\Documents\\Newsletters\\Summer2018.pdf"] + ) + + paths = ["C:\\abc\\def\\Summer2018.pdf", + "C:\\abc\\deg\\Summer2018.pdf"] + self.assertEqual(unambiguous_paths(paths), + ["def\\Summer2018.pdf", + "deg\\Summer2018.pdf", ] + ) + + paths = ["C:\\deg\\Summer2018.pdf", + "D:\\deg\\Summer2018.pdf"] + self.assertEqual(unambiguous_paths(paths), + ["C:\\deg\\Summer2018.pdf", + "D:\\deg\\Summer2018.pdf", ] + ) class TestRecentPath(unittest.TestCase): From f92701f9c124d0e14591a97ccef8aeca413bebbe Mon Sep 17 00:00:00 2001 From: janezd Date: Mon, 5 Jun 2023 19:46:08 +0200 Subject: [PATCH 03/62] MessageGroup.clear: Add argument owner --- orangewidget/utils/messages.py | 37 ++++++++++++++------- orangewidget/utils/tests/test_messages.py | 40 +++++++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 orangewidget/utils/tests/test_messages.py diff --git a/orangewidget/utils/messages.py b/orangewidget/utils/messages.py index cb7b329af..b04cebacb 100644 --- a/orangewidget/utils/messages.py +++ b/orangewidget/utils/messages.py @@ -51,8 +51,8 @@ class are replaced by instances of `_BoundMsg` that are bound to the group. def __new__(cls, msg): return str.__new__(cls, msg) - def bind(self, group): - return _BoundMsg(self, group) + def bind(self, group, owner_class=None): + return _BoundMsg(self, group, owner_class) # The method is implemented in _BoundMsg # pylint: disable=unused-variable @@ -105,12 +105,14 @@ class _BoundMsg(UnboundMsg): Attributes: group (MessageGroup): the group to which this message belongs + owner_class (OWBaseWidget): the class in which the message is defined formatted (str): formatted message """ - def __new__(cls, unbound_msg, group): + def __new__(cls, unbound_msg, group, owner_class=None): self = UnboundMsg.__new__(cls, unbound_msg) self.group = group + self.owner_class = owner_class self.formatted = "" self.tb = None # type: Optional[str] return self @@ -185,12 +187,24 @@ def active(self): return self._active.values() def _bind_messages(self): - # type(self).__dict__ wouldn't return inherited messages, hence dir - for name in dir(self): - msg = getattr(self, name) - if isinstance(msg, UnboundMsg): - msg = msg.bind(self) - self.__dict__[name] = msg + def bind_subgroup(subgroup, widget_class): + for name, msg in subgroup.__dict__.items(): + if type(msg) is UnboundMsg: + msg = msg.bind(self, widget_class) + self.__dict__[name] = msg + + for group in type(self).mro(): + for widget_class in type(self.widget).mro(): + if group in widget_class.__dict__.values(): + break + else: + # MessageGroups outside widget classes (e.g. mixins) + widget_class = None + bind_subgroup(group, widget_class) + + # This binds `_general` -- and any similar cases in which a message is + # added to the instance and not as class attribute + bind_subgroup(self, None) def add_message(self, name, msg="{}"): """Add and bind message to a group that is already instantiated @@ -252,10 +266,11 @@ def deactivate_msg(self, msg): # self has default value to avoid PyCharm warnings when calling # self.Error.clear(): PyCharm doesn't know that Error is instantiated - def clear(self=None): + def clear(self=None, *, owner=None): """Deactivate all active message from this group.""" for msg in list(self._active): - self.deactivate_msg(msg) + if owner is None or msg.owner_class is owner: + self.deactivate_msg(msg) def _add_general(self, id_or_text, text, shown): """Handler for methods `error`, `warning` and `information`; diff --git a/orangewidget/utils/tests/test_messages.py b/orangewidget/utils/tests/test_messages.py new file mode 100644 index 000000000..8b6ac10cb --- /dev/null +++ b/orangewidget/utils/tests/test_messages.py @@ -0,0 +1,40 @@ +import unittest + +from orangewidget.tests.base import WidgetTest +from orangewidget.widget import OWBaseWidget, Msg + + +class TestMessages(WidgetTest): + def test_clear_owner(self): + class WidgetA(OWBaseWidget, openclass=True): + class Error(OWBaseWidget.Error): + err_a = Msg("error a") + + class WidgetB(WidgetA): + class Error(WidgetA.Error): + err_b = Msg("error b") + + w = self.create_widget(WidgetB) + w.Error.err_a() + w.Error.err_b() + self.assertTrue(w.Error.err_a.is_shown()) + self.assertTrue(w.Error.err_b.is_shown()) + w.Error.clear() + self.assertFalse(w.Error.err_a.is_shown()) + self.assertFalse(w.Error.err_b.is_shown()) + + w.Error.err_a() + w.Error.err_b() + w.Error.clear(owner=WidgetB) + self.assertTrue(w.Error.err_a.is_shown()) + self.assertFalse(w.Error.err_b.is_shown()) + + w.Error.err_a() + w.Error.err_b() + w.Error.clear(owner=WidgetA) + self.assertFalse(w.Error.err_a.is_shown()) + self.assertTrue(w.Error.err_b.is_shown()) + + +if __name__ == "__main__": + unittest.main() From 83ca35dae6ddeee77296657cdc48146dcc4c8cf7 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 16 Jun 2023 10:51:16 +0200 Subject: [PATCH 04/62] ci: Update ubuntu image for PyQt6 6.5 tests https://www.riverbankcomputing.com/pipermail/pyqt/2023-June/045313.html --- .github/workflows/run-tests-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index acab98887..0597d2980 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -42,10 +42,10 @@ jobs: test-env: "PyQt6~=6.2.3 PyQt6-Qt6~=6.2.3 PyQt6-WebEngine~=6.2.1 PyQt6-WebEngine-Qt6~=6.2.1" extra-system-packages: "libegl1-mesa" - - os: ubuntu-20.04 + - os: ubuntu-22.04 python-version: "3.11" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" - extra-system-packages: "libegl1-mesa libxcb-cursor0" + extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" # macOS - os: macos-11 From a24ac4c8ab5c69af35b6da1550b63b40d17db792 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 15 Sep 2023 11:20:48 +0200 Subject: [PATCH 05/62] PngExporter: set displayed size after painting This seems to fix problems with missized (and disconnected axes) while keeping proper display size. --- orangewidget/io.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/orangewidget/io.py b/orangewidget/io.py index dfbdcdc02..33a55ab01 100644 --- a/orangewidget/io.py +++ b/orangewidget/io.py @@ -301,6 +301,7 @@ def __init__(self, item): # Copied verbatim from super; # changes are in three lines that define self.png + # and lines related to setDotsPerMeterX after painter.end() def export(self, fileName=None, toBytes=False, copy=False): if fileName is None and not toBytes and not copy: filter = self.getSupportedImageFormats() @@ -322,9 +323,6 @@ def export(self, fileName=None, toBytes=False, copy=False): QtGui.QImage.Format.Format_ARGB32) self.png.fill(self.params['background']) self.png.setDevicePixelRatio(self.ratio) - dpm = int(2835 * self.ratio) - self.png.setDotsPerMeterX(dpm) - self.png.setDotsPerMeterY(dpm) ## set resolution of image: origTargetRect = self.getTargetRect() @@ -349,6 +347,11 @@ def export(self, fileName=None, toBytes=False, copy=False): self.setExportMode(False) painter.end() + # setDotsPerMeter* after painting avoids missized axes problem + dpm = int(2835 * self.ratio) + self.png.setDotsPerMeterX(dpm) + self.png.setDotsPerMeterY(dpm) + if self.params['invertValue']: bg = fn.ndarray_from_qimage(self.png) if sys.byteorder == 'little': From e05874f15f8020a7918b40903b7f36db23f05c8c Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 15 Sep 2023 11:23:37 +0200 Subject: [PATCH 06/62] Import forgotten numpy --- orangewidget/io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/orangewidget/io.py b/orangewidget/io.py index 33a55ab01..6ec063dbe 100644 --- a/orangewidget/io.py +++ b/orangewidget/io.py @@ -4,6 +4,8 @@ from collections import OrderedDict from typing import Union +import numpy as np + from AnyQt import QtGui, QtCore, QtSvg, QtWidgets from AnyQt.QtCore import QMarginsF, Qt, QRectF, QPoint, QRect, QSize from AnyQt.QtGui import QPalette From afa06382ea8e6635cad86d3f6ead16bdc8548963 Mon Sep 17 00:00:00 2001 From: PrimozGodec Date: Fri, 15 Sep 2023 14:49:27 +0200 Subject: [PATCH 07/62] Release version 4.22.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bdeb8c252..6a5e09724 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ NAME = 'orange-widget-base' VERSION = '4.22.0' -ISRELEASED = False +ISRELEASED = True # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From fd1fb70b60e1dd1a95125578d6758e70551c829f Mon Sep 17 00:00:00 2001 From: PrimozGodec Date: Fri, 15 Sep 2023 14:51:44 +0200 Subject: [PATCH 08/62] Bump to 4.23.0.dev0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6a5e09724..869e8f7a1 100755 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ from setuptools import setup, find_packages, Command NAME = 'orange-widget-base' -VERSION = '4.22.0' -ISRELEASED = True +VERSION = '4.23.0' +ISRELEASED = False # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From bbcf5f3221b58058ce830442fd323fdc3c585423 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Mon, 9 Oct 2023 15:21:51 +0200 Subject: [PATCH 09/62] readthedocs: Remove no longer supported 'system_packages' --- .readthedocs.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 9cc53846a..66cfd3855 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,9 +1,7 @@ version: 2 python: - version: 3.8 + version: "3.8" install: - requirements: doc/requirements-rtd.txt # no - method: pip here, -e . is already in doc/requirements-rtd.txt - - system_packages: true From 229c19679c1402e7506c81d1fd5df60179177f71 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Tue, 10 Oct 2023 12:28:33 +0200 Subject: [PATCH 10/62] itemmodels: Remove use of private signals from PyTableModel rowsAboutToBeRemoved/Inserted, ... are private signals of QAbstractItemModel and should not be emitted directly. Use beginInsert/RemoveRows and endInsert/RemoveRows instead. --- orangewidget/utils/itemmodels.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/orangewidget/utils/itemmodels.py b/orangewidget/utils/itemmodels.py index 63a122c6c..743939a04 100644 --- a/orangewidget/utils/itemmodels.py +++ b/orangewidget/utils/itemmodels.py @@ -434,9 +434,9 @@ def __setitem__(self, i, value): # Signal changes parent = QModelIndex() if newstop > stop: - self.rowsAboutToBeInserted.emit(parent, stop, newstop - 1) + self.beginInsertRows(parent, stop, newstop - 1) elif newstop < stop: - self.rowsAboutToBeRemoved.emit(parent, newstop, stop - 1) + self.beginRemoveRows(parent, newstop, stop - 1) # Make changes self._table[i] = value @@ -447,9 +447,9 @@ def __setitem__(self, i, value): self.index(start, 0), self.index(min(stop, newstop) - 1, self.columnCount() - 1)) if newstop > stop: - self.rowsInserted.emit(parent, stop, newstop - 1) + self.endInsertRows() elif newstop < stop: - self.rowsRemoved.emit(parent, newstop, stop - 1) + self.endRemoveRows() else: self._table[i] = value i %= len(self) From cdeb73f1e11bbe51a7030f063f30a286ef8a1eca Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 18 Oct 2023 15:16:28 +0200 Subject: [PATCH 11/62] readthedocs: Add build.os key https://blog.readthedocs.com/use-build-os-config/ --- .readthedocs.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 66cfd3855..20cf46d94 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,7 +1,11 @@ version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.8" + python: - version: "3.8" install: - requirements: doc/requirements-rtd.txt # no - method: pip here, -e . is already in doc/requirements-rtd.txt From ca144853fa6f44e7092a4d9a384e7b5c0f81bf17 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Thu, 19 Oct 2023 11:34:51 +0200 Subject: [PATCH 12/62] ci: Add Python 3.12 to test matrices Also remove Python 3.7 to ease CI workload --- .github/workflows/run-tests-workflow.yml | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 0597d2980..49d9c907a 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -17,10 +17,6 @@ jobs: matrix: include: # Linux - - os: ubuntu-20.04 - python-version: 3.7 - test-env: "PyQt5~=5.9.2 pyqtgraph~=0.11.0" - - os: ubuntu-20.04 python-version: 3.8 test-env: "PyQt5~=5.12.1 PyQtWebEngine~=5.12.1" @@ -47,11 +43,12 @@ jobs: test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" - # macOS - - os: macos-11 - python-version: 3.7 - test-env: "PyQt5~=5.12.1 PyQtWebEngine~=5.12.1" + - os: ubuntu-22.04 + python-version: "3.12" + test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" + extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" + # macOS - os: macos-11 python-version: 3.8 test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" @@ -72,11 +69,11 @@ jobs: python-version: "3.11" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" - # Windows - - os: windows-2019 - python-version: 3.7 - test-env: "PyQt5~=5.9.2 pyqtgraph~=0.11.0" + - os: macos-12 + python-version: "3.12" + test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" + # Windows - os: windows-2019 python-version: 3.8 test-env: "PyQt5~=5.12.1 PyQtWebEngine~=5.12.1" @@ -101,6 +98,10 @@ jobs: python-version: "3.11" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" + - os: windows-2019 + python-version: "3.12" + test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" + steps: - uses: actions/checkout@v3 - name: Setup Python From 8d74124813beb2a93798fd14b8fad61541fdad7e Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 8 Dec 2023 10:58:56 +0100 Subject: [PATCH 13/62] CI: Constrain PyQt6-Qt6 version in tests --- .github/workflows/run-tests-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 49d9c907a..f64e4fb40 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -63,7 +63,7 @@ jobs: - os: macos-12 python-version: "3.11" - test-env: "PyQt6~=6.2.3 PyQt6-WebEngine~=6.2.1" + test-env: "PyQt6~=6.2.3 PyQt6-Qt6~=6.2.3 PyQt6-WebEngine~=6.2.1 PyQt6-WebEngine-Qt6~=6.2.1" - os: macos-12 python-version: "3.11" From 2c6b4cfbba035f7f89bb886a02d8330f2710ce7d Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 3 Nov 2023 00:23:45 +0100 Subject: [PATCH 14/62] gui: Replace use of pkg_resources --- orangewidget/gui.py | 15 +++++++++++---- orangewidget/utils/messages.py | 4 ---- setup.py | 3 ++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/orangewidget/gui.py b/orangewidget/gui.py index 454f47d8c..cadf7e76e 100644 --- a/orangewidget/gui.py +++ b/orangewidget/gui.py @@ -11,8 +11,6 @@ from types import LambdaType from collections import defaultdict -import pkg_resources - from AnyQt import QtWidgets, QtCore, QtGui from AnyQt.QtCore import Qt, QEvent, QObject, QTimer, pyqtSignal as Signal from AnyQt.QtGui import QCursor, QColor @@ -30,6 +28,12 @@ from orangewidget.utils.itemdelegates import text_color_for_state from orangewidget.utils.itemmodels import PyListModel, signal_blocking + +try: + from importlib.resources import files as _resources_files +except ImportError: + from importlib_resources import files as _resources_files + __re_label = re.compile(r"(^|[^%])%\((?P[a-zA-Z]\w*)\)") log = logging.getLogger(__name__) @@ -102,11 +106,14 @@ def sizeHint(self, option, index): return super().sizeHint(option, index) -def resource_filename(path): +def resource_filename(path: str) -> str: # pragma: no cover """ Return a resource filename (package data) for path. """ - return pkg_resources.resource_filename(__name__, path) + warnings.warn( + "'resource_filename' is deprecated.", DeprecationWarning, stacklevel=2 + ) + return str(_resources_files(__package__).joinpath(path)) class OWComponent: diff --git a/orangewidget/utils/messages.py b/orangewidget/utils/messages.py index b04cebacb..961a4c58e 100644 --- a/orangewidget/utils/messages.py +++ b/orangewidget/utils/messages.py @@ -36,7 +36,6 @@ from AnyQt.QtWidgets import QStyle, QSizePolicy -from orangewidget import gui from orangewidget.utils.messagewidget import MessagesWidget @@ -323,21 +322,18 @@ class WidgetMessagesMixin(MessagesMixin): class Error(MessageGroup): """Base class for groups of error messages in widgets""" severity = 3 - icon_path = gui.resource_filename("icons/error.png") bar_background = "#ffc6c6" bar_icon = QStyle.SP_MessageBoxCritical class Warning(MessageGroup): """Base class for groups of warning messages in widgets""" severity = 2 - icon_path = gui.resource_filename("icons/warning.png") bar_background = "#ffffc9" bar_icon = QStyle.SP_MessageBoxWarning class Information(MessageGroup): """Base class for groups of information messages in widgets""" severity = 1 - icon_path = gui.resource_filename("icons/information.png") bar_background = "#ceceff" bar_icon = QStyle.SP_MessageBoxInformation diff --git a/setup.py b/setup.py index 869e8f7a1..a6f37545d 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,8 @@ "AnyQt>=0.1.0", "typing_extensions>=3.7.4.3", "orange-canvas-core>=0.1.30,<0.2a", - 'appnope; sys_platform=="darwin"' + 'appnope; sys_platform=="darwin"', + "importlib_resources; python_version<'3.10'" ] EXTRAS_REQUIRE = { From 0a6e89615b0c55ca44a37482a257a4bae6ff689a Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 8 Nov 2023 14:53:14 +0100 Subject: [PATCH 15/62] widget: Replace use of pkg_resources --- orangewidget/utils/__init__.py | 11 +++++++++-- orangewidget/widget.py | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/orangewidget/utils/__init__.py b/orangewidget/utils/__init__.py index 0ab1b36e7..fdd607ba6 100644 --- a/orangewidget/utils/__init__.py +++ b/orangewidget/utils/__init__.py @@ -1,12 +1,15 @@ import enum import inspect -from typing import Union, Iterator, Optional - +import pkgutil import sys import warnings from operator import attrgetter +from typing import Union, Iterator, Optional from AnyQt.QtCore import QObject, QRect, QSize, QPoint, QTextBoundaryFinder +from AnyQt.QtGui import QIcon + +from orangecanvas.gui.svgiconengine import StyledSvgIconEngine def progress_bar_milestones(count, iterations=100): @@ -195,3 +198,7 @@ def grapheme_slice(text: str, start: int = 0, end: int = None) -> str: if slice_end is None: slice_end = len(text) return text[slice_start: slice_end] + + +def load_styled_icon(package: str, path: str) -> QIcon: + return QIcon(StyledSvgIconEngine(pkgutil.get_data(package, path))) diff --git a/orangewidget/widget.py b/orangewidget/widget.py index d17510084..3a614683a 100755 --- a/orangewidget/widget.py +++ b/orangewidget/widget.py @@ -33,7 +33,7 @@ from orangewidget.gui import OWComponent, VerticalScrollArea from orangewidget.io import ClipboardFormat, ImgFormat from orangewidget.settings import SettingsHandler -from orangewidget.utils import saveplot, getdeepattr +from orangewidget.utils import saveplot, getdeepattr, load_styled_icon from orangewidget.utils.messagewidget import InOutStateWidget from orangewidget.utils.progressbar import ProgressBarMixin from orangewidget.utils.messages import ( @@ -60,7 +60,7 @@ def _load_styled_icon(name): - return QIcon(StyledSvgIconEngine(pkgutil.get_data(__name__, "icons/" + name))) + return load_styled_icon(__package__, "icons/" + name) class Message: From 893834fc1c0ae46a4e680c0eeb76b7f4449f73eb Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 8 Nov 2023 14:59:08 +0100 Subject: [PATCH 16/62] report: Replace use of pkg_resources --- orangewidget/report/owreport.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/orangewidget/report/owreport.py b/orangewidget/report/owreport.py index d2359ea7f..7da395f83 100644 --- a/orangewidget/report/owreport.py +++ b/orangewidget/report/owreport.py @@ -4,13 +4,12 @@ import traceback import warnings import pickle +import pkgutil from collections import OrderedDict from enum import IntEnum from typing import Optional -import pkg_resources - from AnyQt.QtCore import Qt, QObject, pyqtSlot, QSize from AnyQt.QtGui import QIcon, QCursor, QStandardItemModel, QStandardItem from AnyQt.QtWidgets import ( @@ -20,6 +19,7 @@ from orangewidget import gui +from orangewidget.utils import load_styled_icon from orangewidget.widget import OWBaseWidget from orangewidget.settings import Setting @@ -51,12 +51,9 @@ def __init__(self, name, html, scheme, module, icon_name, comment=""): self.icon_name = icon_name self.comment = comment try: - path = pkg_resources.resource_filename(module, icon_name) - except ImportError: - path = "" - except ValueError: - path = "" - icon = QIcon(path) + icon = load_styled_icon(module, icon_name) + except (ImportError, FileNotFoundError): + icon = QIcon() self.id = id(icon) super().__init__(icon, name) @@ -93,10 +90,8 @@ def _icon_item(tooltip): class ReportTable(QTableView): def __init__(self, parent): super().__init__(parent) - self._icon_remove = QIcon(pkg_resources.resource_filename( - __name__, "icons/delete.svg")) - self._icon_scheme = QIcon(pkg_resources.resource_filename( - __name__, "icons/scheme.svg")) + self._icon_remove = load_styled_icon(__package__, "icons/delete.svg") + self._icon_scheme = load_styled_icon(__package__, "icons/scheme.svg") def mouseMoveEvent(self, event): self._clear_icons() @@ -139,9 +134,8 @@ def __init__(self): self.report_changed = False self.have_report_warning_shown = False - index_file = pkg_resources.resource_filename(__name__, "index.html") - with open(index_file, "r") as f: - self.report_html_template = f.read() + index_file = pkgutil.get_data(__name__, "index.html") + self.report_html_template = index_file.decode("utf-8") def _setup_ui_(self): self.table_model = ReportItemModel(0, len(Column.__members__)) From 884fec503b034535932de0fc064b4321ff1daf65 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 8 Nov 2023 14:59:39 +0100 Subject: [PATCH 17/62] workflow/config: Replace use of pkg_resources Also remove a useless examples_entry_points insertion --- orangewidget/workflow/config.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/orangewidget/workflow/config.py b/orangewidget/workflow/config.py index ddee8fcfa..3e4fc1da5 100644 --- a/orangewidget/workflow/config.py +++ b/orangewidget/workflow/config.py @@ -8,12 +8,12 @@ import itertools from typing import Dict, Any, Optional, Iterable, List -import pkg_resources import requests from AnyQt.QtCore import QStandardPaths, QCoreApplication from orangecanvas import config +from orangecanvas.utils.pkgmeta import entry_points, EntryPoint # generated from biolab/orange3-addons repository @@ -39,7 +39,7 @@ def widgets_entry_points(): Return an `EntryPoint` iterator for all registered 'orange.widgets' entry points. """ - return pkg_resources.iter_entry_points(WIDGETS_ENTRY) + return entry_points(group=WIDGETS_ENTRY) @staticmethod def addon_entry_points(): @@ -70,23 +70,11 @@ def core_packages(): @staticmethod def examples_entry_points(): - # type: () -> Iterable[pkg_resources.EntryPoint] + # type: () -> Iterable[EntryPoint] """ Return an iterator over the entry points yielding 'Example Workflows' """ - # `iter_entry_points` yields them in unspecified order, so we insert - # our first - try: - default_ep = (pkg_resources.EntryPoint( - "Orange3", "Orange.canvas.workflows", - dist=pkg_resources.get_distribution("Orange3")),) - except pkg_resources.DistributionNotFound: - default_ep = tuple() - - return itertools.chain( - default_ep, - pkg_resources.iter_entry_points("orange.widgets.tutorials") - ) + return entry_points(group="orange.widgets.tutorials") @staticmethod def widget_discovery(*args, **kwargs): From 4c60532fc27648b5167a705c0dabd88ed0154598 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 8 Nov 2023 15:00:31 +0100 Subject: [PATCH 18/62] errorreporting: Replace use of pkg_resources --- orangewidget/workflow/errorreporting.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/orangewidget/workflow/errorreporting.py b/orangewidget/workflow/errorreporting.py index 62c5c0f6e..5648b01a1 100644 --- a/orangewidget/workflow/errorreporting.py +++ b/orangewidget/workflow/errorreporting.py @@ -15,8 +15,7 @@ from urllib.request import pathname2url, urlopen, build_opener from urllib.error import URLError from unittest.mock import patch - -import pkg_resources +from importlib.metadata import distributions from AnyQt.QtCore import pyqtSlot, QSettings, Qt from AnyQt.QtGui import QDesktopServices, QFont @@ -35,8 +34,8 @@ def get_installed_distributions(): - for dist in pkg_resources.working_set: # type: pkg_resources.Distribution - name = dist.project_name + for dist in distributions(): + name = dist.name try: version = dist.version except ValueError: From 6cb68fa1afcc7f5dbc330a3acaf5aaf4945a554b Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 8 Nov 2023 15:00:57 +0100 Subject: [PATCH 19/62] docs/config: Replace use of pkg_resources --- doc/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index c3460a36d..7d8a57134 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -17,8 +17,8 @@ import os import shlex -import pkg_resources -dist = pkg_resources.get_distribution("orange-widget-base") +from importlib.metadata import distribution +dist = distribution("orange-widget-base") # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the From f0be5a8a1dabc536c1fe23916b83669ad978b032 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 10 Nov 2023 10:05:17 +0100 Subject: [PATCH 20/62] discovery: Replace use of pkg_resources --- orangewidget/workflow/discovery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orangewidget/workflow/discovery.py b/orangewidget/workflow/discovery.py index cf8bff311..fe32353cf 100644 --- a/orangewidget/workflow/discovery.py +++ b/orangewidget/workflow/discovery.py @@ -57,6 +57,6 @@ def widget_description(self, module, widget_name=None, category_name=None, desc.category = category_name if distribution is not None: - desc.project_name = distribution.project_name + desc.project_name = distribution.name return desc From 1da86a244dd65b6a077f9981c905d430b855897d Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 17 Nov 2023 10:17:21 +0100 Subject: [PATCH 21/62] setup.py: Bump required orange-canvas-core version to 0.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a6f37545d..aff4f1a35 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ "pyqtgraph", "AnyQt>=0.1.0", "typing_extensions>=3.7.4.3", - "orange-canvas-core>=0.1.30,<0.2a", + "orange-canvas-core>=0.2a.dev0,<0.3a", 'appnope; sys_platform=="darwin"', "importlib_resources; python_version<'3.10'" ] From 98efb6b9dab5e10c54b4f444f3e63840a42f3be8 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 15 Dec 2023 10:28:50 +0100 Subject: [PATCH 22/62] Release version 4.23.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index aff4f1a35..7e5cda3ce 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ NAME = 'orange-widget-base' VERSION = '4.23.0' -ISRELEASED = False +ISRELEASED = True # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From dae329dfbf34118cae8cb15f2a020e5ff14ce4db Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 15 Dec 2023 10:32:08 +0100 Subject: [PATCH 23/62] Bump version to 4.24.0.dev0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7e5cda3ce..043662ce2 100755 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ from setuptools import setup, find_packages, Command NAME = 'orange-widget-base' -VERSION = '4.23.0' -ISRELEASED = True +VERSION = '4.24.0' +ISRELEASED = False # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From eebc02ebde70b6caa54f538e29ef10ff4fac27e9 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 22 Dec 2023 09:44:12 +0100 Subject: [PATCH 24/62] setup.py: Replace use of imp module --- setup.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 043662ce2..bc222e706 100755 --- a/setup.py +++ b/setup.py @@ -119,8 +119,12 @@ def write_version_py(filename='orangewidget/version.py'): GIT_REVISION = git_version() elif os.path.exists(filename): # must be a source distribution, use existing version file - import imp - version = imp.load_source("orangewidget.version", filename) + import importlib.util + spec = importlib.util.spec_from_file_location( + "orangewidget.version", filename + ) + version = importlib.util.module_from_spec(spec) + spec.loader.exec_module(version) GIT_REVISION = version.git_revision else: GIT_REVISION = "Unknown" From e4b668d0e5100581c2e44f516c90b81f32b943f9 Mon Sep 17 00:00:00 2001 From: janezd Date: Wed, 17 Jan 2024 16:22:44 +0100 Subject: [PATCH 25/62] Add Slovenian translation and tests --- .github/workflows/test-translations.yml | 60 + i18n/si/msgs.jaml | 1648 +++++++++++++++++++++++ i18n/si/trubar-config.yaml | 2 + i18n/trans.sh | 8 + 4 files changed, 1718 insertions(+) create mode 100644 .github/workflows/test-translations.yml create mode 100644 i18n/si/msgs.jaml create mode 100644 i18n/si/trubar-config.yaml create mode 100755 i18n/trans.sh diff --git a/.github/workflows/test-translations.yml b/.github/workflows/test-translations.yml new file mode 100644 index 000000000..fe598eef1 --- /dev/null +++ b/.github/workflows/test-translations.yml @@ -0,0 +1,60 @@ +name: Check translations + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install Trubar + run: | + pip install trubar + + - name: Collect translations + run: | + cd i18n + trubar collect -s ../orangewidget new.jaml + trubar merge -u Obsolete.jaml si/msgs.jaml new.jaml + trubar missing -o Missing.jaml new.jaml + if [ ! -s Missing.jaml ] + then + rm Missing.jaml + fi + + - name: Check Slovenian translations + run: | + cd i18n + for fn in Obsolete Missing + do + if [ -f $fn.jaml ] + then + echo "::group::$fn translations" + cat $fn.jaml + echo "::endgroup::" + fi + done + echo "-------------------------------" + trubar stat new.jaml + echo "-------------------------------" + if [ -f Missing.jaml ] || [ -f Obsolete.jaml ] + then + echo "### Missing or obsolete translations" >> $GITHUB_STEP_SUMMARY + echo "::error::Missing or obsolete translations" + echo "If you wish to fix this, see orange3/i18n/README.md" + exit 1 + else + echo "### All translations are up to date" >> $GITHUB_STEP_SUMMARY + fi diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml new file mode 100644 index 000000000..7565c2b74 --- /dev/null +++ b/i18n/si/msgs.jaml @@ -0,0 +1,1648 @@ +gui.py: + (^|[^%])%\((?P[a-zA-Z]\w*)\): false + _lambda_{i}: false + def `is_macstyle`: + QMacStyle: false + def `resource_filename`: + "'resource_filename' is deprecated.": false + class `OWComponent`: + def `connect_control`: + .: false + def `__setattr__`: + .: false + controlled_attributes: false + def `miscellanea`: + addSpace: false + "'addSpace' has been deprecated. Use gui.separator instead.": false + set: false + box: false + def `_is_horizontal`: + string literals for orientation are deprecated: false + boolean values for orientation are deprecated: false + horizontal: false + def `widgetBox`: + ' ': false + addSpaceBefore: false + def `label`: + value: false + class `SpinBoxMixin`: + def `onEnter`: + "Testing by calling a spinbox's 'onEnter' method is deprecated, ": false + a call to 'setValue' should be sufficient.: false + def `spin`: + "'callbackOnReturn' is deprecated, all spinboxes callback ": false + only when the user is finished editing the value.: false + upButton: false + class `LineEditWFocusOut`: + def `__callback_if_changed`: + cback: false + def `lineEdit`: + baseClass: false + def `radioButtons`: + addToLayout: false + def `appendRadioButton`: + "'addSpace' has been deprecated. Use gui.separator instead.": false + buttons: false + def `hSlider`: + ' %d': false + addToLayout: false + def `labeledSlider`: + ' %d': false + def `valueSlider`: + ' %d': false + def `comboBox`: + addToLayout: false + "'editable' is ignored for searchable combo box.": false + valueType: false + comboBox no longer accepts argument 'valueType': false + def `deferred`: + class `DeferrerProperty`: + def `__get__`: + __{name}_data: false + class `Deferred`: + def `__call__`: + 'This function is deferred; explicitly call ': false + {name}.deferred or {name}.now: false + def `__setattr__`: + now: false + deferred: false + def `__getattribute__`: + now: false + deferred: false + def `__delattr__`: + now: false + deferred: false + def `__set__`: + "decorated {name} can't be mocked; ": false + mock '{name}.now' and/or '{name}.deferred'.: false + def `auto_commit`: + commit: false + deferred: false + {label.title()} Automatically: {label.title()} samodejno + addToLayout: false + decorated: false + {type(master).__name__}.{commit_name} must be: false + decorated with gui.deferred because it overrides: false + a decorated {supertype.__name__}.: false + 'decorate {type(master).__name__}.{commit_name} ': false + 'with @gui.deferred and then explicitly call ': false + {commit_name}.now or {commit_name}.deferred.: false + unconditional_: false + def `auto_send`: + auto_send: false + Send Selection: Pošlji izbor + Send Automatically: Pošlji samodejno + def `auto_apply`: + auto_apply: false + Apply: Uveljavi + Apply Automatically: Uveljavi samodejno + def `disable_opposite`: + opposite: false + class `ControlledCallback`: + def `__init__`: + callbackDeposit: false + def `acyclic_setattr`: + +-: false + class `SetLabelCallback`: + def `__init__`: + %5.2f: false + callbackDeposit: false + class `FunctionCallback`: + def `__init__`: + callbackDeposit: false + def `__call__`: + id: false + widget: false + class `ControlledCallFront`: + def `__call__`: + opposite: false + class `CallFrontComboBox`: + def `action`: + def `action_str`: + "Combo '{combo.objectName()}' has no item '{value}'; ": false + current items are {', '.join(map(repr, items))}.: false + combo '{combo.objectName()}' is empty.: false + def `action_int`: + 'index {value} is out of range ': false + "for combo box '{combo.objectName()}' ": false + with {combo.count()} item(s).: false + combo box '{combo.objectName()}' is empty.: false + class `CallFrontComboBoxModel`: + def `action`: + 'Combo box does not contain item ': false + class `CallFrontLogSlider`: + def `action`: + unable to set %s to %s (value too small): false + class `Disabler`: + def `__call__`: + makeConsistent: false + class `ColoredBarItemDelegate`: + def `__init__`: + %%.%if: false + def `displayText`: + NA: ?? + class `VisibleHeaderSectionContextEventFilter`: + def `eventFilter`: + Visible headers: false + def `checkButtonOffsetHint`: + macintosh (aqua): false + macintosh(aqua): false + plastique: false + cde: false + motif: false + class `ControlGetter`: + def `__getattr__`: + "'{}' is not an attribute related to a gui element or ": false + component: false + class `CalendarWidgetWithTime`: + def `__init__`: + hh:mm:ss: true + 'Time: ': 'Čas: ' + class `DateTimeEditWCalendarTime`: + def `__init__`: + yyyy-MM-dd hh:mm:ss: true +io.py: + ImgFormat: false + Compression: false + PngFormat: false + ClipboardFormat: false + SvgFormat: false + MatplotlibPDFFormat: false + MatplotlibFormat: false + PdfFormat: false + class `Compression`: + .gz: false + .bz2: false + .xz: false + class `_Registry`: + def `__new__`: + registry: false + def `__str__`: + {}({{{}}}): false + ', ': false + class `ImgFormat`: + def `_meta_data`: + width: false + height: false + pixel_ratio: false + def `write_image`: + '{cls.__name__} ': false + cannot imagine {type(object).__name__}: false + def `write`: + scene: false + def `img_writers`: + EXTENSIONS: false + class `PngFormat`: + .png: false + Portable Network Graphics: false + def `_save_buffer`: + png: false + def `_get_exporter`: + class `PngExporter`: + def `export`: + width: false + height: false + 'Cannot export image with size=0 (requested ': false + export size is %dx%d): false + background: false + antialias: false + painter: false + resolutionScale: false + invertValue: false + little: false + def `_export`: + png: false + class `ClipboardFormat`: + System Clipboard: Odložišče + def `_save_buffer`: + pixel_ratio: false + class `SvgFormat`: + .svg: false + Scalable Vector Graphics: false + def `_export`: + background: false + def `write_image`: + ' th.field-name { + text-align: right; + padding-right: 0.2em; + font-weight: bold; + } + dt { + font-weight: bold; + } + ': false + in-out-msg: false + input-summary: false + output-summary: false + buttons: false + def `info`: + "'OWBaseWidget.info' is a property since 3.19 and will be made read ": false + only in v4.0.: false + info: false + def `__setControlAreaVisible`: + action-show-control-area: false + def `__showStatusBarMenu`: + status-bar-menu-button: false + up: false + def `keyReleaseEvent`: + __visible_from_alt_key_press: false + def `__showMessage`: + Ok, got it: Aha, razumem! + ' + MessageOverlayWidget { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop:0 #666, stop:0.3 #6D6D6D, stop:1 #666) + } + MessageOverlayWidget QLabel#text-label { + color: white; + }': false + Learn more\N{HORIZONTAL ELLIPSIS}: Preberi več\N{HORIZONTAL ELLIPSIS} + def `__quicktipOnce`: + user-session-state.ini: false + user-message-history/{0.__module__}.{0.__qualname__}: false + def `_ispending`: + {}/confirmed: false + def `_userconfirmed`: + {}/confirmed: false + def `actionEvent`: + action-canvas-: false + menu-window: false + k: false + M: false + G: false + T: false + P: false + class `StateInfo`: + class `Summary`: + def `default_icon`: + {role}.svg: false + class `Empty`: + def `default_icon`: + {role}-empty.svg: false + class `Partial`: + def `default_icon`: + {role}-partial.svg: false + def `set_input_summary`: + def `assert_single_arg`: + 'No extra arguments expected when `summary` ': false + is `None` or `Message`: false + No data on input: Ni vhodnih podatkov + -: true + input: false + "'None', 'str' or 'Message' instance expected, ": false + got '{}': false + def `set_output_summary`: + def `assert_single_arg`: + 'No extra arguments expected when `summary` ': false + is `None` or `Message`: false + No data on output: Ni izhodnih podatkov + -: true + output: false + "'None', 'str' or 'Message' instance expected, ": false + got '{}': false + def `format_number`: + {n:.3g}{metric_suffix[mag]}: false +report/owreport.py: + class `ReportItemModel`: + def `add_item`: + Remove: Odstrani + Open Scheme: Odpri delotok + class `ReportTable`: + def `__init__`: + icons/delete.svg: false + icons/scheme.svg: false + class `OWReport`: + Report: Poročilo + def `__init__`: + index.html: false + utf-8: true + def `_setup_ui_`: + Back to Last Scheme: Vrni se na zadnji delotok + Save: Shrani + Print: Natisni + def `_add_item`: + {} - {}: true + def `_build_html`: + (function (id) {{: false + ' setSelectedId(id); scrollToId(id); ': false + }}: false + (\"{selected_id}\"));: false + '': false + : false + : false + "
{}
: false +
: false + : false + def `_scroll_to_item`: + scrollToId('{item.id}'): false + scrollToId returned %s: false + def `_change_selected_item`: + setSelectedId('{item.id}');: false + setSelectedId returned %s: false + def `_get_scheme_str`: + utf-8: false + def `save_report`: + HTML (*.html): false + .html: false + PDF (*.pdf): false + .pdf: false + Report (*.report): false + .report: false + Save Report: Shrani poročilo + ;;: false + def `save_html`: + w: false + utf-8: false + def `_print_report`: + Print report: Natisni poročilo + def `save`: + last_scheme: false + open_dir: false + wb: false + def `load`: + rb: false + attributes: false + items: false + def `permission_error`: + PermissionError when trying to write report.: true + Error: Napaka + Permission error when trying to write report.: Napaka pri shranjevanju poročila. + 'Permission error occurred ': "Napaka pri shranjevanju '{}': " + while saving '{}'.: shranjevanje v to mapo ni dovoljeno. + def `set_instance`: + OWReport.set_instance is deprecated: false + def `get_instance`: + OWReport.get_instance is deprecated: false + _report_window: false +report/report.py: + Report: false + bool_str: false + colored_square: false + plural: false + plural_w: false + clip_string: false + clipped_list: false + get_html_img: false + get_html_section: false + get_html_subsection: false + list_legend: false + render_items: false + render_items_vert: false + class `Report`: + def `show_report`: + Missing Component: Manjkajoča komponenta + 'Orange can not display reports, because your installation ': 'Orange ne more pokazati poročila, ker ne najde ' + contains neither WebEngine nor WebKit.\n\n: komponente WabEngine ali WebKit.\n\n + 'If you installed Orange with conda or pip, try using another ': 'Če ste Orange namestili s conda ali pip, poskusite ' + 'PyQt distribution. ': 'z drugo različico PyQt. ' + 'If you installed Orange with a standard installer, please ': 'Če ste Orange namestili z običajno namestitvijo, ' + report this bug.: nas, prosimo, obvestite o tej napaki. + def `create_report_html`: +
: false +
\n: false +
\n\n: false + def `report_table`: + def `report_abstract_model`: + def `item_html`: + def `data`: + ' ': false + displayText: false + black: false + '#ffffff': false + transparent: false + 'font-weight: bold; ': false + left: false + right: false + center: false + top: false + bottom: false + middle: false + style=": false + color:{fgcolor}; background:{bgcolor}; {weight}: false + text-align:{halign}; vertical-align:{valign};": false + th: false + td: false + <{tag} {style}>{decoration}{value}\n: false + : false + : false + : false + def `report_list`: + {}: false + {}: false + ' \n {}\n': false + "+ {} more + ": "+ še {} +" + \n: false +
: false + def `report_paragraph`: +

{}

: false + def `report_caption`: +

{}

: false + def `plural`: + s: false + 'Plural formed by this function is difficult to translate. ': false + Use orangecanvas.utils.localization.pl instead.: false + def `plural_w`: + s: false + zero: false + one: false + two: false + three: false + four: false + five: false + six: false + seven: false + nine: false + ten: false + def `bool_str`: + Yes: Da + No: Ne + def `clip_string`: + ...: true + def `clipped_list`: + ', ': true + ' ': false + def `get_html_section`: + %a %b %d %y, %H:%M:%S: %d. %m. %y, %H:%M:%S +

{} {}

: false + def `get_html_subsection`: +

{}

: false + def `render_items`: +
    : false + {}: {}
    : false +
: false + def `render_items_vert`: + ', ': false + '{}: {}': false + def `get_html_img`: + utf-8: false + 'max-height: {max_height}px': false + pixel_ratio: false + 'zoom: {1 / ratio:.1f}': false + ' style="{"; ".join(style_opts)}"': false + : false + def `get_icon_html`: + PNG: false + ' width="{}" height="{}"': false + utf-8: false + ': false + def `colored_square`: + '': false + def `list_legend`: + model: false + {}: false +tests/base.py: + setdestroyonexit: false + T: false + def `named_file`: + wt: false + class `_FinishedMonitor`: + def `__init__`: + OWBaseWidget: false + class `DummySignalManager`: + def `get_output`: + Failed to get output in the specified timeout: false + def `wait_for_finished`: + OWBaseWidget: false + class `GuiTest`: + English: Slovenian + def `setUpClass`: + -: false + -widgetcount: false + darwin: false + pyqtgraph: false + def `tearDownClass`: + pyqtgraph: false + exitCleanup: false + def `skipNonEnglish`: + English: false + def `runOnLanguage`: + def `decorator`: + Test is valid only for {lang} release: false + class `WidgetTest`: + def `__init_subclass__`: + def `test_minimum_size`: + widget: false + minimum size not tested as .widget was not set: false + def `test_image_export`: + widget: false + image exporting not tested as .widget was not set: false + def `test_msg_base_class`: + widget: false + msg base class not tested as .widget was not set: false + test_minimum_size: false + test_msg_base_class: false + test_image_export: false + def `setUpClass`: + def `get_instance`: + TRAVIS: false + APPVEYOR: false + get_instance: false + def `tearDownClass`: + _Cls__didCallOnDeleteWidget: false + def `reset_default_settings`: + settingsHandler: false + def `send_signals`: + widget: false + Signals are bound to different widgets: false + def `_send_signal`: + "'{}' is not an input name for widget {}": false + "'send_signal' called but the widget is not ": false + in ready state and does not accept inputs.: false + {} should be {}: false + def `wait_until_finished`: + Did not finish in the specified {timeout}ms timeout: false + def `commit_and_wait`: + now: false + def `get_output`: + widget: false + widget {} has no output {}: false + def `check_msg_base_class`: + Error: false + Warning: false + Information: false + class `TestWidgetTest`: + def `test_get_single_output`: + class `A`: + A: false + class `Inputs`: + Question: false + class `Outputs`: + Answer: false + 6 * 7: false + def `test_compute_lazy_signals`: + class `A`: + A: false + class `Outputs`: + Answer: false + class `BaseParameterMapping`: + def `__init__`: + both: false + def `__str__`: + both: false + %s (%s): false + class `ParameterMapping`: + def `_default_values`: + {} is not supported: false + def `_default_get_value`: + {} is not supported: false + def `_default_set_value`: + {} is not supported: false + def `open_widget_classes`: + __init_subclass__: false + def `override_default_settings`: + wb: false + __main__: false +tests/utils.py: + def `excepthook_catch`: + 'The sys.excepthook that was installed by ': false + "'excepthook_catch' context at enter is not ": false + the one present at exit.: false + class `simulate`: + def `combobox_activate_item`: + {!r} not in {}: false +utils/PDFExporter.py: + class `PDFExporter`: + def `export`: + antialias: false + background: false + painter: false +utils/__init__.py: + def `to_html`: + <=: false + ≤: false + >=: false + ≥: false + <: false + <: false + >: false + >: false + =\\=: false + ≠: false + def `dumpObjectTree`: + {indent}{type} "{name}": false + ' ': false + class `DeprecatedSignal`: + def `__init__`: + Deprecated: false + def `dropdown_popup_geometry`: + down: false + up: false + Invalid 'preferred_direction' ({preferred_direction}): false + def `graphemes`: + utf-16-le: false + def `grapheme_slice`: + negative start or end: false +utils/cache.py: + class `LRUCache`: + __dict: false + __maxlen: false + __miss: false + __hit: false +utils/combobox.py: + class `ComboBox`: + def `_get_size_hint`: + X: true + class `_ComboBoxListDelegate`: + def `paint`: + separator: false + class `ComboBoxSearch`: + def `_get_size_hint`: + X: true + def `showPopup`: + Filter...: true + darwin: false +utils/concurrent.py: + class `FutureRunnable`: + def `run`: + Exception in worker thread.: false + class `FutureWatcher`: + def `setFuture`: + Future already set: false + def `isCancelled`: + isCancelled is deprecated: false + def `isDone`: + isDone is deprecated: false + def `result`: + Future is not yet done: false + def `exception`: + Future is not yet done: false + class `FutureSetWatcher`: + def `__init__`: + Future: false + def `setFutures`: + already set: false + __emitpending: false + doneAll: false + def `flush`: + `flush()` called from a wrong thread.: false + def `wait`: + Futures were not set.: false +utils/filedialogs.py: + def `fix_extension`: + Mismatching extension: Napačna končnica + Extension '{ext}' does not match the chosen file format, {format}.: Končnica '{ext}' se ne ujema z izbrano obliko datoteke, {format}. + \n\nWould you like to fix this?: \n\nPopravim? + Change extension to {suggested_ext}: Spremeni končnico v {suggested_ext} + 'Save as ': 'Shrani kot ' + Back: Nazaj + def `unambiguous_paths`: + {sep}|{re.escape(os.path.altsep)}: false + def `format_filter`: + {} (*{}): false + ' *': false + def `open_filename_dialog_save`: + Save as...: Shrani kot... + def `open_filename_dialog`: + Open...: Odpri... + All readable files (*{}): Vse znane datoteke (*{}) + ' *': false + ;;: false + class `RecentPath`: + def `__init__`: + nt: false + /: false + def `resolve`: + basedir: false + def `__repr__`: + '{0.__class__.__name__}(abspath={0.abspath!r}, ': false + 'prefix={0.prefix!r}, relpath={0.relpath!r}, ': false + title={0.title!r}): false + class `RecentPathsWidgetMixin`: + def `_check_init`: + RecentPathsWidgetMixin.__init__ was not called: false + def `_search_paths`: + basedir: false + class `RecentPathsWComboMixin`: + def `set_file_list`: + (none): (prazno) + def `update_file_list`: + basedir: false +utils/itemdelegates.py: + A: false + class `ModelItemCache`: + __model: false + __cache_data: false + def `__init__`: + LRUCache[ModelItemCache.__KEY, Any]: false + class `CachedDataItemDelegate`: + roles: false + __cache: false + class `StyledItemDelegate`: + def `displayText`: + ' ': false + class `DataDelegate`: + __static_text_lru_cache: false + __pen_lru_cache: false + __style: false + class `BarItemDataDelegate`: + color: false + penWidth: false + barFillRatioRole: false + barColorRole: false + __line: false + __pen: false +utils/itemmodels.py: + def `_as_contiguous_range`: + Non-contiguous range.: false + class `AbstractSortTableModel`: + def `_sortColumnData`: + Data should be 1- or 2-dimensional: false + def `_argsortData`: + mergesort: false + class `PyTableModel`: + def `data`: + {:.{}{}}: false + f: false + e: false + def `_check_sort_order`: + Can't modify PyTableModel when it's sorted: false + class `PyListModel`: + application/x-Orange-PyListModelData: false + def `_is_index_valid`: + Mismatched length of model and its _other_data: false + def `data`: + separator: false + def `flags`: + flags: false + def `__repr__`: + PyListModel(%s): false + def `mimeData`: + 'see properties: _items, _itemdata': false + _items: false + _itemdata: false + def `dropMimeData`: + _items: false + _itemdata: false +utils/listview.py: + class `ListViewFilter`: + def `__init__`: + Filter...: ture + def `setModel`: + 'The model cannot be changed. ': false + Use set_source_model() instead.: false + class `ListViewSearch`: + def `__init__`: + 'ListViewSearch is deprecated and will be removed ': false + in upcoming releases. Use ListViewFilter instead.: false + Filter...: true + def `main`: + A: false + Z: false + {next(c)}{next(c)}{next(c)}{next(c)}: false + __main__: false +utils/matplotlib_export.py: + def `numpy_repr`: + array(: false + ): false + def `numpy_repr_int`: + array(: false + , dtype='int'): false + def `code_with_indices`: + [: false + ]: false + def `scatterplot_code`: + x: false + y: false + size: false + '# data': false + x = {}: false + y = {}: false + '# style': false + sizes = {}: false + pen: false + brush: false + def `do_colors`: + {} = {}: false + {}_index = {}: false + array({})[{}_index]: false + edgecolors: false + facecolors: false + linewidths = {}: false + def `matplotlib_marker`: + t: false + ^: false + t2: false + >: false + t3: false + <: false + star: false + *: false + +: false + P: false + x: false + X: false + symbol: false + indices = {}: false + def `indexed`: + indices: false + plt.scatter(x={}, y={}, s={}, marker={},\n: false + ' facecolors={}, edgecolors={},\n': false + ' linewidths={})': false + sizes: false + **2/4: false + linewidths: false + \n: false + def `scene_code`: + import matplotlib.pyplot as plt: false + from numpy import array: false + plt.clf(): false + bottom: false + plt.xticks: false + plt.xlabel: false + left: false + plt.yticks: false + plt.ylabel: false + {}({}): false + {}({}, {}): false + \n: false +utils/messages.py: + class `UnboundMsg`: + def `__call__`: + Message is not bound: false + def `clear`: + Message is not bound: false + def `is_shown`: + Message is not bound: false + class `MessageGroup`: + def `__init__`: + {}: false + def `add_message`: + {}: false + class `MessagesMixin`: + def `__init__`: + severity: false + class `WidgetMessagesMixin`: + class `Error`: + '#ffc6c6': false + class `Warning`: + '#ffffc9': false + class `Information`: + '#ceceff': false + def `update_message_state`: + def `msg`: + \n: false + def `_warn_obsolete`: + "'{}' with id is deprecated; use {} class": false + def `information`: + information: false + def `warning`: + warning: false + def `error`: + error: false +utils/messagewidget.py: + Message: false + MessagesWidget: false + def `image_data`: + png: false + ascii: false + data:image/png;base64,: false + class `Message`: + Message: false + severity: false + icon: false + text: false + informativeText: false + detailedText: false + textFormat: false + def `asHtml`: + '{}': false +
: false +
: false + : false + ' {text}': false +
: false +
{}
: false +
{}
: false + \n: false + def `summarize`: + {nerrors} {pl(nerrors, 'error')}: {nerrors} {plsi(nerrors, 'napaka')} + {nwarnings} {pl(nwarnings, 'warning')}: {nwarnings} {plsi(nwarnings, 'opozorilo|opozorili|opozorila|opozoril')} + {ninfo} {pl(ninfo, 'message')}: {ninfo} {plsi(ninfo, 'sporočilo|sporočili|sporočila|sporočil')} + {ninfo} other {pl(ninfo, 'message')}: {ninfo} {plsi(ninfo, 'drugo sporočilo|drugi sporočili|druga sporočila|drugih sporočil')} + ' (': true + ', ': true + ): true +
: false + class `MessageWidget`: + def `__init__`: + sizePolicy: false + opacity: false + def `_styled`: + \n{html}: false + class `IconWidget`: + def `__init__`: + sizePolicy: false + def `main`: + Encountered a HCF: false + AAA! It burns.: false + ACHTUNG!: false + '
DAS KOMPUTERMASCHINE IST ': false + NICHT FÜR DER GEFINGERPOKEN
: false + The rain in spain falls mostly on the plain: false + Link: false + I did not do this!: false + The computer made suggestions...: false + ... and the default options was yes.: false + __main__: false +utils/overlay.py: + class `MessageWidget`: + _Button: false + button: false + role: false + stdbutton: false + def `__init__`: + icon-label: false + text-label: false + darwin: false + def `addButton`: + 'Wrong number of arguments for ': false + addButton(QAbstractButton, role): false + addButton(StandardButton): false + Ok: true + Help: Pomoč + addButton(str, ButtonRole): false + darwin: false + def `proxydoc`: + __doc__: false +utils/progressbar.py: + class `ProgressBarMixin`: + def `progressBarInit`: + ' (0% complete)': ' (0% opravljeno)' + def `progressBarSet`: + 'progressBarSet() called without a ': false + preceding progressBarInit(): false + {}:{:02}:{:02}: true + {}:{}:{:02}: true + '{} ({:d}%, ETA: {})': {} ({:d}%, Še: {}) + ' (0% complete)': ' (0% opravljeno)' +utils/saveplot.py: + def `save_plot`: + directories/last_graph_directory: false + directories/last_graph_filter: false + ~: false + Error: Napaka + 'Error occurred while saving file "{}": {}': Napaka pri shranjevanju datoteke "{}": {} + __main__: false +utils/signals.py: + ' +Function for summarizing the input or output data. + +The function must be decorated with `@summarize.register`. It accepts an +argument of arbitrary type and returns a `PartialSummary`, which is a tuple +consisting of two strings: a short summary (usually a number) and details. +': false + ' + +': false + def `can_summarize`: + To silence this warning, set auto_summary of '{name}' to False.: false + 'Output is specified with qualified name ({a_type}). ': false + 'To enable auto summary, set auto_summary to True. ': false + "register 'summarize' function for type {a_type.__name__}. ": false + Closed: false + __doc__: false + Explicit connection closing sentinel.: false + __repr__: false + __str__: false + class `Input`: + def `__call__`: + Input {} is already bound to method {}: false + class `MultiInput`: + def `__init__`: + multiple: false + multiple cannot be set to False: false + def `__get_summary_ids`: + WidgetSignalsMixin: false + def `bound_signal`: + insert_handler is not set: false + remove_handler is not set: false + def `_parse_call_id_arg`: + '`id` parameter is deprecated and will be removed in the ': false + future: false + class `WidgetSignalsMixin`: + def `_bind_signals`: + Inputs: false + Outputs: false + def `send`: + {} is not a valid output signal for widget {}: false + def `convert_signals`: + inputs: false + outputs: false + Inputs: false + Outputs: false + def `_check_input_handlers`: + 'unbound signal(s) in {}: {}': false + ', ': false + 'missing handlers in {}: {}': false + def `_check_ids_unique`: + input: false + output: false + s: false + 'Name {signal.name} refers to different {direction} ': false + signals of {cls.__name__}: false + 'Id {signal.id} refers to different {direction} ': false + '{name} appears as a name and an id of two different ': false + {direction} signals in {cls.__name__}: false + def `_update_summary`: + def `format_short`: + -: true + 'summary must be None, string or int; ': false + got {type(summary).__name__}: false + def `format_detail`: + -: true + def `join_multiples`: + -: true + ' ': false +
: false + ' | ': true +
: false + ': false +
{name}: ': false + {detail}
: false + def `show_preview`: +
: false + ': false +
{name}: ': false + -
: false + ' ({i})': false + -: false + '{name}{part_no}: ': false + {detail}: false + def `get_input_meta`: + inputs: false + def `get_widget_inputs`: + _WidgetSignalsMixin__input_state: false +utils/visual_settings_dlg.py: + class `SettingsDialog`: + def `__init__`: + Visual Settings: Vizualne nastavitve + def `__add_row`: + {label}:: false + class `VisualSettingsDialog`: + def `__init__`: + visual_settings: false + def `_`: + '{key[-1]} ': false + __main__: false + Foo: false + Bar: false + Baz: false + Foo Bar: false + Foo Baz: false + Bar Baz: false + Box 1: false + Item 1: false + Parameter 1: false + Parameter 2: false + Parameter 3: false + Item 2: false + Item 3: false + Box 2: false + Item 4: false + Item 5: false + Item 6: false + Open dialog: false +utils/webview.py: + _webview: false + helpers.js: false + init-webengine-webchannel.js: false + ORANGE_DEBUG: false + class `WebEngineView`: + __ORANGE_: false + def `__init__`: + QTWEBENGINE_REMOTE_DEBUGGING: false + 12088: false + 'To debug QWebEngineView, set environment variable ': false + 'QTWEBENGINE_REMOTE_DEBUGGING={port} and then visit ': false + 'http://127.0.0.1:{port}/ in a Chromium-based browser. ': false + 'See https://doc.qt.io/qt-5/qtwebengine-debugging.html ': false + This has also been done for you.: false + utf-8: false + webview_helpers: false + ://qtwebchannel/qwebchannel.js: false + webchannel_init: false + ://qtwebchannel/qwebchannel.js is not readable.: false + ;window.__load_finished = true;: false + load_finished: false + "Don't expose QWidgets in WebView. Construct minimal ": false + QObjects instead.: false + pybridge: false + __bridge: false + def `_onloadJS`: + script_: false + class `WebKitView`: + def `__init__`: + "Don't expose QWidgets in WebView. Construct minimal ": false + QObjects instead.: false + def `_onload`: + pybridge: false + utf-8: false + def `_to_primitive_types`: + "Don't expose QWidgets in WebView. Construct minimal ": false + QObjects instead.: false + 'object must consist of primitive types ': false + '(allowed: int, float, str, bool, list, ': false + 'dict, set, numpy.ndarray, ...). Type is: ': false + class `_WebViewBase`: + def `evalJS`: + def `_later`: + \n;: false + ;0;: false + def `svg`: + ': false + def `toFileURL`: + file:: false + class `_JSObject`: + def `pop_object`: + QVariantMap: false + class `WebviewWidget`: + def `__init__`: + __js_object_channel: false + def `html`: + _html: false + class `_JSObjectChannel`: + QVariantMap: false + def `send_object`: + "QWebChannel doesn't transmit QObject instances. If you ": false + 'need a QObject available in JavaScript, pass it as a ': false + bridge in WebviewWidget constructor.: false +utils/widgetpreview.py: + class `WidgetPreview`: + def `_find_handler_name`: + inputs: false + no signal handlers for '{chunk_type}': false + multiple signal handlers for '{chunk_type}': false +workflow/config.py: + https://orange.biolab.si/addons/list: false + orange.widgets: false + class `Config`: + biolab.si: false + Orange Canvas: false + 0.0.0: false + def `core_packages`: + orange-widget-base: false + def `examples_entry_points`: + orange.widgets.tutorials: false + def `data_dir`: + Orange: false + 0.0.0: false + def `cache_dir`: + "'{__name__}.cache_dir' is deprecated.": false + Orange: false + 0.0.0: false + def `log_dir`: + "'{__name__}.log_dir' is deprecated.": false + darwin: false + Orange: false + ~/Library/Logs: false + def `widget_settings_dir`: + "'{__name__}.widget_settings_dir' is deprecated.": false +workflow/discovery.py: + def `widget_desc_from_module`: + get_widget_description: false +workflow/drophandler.py: + OWNodeFromMimeDataDropHandler: false + SingleUrlDropHandler: false + UrlsDropHandler: false + SingleFileDropHandler: false + FilesDropHandler: false + class `SingleUrlDropHandler`: + def `canDropMimeData`: + SchemeEditWidget: false + QMimeData: false + def `parametersFromMimeData`: + SchemeEditWidget: false + QMimeData: false + Dict[str, Any]: false + def `parametersFromUrl`: + Dict[str, Any]: false + class `UrlsDropHandler`: + def `canDropMimeData`: + SchemeEditWidget: false + QMimeData: false + def `parametersFromMimeData`: + SchemeEditWidget: false + QMimeData: false + Dict[str, Any]: false + def `parametersFromUrls`: + Dict[str, Any]: false + class `SingleFileDropHandler`: + def `canDropMimeData`: + SchemeEditWidget: false + QMimeData: false + def `parametersFromMimeData`: + SchemeEditWidget: false + QMimeData: false + Dict[str, Any]: false + def `parametersFromFile`: + Dict[str, Any]: false + class `FilesDropHandler`: + def `canDropMimeData`: + SchemeEditWidget: false + QMimeData: false + def `parametersFromMimeData`: + SchemeEditWidget: false + QMimeData: false + Dict[str, Any]: false + def `parametersFromFiles`: + Dict[str, Any]: false +workflow/errorreporting.py: + https://service.biolab.si/error_report/: false + def `get_installed_distributions`: + Unknown: false + {name}=={version}: false + class `ErrorReporting`: + class `DataField`: + Exception: false + Module: false + Widget Name: false + Widget Module: false + Version: false + Environment: false + Installed Packages: false + Machine ID: false + Widget Scheme: false + Stack Trace: false + Local Variables: false + def `__init__`: + Unexpected Error: Nepričakovana napaka + The program encountered an unexpected error. Please
: Program je naletel na nepričakovano napako. Prosimo
+ report it anonymously to the developers.

: za anonimno poročilo razvijalcem.

+ The following data will be reported:: Poslani bodo naslednji podatki: + Monospace: false + def `_reload_text`: + error-reporting/add-scheme: false + : false + _: false + {}: false + file:: false + \n: false +
: false + ' ': false +  : false + : false +
{}:{}
: false + Include workflow (data will NOT be transmitted): Pripni delotok (podatki NE BODO poslani) + error-reporting/add-scheme: false + Send Report (Thanks!): Pošlji poročilo (Hvala!) + Don't Send: Ne pošiljaj + def `accept`: + error-reporting/add-scheme: false + def `_post_report`: + utf8: false + Error reporting failed: false + def `handle_exception`: + sys.excepthook: false + {}:{}: false + __name__: false + def `_find_widget_frame`: + self: false + self: false + ', ': false + error-reporting/machine-id: false + Error Encountered: Napaka + Error encountered{}:

{}: Napaka{}:

{} + ' in widget {}': ' v gradniku {}' + \n: false +
: false + ' ': false +  : false + ows-: false + .ows.xml: false + wb: false + utf-8: false + Python {} on {} {} {} {}: false +workflow/mainwindow.py: + class `OWCanvasMainWindow`: + def `__init__`: + Show report: Pokaži poročilo + action-show-report: false + Show a report window: Pokaži okno s poročilom + Shift+R: false + Open Report...: Odpri poročilo... + action-open-report: false + Open a saved report: Odpri shranjeno poročilo... + Reset Widget Settings...: Pobriši nastavitve gradnikov... + file-menu: false + open-actions-separator: false + view-menu: false + view-visible-actions-separator: false + options-menu: false + canvas-addons-action: false + def `open_report`: + report/file-dialog-dir: false + Open Report: Odpri poročilo + Report (*.report): Poročilo (*.report) + def `reset_widget_settings`: + Orange: true + Clear settings: Pobriši shranjene nastavitve + {name} needs to be restarted for the changes to take effect.: Da se nastavitve pobrišejo, se mora {name} ponovno zagnati. + Press OK to restart {name} now.: Pritisnite OK za ponovni zagon. + DELETE_ON_START: false + a: false + def `restart`: + Restart Cancelled: Ponovni zagon preklican + Settings will be reset on {name}'s next restart: Nastavitve bodo ponastavljene ob naslednjem ponovnem zagonu + def `ask_save_report`: + Report window: Shranjevanje poročila + The report contains unsaved changes.: Poročilo vsebuje neshranjene spremembe. + Would you like to save the report?: Želite shraniti poročilo? +workflow/utils.py: + T: false +workflow/widgetsscheme.py: + class `WidgetsScheme`: + def `sync_node_properties`: + 'Scheme node properties sync (changed: %s)': false + class `OWWidgetManager`: + def `delete_widget_for_node`: + 'A widget/node {0} was removed while being initialized. ': false + 'This is most likely a result of an explicit ': false + 'QApplication.processEvents call from the ': false + "'{1.__module__}.{1.__qualname__}' ": false + widget's __init__.: false + 'Destroyed namespace for: %s': false + def `__delete_item`: + 'Widget %s removed but still in state :%s. ': false + Deferring deletion.: false + def `__try_delete`: + Delayed delete for widget %s: false + def `create_widget_instance`: + "WidgetManager: Creating '%s.%s' instance '%s'.": false + action-help: false + def `__on_help_request`: + help://search?: false + id: false + def `__on_processing_started`: + MUST NOT re-enter: false + def `__on_widget_state_changed`: + State change for a non-tracked widget {}: false + def `actions_for_context_menu`: + ext-workflow-node-menu-action: false + Show settings: false + show-settings: false + Show widget settings: false + def `user_message_from_state`: + {0.__name__}.{0.__qualname__}: false +
: false + content-type: false + text/html: false + class `WidgetsSignalManager`: + def `send`: + Node for '%s' (%s.%s) is not in the scheme.: false + %r is not valid signal name for %r: false + def `_parse_call_signal_id`: + "'signal_id' parameter is deprecated": false + def `get_widget_input_signals`: + _OWBaseWidget__process_signal_input: false diff --git a/i18n/si/trubar-config.yaml b/i18n/si/trubar-config.yaml new file mode 100644 index 000000000..2d1c8ff0c --- /dev/null +++ b/i18n/si/trubar-config.yaml @@ -0,0 +1,2 @@ +auto-import: "from orangecanvas.utils.localization.si import plsi, plsi_sz, z_besedo # pylint: disable=wrong-import-order" +encoding: "utf-8" diff --git a/i18n/trans.sh b/i18n/trans.sh new file mode 100755 index 000000000..6d0e5c077 --- /dev/null +++ b/i18n/trans.sh @@ -0,0 +1,8 @@ +if [ "$#" -ne 2 ] +then + echo "Usage: trans " +else + lang=$1 + dest=$2 + trubar --conf $lang/trubar-config.yaml translate -s ../orangewidget -d $dest/orangewidget $lang/msgs.jaml +fi From 499df3a0abc13a6be3bca1ce66a01d68392d1a03 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Wed, 24 Jan 2024 13:54:18 +0100 Subject: [PATCH 26/62] gui.spinBox: Remove extraneous created widget box --- orangewidget/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orangewidget/gui.py b/orangewidget/gui.py index cadf7e76e..7a0808c32 100644 --- a/orangewidget/gui.py +++ b/orangewidget/gui.py @@ -773,7 +773,7 @@ def spin(widget, master, value, minv, maxv, step=1, box=None, label=None, else: b = widget hasHBox = False - if not hasHBox and (checked or callback or posttext): + if not hasHBox and (checked or posttext): bi = hBox(b, addToLayout=False) else: bi = b From 15933ddc612c1b7d7255d6ce282903a4bea86518 Mon Sep 17 00:00:00 2001 From: PrimozGodec Date: Fri, 26 Jan 2024 09:32:19 +0100 Subject: [PATCH 27/62] Use test-translations workflow from common repository --- .github/workflows/test-translations.yml | 53 ++----------------------- 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test-translations.yml b/.github/workflows/test-translations.yml index fe598eef1..18837bc15 100644 --- a/.github/workflows/test-translations.yml +++ b/.github/workflows/test-translations.yml @@ -9,52 +9,7 @@ on: - master jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - - name: Install Trubar - run: | - pip install trubar - - - name: Collect translations - run: | - cd i18n - trubar collect -s ../orangewidget new.jaml - trubar merge -u Obsolete.jaml si/msgs.jaml new.jaml - trubar missing -o Missing.jaml new.jaml - if [ ! -s Missing.jaml ] - then - rm Missing.jaml - fi - - - name: Check Slovenian translations - run: | - cd i18n - for fn in Obsolete Missing - do - if [ -f $fn.jaml ] - then - echo "::group::$fn translations" - cat $fn.jaml - echo "::endgroup::" - fi - done - echo "-------------------------------" - trubar stat new.jaml - echo "-------------------------------" - if [ -f Missing.jaml ] || [ -f Obsolete.jaml ] - then - echo "### Missing or obsolete translations" >> $GITHUB_STEP_SUMMARY - echo "::error::Missing or obsolete translations" - echo "If you wish to fix this, see orange3/i18n/README.md" - exit 1 - else - echo "### All translations are up to date" >> $GITHUB_STEP_SUMMARY - fi + test-translations: + uses: biolab/orange-ci-cd/.github/workflows/test-translations.yml@master + with: + package-dir: orangewidget From 94bb8d1a06df8d5500f4723be9ae029c119aebad Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Fri, 9 Feb 2024 11:17:24 +0100 Subject: [PATCH 28/62] ci: Update various action versions --- .github/workflows/run-docs-build.yml | 6 +++--- .github/workflows/run-tests-workflow.yml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run-docs-build.yml b/.github/workflows/run-docs-build.yml index c5fa85c84..a1221ab77 100644 --- a/.github/workflows/run-docs-build.yml +++ b/.github/workflows/run-docs-build.yml @@ -19,14 +19,14 @@ jobs: python: 3.8 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Setup Pip Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .pip-cache key: ${{ runner.os }}-py-${{ matrix.python }}-pip-${{ hashFiles('setup.*', '.github/workflows/run-docs-build.yml') }} diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index f64e4fb40..511f7c038 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -103,9 +103,9 @@ jobs: test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -119,7 +119,7 @@ jobs: sudo apt-get install -y libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0 $PACKAGES - name: Setup Pip Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .pip-cache key: ${{ runner.os }}-py-${{ matrix.python-version }}-pip-${{ hashFiles('setup.*', '.github/workflows/run-tests-workflow.yml') }} @@ -154,6 +154,6 @@ jobs: coverage xml - name: Upload Coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: - fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} From b792eb8c52c188b19a494aab103b985bdb579f4c Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Thu, 15 Feb 2024 11:37:15 +0100 Subject: [PATCH 29/62] WidgetTest: Pass extra kwargs in create_widget to the __new__ --- orangewidget/tests/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/orangewidget/tests/base.py b/orangewidget/tests/base.py index 5367cb465..9a09036e2 100644 --- a/orangewidget/tests/base.py +++ b/orangewidget/tests/base.py @@ -309,8 +309,8 @@ def tearDown(self): self.signal_manager.clear() super().tearDown() - def create_widget(self, cls, stored_settings=None, reset_default_settings=True): - # type: (Type[T], Optional[dict], bool) -> T + def create_widget(self, cls: Type[T], stored_settings: Optional[dict]=None, + reset_default_settings=True, **kwargs) -> T: """Create a widget instance using mock signal_manager. When used with default parameters, it also overrides settings stored @@ -350,7 +350,7 @@ def onDeleteWidget(self): if reset_default_settings: self.reset_default_settings(Cls) widget = Cls.__new__(Cls, signal_manager=self.signal_manager, - stored_settings=stored_settings) + stored_settings=stored_settings, **kwargs) widget.__init__() self.process_events() self.widgets.append(widget) From 29a6da880fd66615c0134fb3fb67f0af4290c107 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 14 May 2024 19:31:00 +0200 Subject: [PATCH 30/62] ListViewFilter: Retain selection when filtering (#270) ListViewFilter: Retain selection when filtering --- orangewidget/utils/listview.py | 25 ++++++++++++++++++++++- orangewidget/utils/tests/test_listview.py | 15 ++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/orangewidget/utils/listview.py b/orangewidget/utils/listview.py index dda8e0836..3d9f43306 100644 --- a/orangewidget/utils/listview.py +++ b/orangewidget/utils/listview.py @@ -10,8 +10,11 @@ QSortFilterProxyModel, QItemSelection, QSize, + QItemSelectionModel, ) +from orangewidget.utils.itemmodels import signal_blocking + class ListViewFilter(QListView): """ @@ -27,6 +30,7 @@ def __init__( **kwargs ): super().__init__(*args, **kwargs) + self.__selection = QItemSelection() self.__search = QLineEdit(self, placeholderText="Filter...") self.__search.textEdited.connect(self.__on_text_edited) self.__preferred_size = preferred_size @@ -40,9 +44,28 @@ def __init__( assert isinstance(proxy, QSortFilterProxyModel) super().setModel(proxy) self.set_source_model(model) + self.selectionModel().selectionChanged.connect(self.__on_sel_changed) + + def __on_sel_changed( + self, + selected: QItemSelection, + deselected: QItemSelection + ): + selected = self.model().mapSelectionToSource(selected) + deselected = self.model().mapSelectionToSource(deselected) + self.__selection.merge(selected, QItemSelectionModel.Select) + self.__selection.merge(deselected, QItemSelectionModel.Deselect) + self.__select() def __on_text_edited(self, string: str): - self.model().setFilterFixedString(string) + with signal_blocking(self.selectionModel()): + self.model().setFilterFixedString(string) + self.__select() + + def __select(self): + selection = self.model().mapSelectionFromSource(self.__selection) + self.selectionModel().select(selection, + QItemSelectionModel.ClearAndSelect) def setModel(self, _): raise TypeError("The model cannot be changed. " diff --git a/orangewidget/utils/tests/test_listview.py b/orangewidget/utils/tests/test_listview.py index b43ef8340..aeb91730b 100644 --- a/orangewidget/utils/tests/test_listview.py +++ b/orangewidget/utils/tests/test_listview.py @@ -134,6 +134,21 @@ def test_set_proxy(self): def test_set_proxy_raises(self): self.assertRaises(Exception, ListViewFilter, proxy=PyListModel()) + def test_selection(self): + view = ListViewFilter() + view.model().setSourceModel(PyListModel(["one", "two", "three"])) + sel_model = view.selectionModel() + selected_row = 0 + index = view.model().index(selected_row, 0) + sel_model.select(index, sel_model.ClearAndSelect) + self.assertEqual(sel_model.selectedRows(0)[0].row(), selected_row) + + view._ListViewFilter__search.textEdited.emit("two") + self.assertEqual(len(sel_model.selectedRows(0)), 0) + + view._ListViewFilter__search.textEdited.emit("") + self.assertEqual(sel_model.selectedRows(0)[0].row(), selected_row) + if __name__ == "__main__": unittest.main() From 2165c23b5bfa8454f6f12eb4a76c253fae64aeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Ho=C4=8Devar?= Date: Fri, 17 May 2024 17:14:41 +0200 Subject: [PATCH 31/62] Remove window help button (#271) --- orangewidget/widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/orangewidget/widget.py b/orangewidget/widget.py index 3a614683a..b26c486e8 100755 --- a/orangewidget/widget.py +++ b/orangewidget/widget.py @@ -314,6 +314,9 @@ def __new__(cls, *args, captionTitle=None, **kwargs): WidgetMessagesMixin.__init__(self) WidgetSignalsMixin.__init__(self) + # disable window help button everywhere + self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) + # handle deprecated left_side_scrolling if hasattr(self, 'left_side_scrolling'): warnings.warn( From 5e73980630aa22a50e5672db3a9ff8463301789a Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 27 May 2024 12:51:31 +0200 Subject: [PATCH 32/62] Release version 4.24.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bc222e706..a0b15d9fd 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ NAME = 'orange-widget-base' VERSION = '4.24.0' -ISRELEASED = False +ISRELEASED = True # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From 326bed156fb73afd5971374b7c56b7bac75e0214 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 27 May 2024 12:52:43 +0200 Subject: [PATCH 33/62] Bump version to 4.25.0.dev0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a0b15d9fd..a54333dd6 100755 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ from setuptools import setup, find_packages, Command NAME = 'orange-widget-base' -VERSION = '4.24.0' -ISRELEASED = True +VERSION = '4.25.0' +ISRELEASED = False # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From ef2eb36df4884bce9e0df7c90edac0aabae42139 Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 6 Jun 2024 18:38:30 +0200 Subject: [PATCH 34/62] WidgetMessagesMixin: Fix message binding to not expect class attributes to have --- orangewidget/utils/messages.py | 7 ++++++- orangewidget/utils/tests/test_messages.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/orangewidget/utils/messages.py b/orangewidget/utils/messages.py index 961a4c58e..5c804a116 100644 --- a/orangewidget/utils/messages.py +++ b/orangewidget/utils/messages.py @@ -192,9 +192,14 @@ def bind_subgroup(subgroup, widget_class): msg = msg.bind(self, widget_class) self.__dict__[name] = msg + # Iterate through all base classes of this message group. + # Among the widgets in mro, find the one that defined this class + # and bind all messages in the group to that class. + # This is needed so that each message has its "owner" and that `clear` + # method can clear only messages from the owner (see method `clear`) for group in type(self).mro(): for widget_class in type(self.widget).mro(): - if group in widget_class.__dict__.values(): + if widget_class.__dict__.get(group.__name__) is group: break else: # MessageGroups outside widget classes (e.g. mixins) diff --git a/orangewidget/utils/tests/test_messages.py b/orangewidget/utils/tests/test_messages.py index 8b6ac10cb..a3cc82b99 100644 --- a/orangewidget/utils/tests/test_messages.py +++ b/orangewidget/utils/tests/test_messages.py @@ -35,6 +35,19 @@ class Error(WidgetA.Error): self.assertFalse(w.Error.err_a.is_shown()) self.assertTrue(w.Error.err_b.is_shown()) + def test_numpy_class_attributes(self): + # There used to be a bug where numpy class attributes crash message + # binding it expected them to have __eq__ method (see other changes in + # this commit). This test is to make sure the problem doesn't resurface. + class Neq: + def __eq__(self, other): + raise NotImplementedError + + class WidgetA(OWBaseWidget, openclass=True): + a = Neq() + + self.create_widget(WidgetA) + if __name__ == "__main__": unittest.main() From 1d621d01dff28bcf7802db6c964090cc63df462e Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 20 Jun 2024 10:38:58 +0000 Subject: [PATCH 35/62] ListViewFilter: Fix selection (#274) --- orangewidget/utils/listview.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/orangewidget/utils/listview.py b/orangewidget/utils/listview.py index 3d9f43306..1a5838594 100644 --- a/orangewidget/utils/listview.py +++ b/orangewidget/utils/listview.py @@ -11,6 +11,7 @@ QItemSelection, QSize, QItemSelectionModel, + Signal, ) from orangewidget.utils.itemmodels import signal_blocking @@ -20,6 +21,7 @@ class ListViewFilter(QListView): """ A QListView with implicit and transparent row filtering. """ + sigSelectionChanged = Signal(object) def __init__( self, @@ -46,6 +48,10 @@ def __init__( self.set_source_model(model) self.selectionModel().selectionChanged.connect(self.__on_sel_changed) + @property + def selection(self) -> QItemSelection: + return self.__selection + def __on_sel_changed( self, selected: QItemSelection, @@ -53,9 +59,11 @@ def __on_sel_changed( ): selected = self.model().mapSelectionToSource(selected) deselected = self.model().mapSelectionToSource(deselected) + if self.selectionMode() == QListView.SelectionMode.SingleSelection: + self.__selection.clear() self.__selection.merge(selected, QItemSelectionModel.Select) self.__selection.merge(deselected, QItemSelectionModel.Deselect) - self.__select() + self.sigSelectionChanged.emit(self.__selection) def __on_text_edited(self, string: str): with signal_blocking(self.selectionModel()): From e75bdf8366fca954089250c24e24052a797c80a2 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Tue, 16 Jul 2024 12:46:10 +0200 Subject: [PATCH 36/62] ci: Replace deprecated macos-11 GH runners --- .github/workflows/run-tests-workflow.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 511f7c038..8cac0221d 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -49,27 +49,27 @@ jobs: extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" # macOS - - os: macos-11 + - os: macos-12 python-version: 3.8 test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" - - os: macos-11 + - os: macos-12 python-version: 3.9 test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" - - os: macos-12 + - os: macos-13 python-version: "3.10" test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" - - os: macos-12 + - os: macos-13 python-version: "3.11" test-env: "PyQt6~=6.2.3 PyQt6-Qt6~=6.2.3 PyQt6-WebEngine~=6.2.1 PyQt6-WebEngine-Qt6~=6.2.1" - - os: macos-12 + - os: macos-latest python-version: "3.11" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" - - os: macos-12 + - os: macos-latest python-version: "3.12" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" From 513f8940f8ae819991a28bef1ef9aee9ff76b816 Mon Sep 17 00:00:00 2001 From: Ales Erjavec Date: Thu, 20 Jun 2024 23:19:52 +0200 Subject: [PATCH 37/62] Numpy 2.0 compatibility Numpy 2.0 changes scalar repr as per: https://numpy.org/neps/nep-0051-scalar-representation.html Make matplotlib_export convert to python scalars where appropriate to avoid 'np.*' in generated code. --- orangewidget/tests/test_matplotlib_export.py | 23 ++++++++++++++++++-- orangewidget/utils/matplotlib_export.py | 15 ++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/orangewidget/tests/test_matplotlib_export.py b/orangewidget/tests/test_matplotlib_export.py index dc7b27e4b..3a0f073e7 100644 --- a/orangewidget/tests/test_matplotlib_export.py +++ b/orangewidget/tests/test_matplotlib_export.py @@ -1,7 +1,10 @@ +import numpy as np import pyqtgraph as pg from orangewidget.tests.base import GuiTest -from orangewidget.utils.matplotlib_export import scatterplot_code +from orangewidget.utils.matplotlib_export import ( + scatterplot_code, numpy_repr, compress_if_all_same, numpy_repr_int +) def add_intro(a): @@ -15,8 +18,24 @@ class TestScatterPlot(GuiTest): def test_scatterplot_simple(self): plotWidget = pg.PlotWidget(background="w") scatterplot = pg.ScatterPlotItem() - scatterplot.setData(x=[1, 2, 3], y=[3, 2, 1]) + scatterplot.setData( + x=np.array([1., 2, 3]), + y=np.array([3., 2, 1]), + size=np.array([1., 1, 1]) + ) plotWidget.addItem(scatterplot) code = scatterplot_code(scatterplot) self.assertIn("plt.scatter", code) exec(add_intro(code), {}) + + def test_utils(self): + a = np.array([1.5, 2.5]) + self.assertIn("1.5, 2.5", numpy_repr(a)) + a = np.array([1, 1]) + v = compress_if_all_same(a) + self.assertEqual(v, 1) + self.assertEqual(repr(v), "1") + self.assertIs(type(v), int) + a = np.array([1, 2], dtype=int) + v = numpy_repr_int(a) + self.assertIn("1, 2", v) diff --git a/orangewidget/utils/matplotlib_export.py b/orangewidget/utils/matplotlib_export.py index f82a07feb..1bccf470a 100644 --- a/orangewidget/utils/matplotlib_export.py +++ b/orangewidget/utils/matplotlib_export.py @@ -14,7 +14,7 @@ def numpy_repr(a): # avoid numpy repr as it changes between versions # TODO handle numpy repr differences if isinstance(a, np.ndarray): - return "array(" + repr(list(a)) + ")" + return "array(" + repr(a.tolist()) + ")" try: np.set_printoptions(threshold=10**10) return repr(a) @@ -25,12 +25,20 @@ def numpy_repr(a): def numpy_repr_int(a): # avoid numpy repr as it changes between versions # TODO handle numpy repr differences - return "array(" + repr(list(a)) + ", dtype='int')" + if isinstance(a, np.ndarray): + a = a.tolist() + else: + a = list(a) + return "array(" + repr(a) + ", dtype='int')" def compress_if_all_same(l): s = set(l) - return s.pop() if len(s) == 1 else l + if len(s) == 1: + v = s.pop() + return v.item() if isinstance(v, np.generic) else v + else: + return l def is_sequence_not_string(a): @@ -188,6 +196,7 @@ def scene_code(scene): code = [] code.append("import matplotlib.pyplot as plt") + code.append("import numpy as np") code.append("from numpy import array") code.append("") From 511f4be4a5e8d52d0f3852192d49973229bdb56c Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 19 Sep 2024 17:17:24 +0200 Subject: [PATCH 38/62] open_filename_dialog: Allow reading *.* --- i18n/si/msgs.jaml | 3 ++ orangewidget/utils/filedialogs.py | 8 +++- orangewidget/utils/tests/test_filedialogs.py | 48 ++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index 7565c2b74..37f34c5df 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -930,6 +930,8 @@ utils/filedialogs.py: Save as...: Shrani kot... def `open_filename_dialog`: Open...: Odpri... + *: false + All files (*.*): Vse datoteke (*.*) All readable files (*{}): Vse znane datoteke (*{}) ' *': false ;;: false @@ -1080,6 +1082,7 @@ utils/matplotlib_export.py: \n: false def `scene_code`: import matplotlib.pyplot as plt: false + import numpy as np: false from numpy import array: false plt.clf(): false bottom: false diff --git a/orangewidget/utils/filedialogs.py b/orangewidget/utils/filedialogs.py index 5abfc8e23..a1f89ed8f 100644 --- a/orangewidget/utils/filedialogs.py +++ b/orangewidget/utils/filedialogs.py @@ -162,7 +162,8 @@ def open_filename_dialog(start_dir: str, start_filter: str, file_formats, start_dir (str): initial directory, optionally including the filename start_filter (str): initial filter file_formats (a list of FileFormat): file formats - add_all (bool): add a filter for all supported extensions + add_all (bool, default True): add a filter for all supported extensions. + If set to `*`, show an option to read all files, *.*. title (str): title of the dialog dialog: a function that creates a QT dialog Returns: @@ -172,7 +173,10 @@ def open_filename_dialog(start_dir: str, start_filter: str, file_formats, filters = [format_filter(f) for f in file_formats] # add all readable files option - if add_all: + if add_all == "*": + file_formats.insert(0, None) + filters.insert(0, "All files (*.*)") + elif add_all and len(file_formats) > 1: all_extensions = set() for f in file_formats: all_extensions.update(f.EXTENSIONS) diff --git a/orangewidget/utils/tests/test_filedialogs.py b/orangewidget/utils/tests/test_filedialogs.py index aca2a29e5..9454b9ef3 100644 --- a/orangewidget/utils/tests/test_filedialogs.py +++ b/orangewidget/utils/tests/test_filedialogs.py @@ -132,6 +132,54 @@ class ABCFormat: self.assertEqual(file_format, None) self.assertEqual(file_filter, None) + def test_add_all(self): + class ABCFormat: + EXTENSIONS = ('.abc', '.jkl') + DESCRIPTION = 'abc file' + PRIORITY = 30 + + class DEFFormat: + EXTENSIONS = ('.def', ) + DESCRIPTION = 'def file' + PRIORITY = 40 + + # Add all known extensions + dialog = Mock(return_value=("foo.abc", "")) + open_filename_dialog(".", "", [ABCFormat, DEFFormat], dialog=dialog) + self.assertIn("(*.abc *.def *.jkl)", dialog.call_args[0][3]) + self.assertIn("(*.abc *.jkl)", dialog.call_args[0][3]) + self.assertIn("(*.def)", dialog.call_args[0][3]) + + # Add all extensions (*.*) + dialog = Mock(return_value=("foo.abc", "")) + open_filename_dialog(".", "", [ABCFormat, DEFFormat], + add_all="*", dialog=dialog) + self.assertIn("(*.*)", dialog.call_args[0][3]) + self.assertIn("(*.abc *.jkl)", dialog.call_args[0][3]) + self.assertIn("(*.def)", dialog.call_args[0][3]) + + # Don't add any extensions + dialog = Mock(return_value=("foo.abc", "")) + open_filename_dialog(".", "", [ABCFormat, DEFFormat], + add_all=False, dialog=dialog) + self.assertNotIn("(*.abc *.def *.jkl)", dialog.call_args[0][3]) + self.assertIn("(*.abc *.jkl)", dialog.call_args[0][3]) + self.assertIn("(*.def)", dialog.call_args[0][3]) + + # With a single format, add all known extensions is ignored + dialog = Mock(return_value=("foo.abc", "")) + open_filename_dialog(".", "", [ABCFormat], + dialog=dialog) + self.assertNotIn("(*.*)", dialog.call_args[0][3]) + self.assertEqual(dialog.call_args[0][3].count("(*.abc *.jkl)"), 1) + + # With a single format, add all extensions (*.*) still applies + dialog = Mock(return_value=("foo.abc", "")) + open_filename_dialog(".", "", [ABCFormat], + add_all="*", dialog=dialog) + self.assertIn("(*.*)", dialog.call_args[0][3]) + self.assertIn("(*.abc *.jkl)", dialog.call_args[0][3]) + if __name__ == "__main__": unittest.main() From 7c6f18952ad3e04f1170fd4899918f5896322f79 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 20 Sep 2024 11:04:29 +0200 Subject: [PATCH 39/62] open_filename_dialog: gix docs: --- orangewidget/utils/filedialogs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orangewidget/utils/filedialogs.py b/orangewidget/utils/filedialogs.py index a1f89ed8f..b1bc2909d 100644 --- a/orangewidget/utils/filedialogs.py +++ b/orangewidget/utils/filedialogs.py @@ -162,8 +162,8 @@ def open_filename_dialog(start_dir: str, start_filter: str, file_formats, start_dir (str): initial directory, optionally including the filename start_filter (str): initial filter file_formats (a list of FileFormat): file formats - add_all (bool, default True): add a filter for all supported extensions. - If set to `*`, show an option to read all files, *.*. + add_all (False, True, or `"*"`; default True): add a filter for all supported extensions. + If set to `"*"`, show an option to read all files, *.*. title (str): title of the dialog dialog: a function that creates a QT dialog Returns: From 7ddb5e1129d60eb8d336e75d346781a80f4321aa Mon Sep 17 00:00:00 2001 From: janezd Date: Tue, 11 Jun 2024 18:00:26 +0200 Subject: [PATCH 40/62] Multilingual configuration --- i18n/si/msgs.jaml | 2 +- i18n/si/trubar-config.yaml | 2 -- i18n/trans.sh | 10 +++++----- i18n/trubar-config.yaml | 13 +++++++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) delete mode 100644 i18n/si/trubar-config.yaml create mode 100644 i18n/trubar-config.yaml diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index 37f34c5df..c66b4fed6 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -624,7 +624,7 @@ report/report.py: def `show_report`: Missing Component: Manjkajoča komponenta 'Orange can not display reports, because your installation ': 'Orange ne more pokazati poročila, ker ne najde ' - contains neither WebEngine nor WebKit.\n\n: komponente WabEngine ali WebKit.\n\n + contains neither WebEngine nor WebKit.\n\n: komponente WebEngine ali WebKit.\n\n 'If you installed Orange with conda or pip, try using another ': 'Če ste Orange namestili s conda ali pip, poskusite ' 'PyQt distribution. ': 'z drugo različico PyQt. ' 'If you installed Orange with a standard installer, please ': 'Če ste Orange namestili z običajno namestitvijo, ' diff --git a/i18n/si/trubar-config.yaml b/i18n/si/trubar-config.yaml deleted file mode 100644 index 2d1c8ff0c..000000000 --- a/i18n/si/trubar-config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -auto-import: "from orangecanvas.utils.localization.si import plsi, plsi_sz, z_besedo # pylint: disable=wrong-import-order" -encoding: "utf-8" diff --git a/i18n/trans.sh b/i18n/trans.sh index 6d0e5c077..8a314a943 100755 --- a/i18n/trans.sh +++ b/i18n/trans.sh @@ -1,8 +1,8 @@ -if [ "$#" -ne 2 ] +if [ "$#" -ne 1 ] then - echo "Usage: trans " + echo "trans " + exit else - lang=$1 - dest=$2 - trubar --conf $lang/trubar-config.yaml translate -s ../orangewidget -d $dest/orangewidget $lang/msgs.jaml + dest=$1 + trubar --conf trubar-config.yaml translate -s ../orangewidget -d $dest/orangewidget msgs.jaml fi diff --git a/i18n/trubar-config.yaml b/i18n/trubar-config.yaml new file mode 100644 index 000000000..bac494c61 --- /dev/null +++ b/i18n/trubar-config.yaml @@ -0,0 +1,13 @@ +languages: + en: + name: English + original: true + si: + name: Slovenščina + international-name: Slovenian + auto-import: from orangecanvas.utils.localization.si import plsi, plsi_sz, z_besedo # pylint: disable=wrong-import-order +auto-import: |2 + from orangecanvas.utils.localization import Translator # pylint: disable=wrong-import-order + _tr = Translator("orangewidget", "biolab.si", "Orange") + del Translator +encoding: "utf-8" \ No newline at end of file From 899356ea7bf74090d900a4d06c12a635a99f5955 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 20 Sep 2024 13:42:28 +0200 Subject: [PATCH 41/62] Raise Python requirement to >=3.9 --- .github/workflows/run-docs-build.yml | 2 +- .github/workflows/run-tests-workflow.yml | 12 ++---------- .readthedocs.yml | 2 +- doc/requirements-rtd.txt | 2 +- doc/source/conf.py | 6 +++--- setup.py | 2 +- 6 files changed, 9 insertions(+), 17 deletions(-) diff --git a/.github/workflows/run-docs-build.yml b/.github/workflows/run-docs-build.yml index a1221ab77..7271a5831 100644 --- a/.github/workflows/run-docs-build.yml +++ b/.github/workflows/run-docs-build.yml @@ -16,7 +16,7 @@ jobs: matrix: include: - os: ubuntu-22.04 - python: 3.8 + python: 3.9 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 8cac0221d..640c7be83 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -18,7 +18,7 @@ jobs: include: # Linux - os: ubuntu-20.04 - python-version: 3.8 + python-version: 3.9 test-env: "PyQt5~=5.12.1 PyQtWebEngine~=5.12.1" - os: ubuntu-20.04 @@ -49,13 +49,9 @@ jobs: extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" # macOS - - os: macos-12 - python-version: 3.8 - test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" - - os: macos-12 python-version: 3.9 - test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" + test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" - os: macos-13 python-version: "3.10" @@ -74,10 +70,6 @@ jobs: test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" # Windows - - os: windows-2019 - python-version: 3.8 - test-env: "PyQt5~=5.12.1 PyQtWebEngine~=5.12.1" - - os: windows-2019 python-version: 3.9 test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" diff --git a/.readthedocs.yml b/.readthedocs.yml index 20cf46d94..06e848b84 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.8" + python: "3.9" python: install: diff --git a/doc/requirements-rtd.txt b/doc/requirements-rtd.txt index 5fd43f3d6..8b2731d64 100644 --- a/doc/requirements-rtd.txt +++ b/doc/requirements-rtd.txt @@ -1,7 +1,7 @@ --only-binary PyQt5,matplotlib setuptools -sphinx~=4.2.0 +sphinx~=7.4.7 AnyQt PyQt5~=5.12.1 PyQtWebengine~=5.12.1 diff --git a/doc/source/conf.py b/doc/source/conf.py index 7d8a57134..37422ce2d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -52,7 +52,7 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = {'.rst': 'restructuredtext'} # The encoding of source files. #source_encoding = 'utf-8-sig' @@ -371,6 +371,6 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'https://docs.python.org/3/': None, - 'https://orange-canvas-core.readthedocs.io/en/latest/': None, + 'python': ('https://docs.python.org/3', None), + 'orangecanvas': ('https://orange-canvas-core.readthedocs.io/en/latest/', None), } diff --git a/setup.py b/setup.py index a54333dd6..f4bdd452a 100755 --- a/setup.py +++ b/setup.py @@ -174,7 +174,7 @@ def setup_package(): install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, entry_points=ENTRY_POINTS, - python_requires=">=3.6", + python_requires=">=3.9", zip_safe=False, ) From 8cea3f1fed66a72e7fef95c836a81b33d3eb295e Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 20 Sep 2024 13:42:52 +0200 Subject: [PATCH 42/62] setup.py: Add translation --- setup.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/setup.py b/setup.py index f4bdd452a..00b5cc3a2 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ import os import subprocess from setuptools import setup, find_packages, Command +from setuptools.command.install import install NAME = 'orange-widget-base' VERSION = '4.25.0' @@ -71,6 +72,21 @@ DATA_FILES = [] +class InstallMultilingualCommand(install): + def run(self): + super().run() + self.compile_to_multilingual() + + def compile_to_multilingual(self): + from trubar import translate + + package_dir = os.path.dirname(os.path.abspath(__file__)) + translate( + "msgs.jaml", + source_dir=os.path.join(self.install_lib, "orangewidget"), + config_file=os.path.join(package_dir, "i18n", "trubar-config.yaml")) + + # Return the git revision as a string def git_version(): """Return the git revision as a string. @@ -172,6 +188,9 @@ def setup_package(): package_data=PACKAGE_DATA, data_files=DATA_FILES, install_requires=INSTALL_REQUIRES, + cmdclass={ + 'install': InstallMultilingualCommand, + }, extras_require=EXTRAS_REQUIRE, entry_points=ENTRY_POINTS, python_requires=">=3.9", From 4372813c51523bcbf379c64881ab0872d1a02bc6 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 20 Sep 2024 13:51:04 +0200 Subject: [PATCH 43/62] Resolve missing translations --- i18n/si/msgs.jaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index c66b4fed6..d910dfe9a 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -302,9 +302,9 @@ settings.py: values=: false ): false version.py: - 4.24.0: false - 4.24.0.dev0+31e1b65: false - 31e1b655e24581af44ab5f14523ace5a364f57a8: false + 4.25.0: false + 4.25.0.dev0+8cea3f1: false + 8cea3f1fed66a72e7fef95c836a81b33d3eb295e: false .dev: false widget.py: OWBaseWidget: false From fe4af136129bbfa3b1eb92072524a05163f08b44 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 20 Sep 2024 14:18:59 +0200 Subject: [PATCH 44/62] add i18n config to the manifest --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index aafb5423b..7f743cebd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,3 +6,5 @@ include README.md include CONTRIBUTING.md include LICENSE include setup.py + +recursive-include i18n *.jaml *.yaml From 78b95fc072a8267a000178eda2540236e6f3df02 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 20 Sep 2024 15:13:08 +0200 Subject: [PATCH 45/62] add pyproject.toml with trubar --- MANIFEST.in | 1 + pyproject.toml | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 pyproject.toml diff --git a/MANIFEST.in b/MANIFEST.in index 7f743cebd..07c45fdb4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,5 +6,6 @@ include README.md include CONTRIBUTING.md include LICENSE include setup.py +include pyproject.toml recursive-include i18n *.jaml *.yaml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..f5bf23296 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "trubar>=0.3.3"] +build-backend = "setuptools.build_meta" From b30b6ab9b117a2bcf726c552577e7597feac8004 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 10 Oct 2024 12:57:33 +0200 Subject: [PATCH 46/62] Build release on github actions --- .github/workflows/release.yml | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..3d5f314f3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,79 @@ +name: Release +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build_dist_files: + # Build source distribution and wheel if package is pure python + name: Build distribution files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install build tool + run: python -m pip install build + + - name: Build sdist + run: python -m build --sdist . + + - name: Build wheel + run: | + python -m pip install -r doc/requirements-rtd.txt + cd doc && make html && cd .. + python -m build --wheel . + + - name: Upload dist files + uses: actions/upload-artifact@v4 + with: + name: dist-sdist + path: dist/* + + upload_release_assets: + name: Upload Release Assets + needs: [build_dist_files, build_wheels] + # run eventhough job in need is skipped + if: always() && !failure() && !cancelled() && startsWith(github.ref, 'refs/tags') + runs-on: ubuntu-latest + + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all dist artifacts into dist/ + pattern: dist-* + path: dist + merge-multiple: true + + - name: Upload to release + uses: shogo82148/actions-upload-release-asset@v1 + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: dist/*.whl + + pypi_publish: + name: PyPI Publish + needs: [build_dist_files, build_wheels] + runs-on: ubuntu-latest + permissions: + id-token: write # this permission is mandatory for trusted publishing + # run eventhough job in need is skipped + if: always() && !failure() && !cancelled() && startsWith(github.ref, 'refs/tags') + + steps: + - uses: actions/download-artifact@v4 + with: + # unpacks all dist artifacts into dist/ + pattern: dist-* + path: dist + merge-multiple: true + + - name: Publish packages to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: dist/ From d54133b52665c4352b9036bcf63992cad6fb3f41 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 10 Oct 2024 13:00:03 +0200 Subject: [PATCH 47/62] Build release: remove unused job --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d5f314f3..9e1d74265 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: upload_release_assets: name: Upload Release Assets - needs: [build_dist_files, build_wheels] + needs: [build_dist_files] # run eventhough job in need is skipped if: always() && !failure() && !cancelled() && startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest @@ -56,7 +56,7 @@ jobs: pypi_publish: name: PyPI Publish - needs: [build_dist_files, build_wheels] + needs: [build_dist_files] runs-on: ubuntu-latest permissions: id-token: write # this permission is mandatory for trusted publishing From 49fad601004ade360fff802cf678167febb5872a Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 10 Oct 2024 13:08:31 +0200 Subject: [PATCH 48/62] Release build: do not build docs (they are not distrubuted) --- .github/workflows/release.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e1d74265..b6ae54f32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,10 +22,7 @@ jobs: run: python -m build --sdist . - name: Build wheel - run: | - python -m pip install -r doc/requirements-rtd.txt - cd doc && make html && cd .. - python -m build --wheel . + run: python -m build --wheel . - name: Upload dist files uses: actions/upload-artifact@v4 From 8981db3d8c92a697cf1ab1ae1482929f9b2afcdf Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 10 Oct 2024 13:23:47 +0200 Subject: [PATCH 49/62] Release 4.25.0 [ENH] Multilingual package [ENH] open_filename_dialog: Allow reading *.* [ENH] Numpy 2.0 compatibility [FIX] ListViewFilter: Fix selection [FIX] WidgetMessagesMixin: Fix message binding to not expect class attributes to have `__eq__` --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 00b5cc3a2..ac7d5bbab 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ NAME = 'orange-widget-base' VERSION = '4.25.0' -ISRELEASED = False +ISRELEASED = True # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From 0affc1f4f005da3477430b55b7e29ad263f3987a Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 10 Oct 2024 13:25:18 +0200 Subject: [PATCH 50/62] Bump dev version to 4.26.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ac7d5bbab..fef30b02f 100755 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ from setuptools.command.install import install NAME = 'orange-widget-base' -VERSION = '4.25.0' -ISRELEASED = True +VERSION = '4.26.0' +ISRELEASED = False # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From 96e530c9b6913c7f83861f64302b5ce23bcddb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Erjavec?= Date: Fri, 8 Nov 2024 08:53:47 +0100 Subject: [PATCH 51/62] ci: Add Python 3.13 and PyQt 6.7 to test matrix --- .github/workflows/run-tests-workflow.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 640c7be83..9ed20dfcf 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -17,10 +17,6 @@ jobs: matrix: include: # Linux - - os: ubuntu-20.04 - python-version: 3.9 - test-env: "PyQt5~=5.12.1 PyQtWebEngine~=5.12.1" - - os: ubuntu-20.04 python-version: 3.9 test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" @@ -48,6 +44,11 @@ jobs: test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" + - os: ubuntu-22.04 + python-version: "3.13" + test-env: "PyQt6~=6.7.0 PyQt6-Qt6~=6.7.0 PyQt6-WebEngine~=6.7.0 PyQt6-WebEngine-Qt6~=6.7.0" + extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" + # macOS - os: macos-12 python-version: 3.9 @@ -69,6 +70,10 @@ jobs: python-version: "3.12" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" + - os: macos-latest + python-version: "3.13" + test-env: "PyQt6~=6.7.0 PyQt6-Qt6~=6.7.0 PyQt6-WebEngine~=6.7.0 PyQt6-WebEngine-Qt6~=6.7.0" + # Windows - os: windows-2019 python-version: 3.9 @@ -94,6 +99,10 @@ jobs: python-version: "3.12" test-env: "PyQt6~=6.5.0 PyQt6-Qt6~=6.5.0 PyQt6-WebEngine~=6.5.0 PyQt6-WebEngine-Qt6~=6.5.0" + - os: windows-2019 + python-version: "3.13" + test-env: "PyQt6~=6.7.0 PyQt6-Qt6~=6.7.0 PyQt6-WebEngine~=6.7.0 PyQt6-WebEngine-Qt6~=6.7.0" + steps: - uses: actions/checkout@v4 - name: Setup Python From d6d1b1c2c90e6b4a20e927f589ad44278050a35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Erjavec?= Date: Fri, 8 Nov 2024 08:56:05 +0100 Subject: [PATCH 52/62] ci: Update macos runner images macos-12 is deprecateda --- .github/workflows/run-tests-workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 9ed20dfcf..2143e3395 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -50,15 +50,15 @@ jobs: extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" # macOS - - os: macos-12 + - os: macos-13 python-version: 3.9 test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" - - os: macos-13 + - os: macos-14 python-version: "3.10" test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" - - os: macos-13 + - os: macos-14 python-version: "3.11" test-env: "PyQt6~=6.2.3 PyQt6-Qt6~=6.2.3 PyQt6-WebEngine~=6.2.1 PyQt6-WebEngine-Qt6~=6.2.1" From 08a3b2a0c4e9f337bd6efb06b473034599172373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Erjavec?= Date: Fri, 8 Nov 2024 08:58:35 +0100 Subject: [PATCH 53/62] test_combobox: Remove test_empty Test fails on Qt6.7 because a QListView is already created by the base class. The test did not really test much so just remove it. --- orangewidget/utils/tests/test_combobox.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/orangewidget/utils/tests/test_combobox.py b/orangewidget/utils/tests/test_combobox.py index 5295a93f9..ff5eede57 100644 --- a/orangewidget/utils/tests/test_combobox.py +++ b/orangewidget/utils/tests/test_combobox.py @@ -116,13 +116,6 @@ def test_track(self): self.assertEqual(popup.currentIndex().row(), 2) cb.hidePopup() - def test_empty(self): - cb = self.cb - cb.clear() - cb.showPopup() - popup = cb.findChild(QListView) # type: QListView - self.assertIsNone(popup) - def test_kwargs_enabled_focus_out(self): # PyQt5's property via kwargs can invoke virtual overrides while still # not fully constructed From 05a2d09483f44102d5462a2c2316c4fbbc814817 Mon Sep 17 00:00:00 2001 From: janezd Date: Thu, 5 Dec 2024 22:56:54 +0100 Subject: [PATCH 54/62] Report: Fix saving to PDF --- orangewidget/report/owreport.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/orangewidget/report/owreport.py b/orangewidget/report/owreport.py index 7da395f83..3db712106 100644 --- a/orangewidget/report/owreport.py +++ b/orangewidget/report/owreport.py @@ -11,7 +11,8 @@ from typing import Optional from AnyQt.QtCore import Qt, QObject, pyqtSlot, QSize -from AnyQt.QtGui import QIcon, QCursor, QStandardItemModel, QStandardItem +from AnyQt.QtGui import ( + QIcon, QCursor, QStandardItemModel, QStandardItem, QPageSize) from AnyQt.QtWidgets import ( QApplication, QDialog, QFileDialog, QTableView, QHeaderView, QMessageBox) @@ -354,7 +355,7 @@ def save_report(self): _, extension = os.path.splitext(filename) if extension == ".pdf": printer = QPrinter() - printer.setPageSize(QPrinter.A4) + printer.setPageSize(QPageSize.A4) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(filename) self._print_to_printer(printer) From d932d5fd0240b6d21e737e6ebb54d039796409e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Erjavec?= Date: Fri, 6 Dec 2024 10:29:08 +0100 Subject: [PATCH 55/62] owreport: Add tests for saving to html and pdf --- orangewidget/report/tests/test_report.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/orangewidget/report/tests/test_report.py b/orangewidget/report/tests/test_report.py index f7eab73d5..17cc0317d 100644 --- a/orangewidget/report/tests/test_report.py +++ b/orangewidget/report/tests/test_report.py @@ -79,22 +79,32 @@ def test_save_report_permission(self): rep.save_report() log.assert_called() - def test_save_report(self): + def _test_save_report(self, filter): + ext = re.search(r"\.(?P\w+)\)$", filter).group("format") rep = OWReport() widget = TstWidget() widget.create_report_html() rep.make_report(widget) - temp_dir = tempfile.mkdtemp() - temp_name = os.path.join(temp_dir, "f.report") + temp_f = tempfile.NamedTemporaryFile("w", suffix=f".{ext}", delete=False) + temp_name = temp_f.name + temp_f.close() try: with patch("AnyQt.QtWidgets.QFileDialog.getSaveFileName", - return_value=(temp_name, 'Report (*.report)')), \ + return_value=(temp_name, filter)), \ patch("AnyQt.QtWidgets.QMessageBox.exec", return_value=True): rep.save_report() finally: os.remove(temp_name) - os.rmdir(temp_dir) + + def test_save_report_pdf(self): + self._test_save_report("PDF (*.pdf)") + + def test_save_report_html(self): + self._test_save_report("HTML (*.html)") + + def test_save_report_(self): + self._test_save_report("Report (*.report)") @patch("AnyQt.QtWidgets.QFileDialog.getSaveFileName", return_value=(False, 'HTML (*.html)')) From 39b8e9a17eab5c14ec074ce6aae121f4e0fca804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Erjavec?= Date: Fri, 6 Dec 2024 10:32:59 +0100 Subject: [PATCH 56/62] owreport: Use setPageSize(QPageSize(QPageSize.A4)) --- orangewidget/report/owreport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orangewidget/report/owreport.py b/orangewidget/report/owreport.py index 3db712106..0dc4d4fad 100644 --- a/orangewidget/report/owreport.py +++ b/orangewidget/report/owreport.py @@ -355,7 +355,7 @@ def save_report(self): _, extension = os.path.splitext(filename) if extension == ".pdf": printer = QPrinter() - printer.setPageSize(QPageSize.A4) + printer.setPageSize(QPageSize(QPageSize.A4)) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(filename) self._print_to_printer(printer) From 0392606b2a76682fa53193b40da7d6c6ef42d987 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 23 Dec 2024 07:43:36 +0100 Subject: [PATCH 57/62] Release 4.25.1 [FIX] Report: Fix saving to PDF --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index fef30b02f..4916f2b5e 100755 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ from setuptools.command.install import install NAME = 'orange-widget-base' -VERSION = '4.26.0' -ISRELEASED = False +VERSION = '4.25.1' +ISRELEASED = True # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From ae29dd059f208f7ac2290426cbde25dcf07fdfad Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Mon, 23 Dec 2024 07:44:59 +0100 Subject: [PATCH 58/62] Bump dev version to 4.26.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4916f2b5e..fef30b02f 100755 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ from setuptools.command.install import install NAME = 'orange-widget-base' -VERSION = '4.25.1' -ISRELEASED = True +VERSION = '4.26.0' +ISRELEASED = False # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From e98e6d193113e3d61dabab24e9a50302f22bde98 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 24 Jan 2025 10:46:16 +0100 Subject: [PATCH 59/62] Bump minimum python version to 3.10 --- .github/workflows/run-docs-build.yml | 2 +- .github/workflows/run-tests-workflow.yml | 12 ------------ .readthedocs.yml | 6 +++++- orangewidget/gui.py | 5 +---- setup.py | 3 +-- 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.github/workflows/run-docs-build.yml b/.github/workflows/run-docs-build.yml index 7271a5831..6874aee75 100644 --- a/.github/workflows/run-docs-build.yml +++ b/.github/workflows/run-docs-build.yml @@ -16,7 +16,7 @@ jobs: matrix: include: - os: ubuntu-22.04 - python: 3.9 + python: "3.10" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/run-tests-workflow.yml b/.github/workflows/run-tests-workflow.yml index 2143e3395..06711f46b 100644 --- a/.github/workflows/run-tests-workflow.yml +++ b/.github/workflows/run-tests-workflow.yml @@ -17,10 +17,6 @@ jobs: matrix: include: # Linux - - os: ubuntu-20.04 - python-version: 3.9 - test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" - - os: ubuntu-20.04 python-version: "3.10" test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" @@ -50,10 +46,6 @@ jobs: extra-system-packages: "libegl1-mesa libxcb-cursor0 glibc-tools" # macOS - - os: macos-13 - python-version: 3.9 - test-env: "PyQt5~=5.14.0 PyQtWebEngine~=5.14.0" - - os: macos-14 python-version: "3.10" test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" @@ -75,10 +67,6 @@ jobs: test-env: "PyQt6~=6.7.0 PyQt6-Qt6~=6.7.0 PyQt6-WebEngine~=6.7.0 PyQt6-WebEngine-Qt6~=6.7.0" # Windows - - os: windows-2019 - python-version: 3.9 - test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" - - os: windows-2019 python-version: "3.10" test-env: "PyQt5~=5.15.0 PyQtWebEngine~=5.15.0" diff --git a/.readthedocs.yml b/.readthedocs.yml index 06e848b84..476bea02e 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,9 +1,13 @@ version: 2 +sphinx: + # Path to your Sphinx configuration file. + configuration: doc/source/conf.py + build: os: ubuntu-22.04 tools: - python: "3.9" + python: "3.10" python: install: diff --git a/orangewidget/gui.py b/orangewidget/gui.py index 7a0808c32..44b0cc462 100644 --- a/orangewidget/gui.py +++ b/orangewidget/gui.py @@ -29,10 +29,7 @@ from orangewidget.utils.itemmodels import PyListModel, signal_blocking -try: - from importlib.resources import files as _resources_files -except ImportError: - from importlib_resources import files as _resources_files +from importlib.resources import files as _resources_files __re_label = re.compile(r"(^|[^%])%\((?P[a-zA-Z]\w*)\)") diff --git a/setup.py b/setup.py index fef30b02f..530679813 100755 --- a/setup.py +++ b/setup.py @@ -60,7 +60,6 @@ "typing_extensions>=3.7.4.3", "orange-canvas-core>=0.2a.dev0,<0.3a", 'appnope; sys_platform=="darwin"', - "importlib_resources; python_version<'3.10'" ] EXTRAS_REQUIRE = { @@ -193,7 +192,7 @@ def setup_package(): }, extras_require=EXTRAS_REQUIRE, entry_points=ENTRY_POINTS, - python_requires=">=3.9", + python_requires=">=3.10", zip_safe=False, ) From e88298317b28489a949bed4222349c62da0f9884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Erjavec?= Date: Wed, 12 Feb 2025 12:08:10 +0100 Subject: [PATCH 60/62] widget: Add all actions to top level widget Shortcuts would not work if menu bar is not visible. --- orangewidget/widget.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/orangewidget/widget.py b/orangewidget/widget.py index b26c486e8..10f27f787 100755 --- a/orangewidget/widget.py +++ b/orangewidget/widget.py @@ -355,6 +355,7 @@ def __new__(cls, *args, captionTitle=None, **kwargs): self.__statusbar_action = None # type: Optional[QAction] self.__menubar_action = None self.__menubar_visible_timer = None + self.__toggle_control_area_action = None # this action is enabled by the canvas framework self.__help_action = QAction( @@ -402,8 +403,6 @@ def __new__(cls, *args, captionTitle=None, **kwargs): self.__visual_settings_action.setEnabled(True) self.__visual_settings_action.setVisible(True) - self.addAction(self.__help_action) - self.__copy_action = QAction( "Copy to Clipboard", self, objectName="action-copy-to-clipboard", shortcut=QKeySequence.Copy, enabled=False, visible=False @@ -530,7 +529,7 @@ def __new__(cls, *args, captionTitle=None, **kwargs): helpaction.menu().addAction(self.__quick_help_action) if self.__splitter is not None and self.__splitter.count() > 1: - action = QAction( + self.__toggle_control_area_action = action = QAction( "Show Control Area", self, objectName="action-show-control-area", shortcut=QKeySequence("Ctrl+Shift+D"), @@ -545,6 +544,21 @@ def __new__(cls, *args, captionTitle=None, **kwargs): if self.__menubar_action is not None: viewaction.menu().addAction(self.__menubar_action) + actions = ( + self.__help_action, + self.__report_action, + self.__save_image_action, + self.__reset_action, + self.__visual_settings_action, + self.__copy_action, + self.__minimize_action, + self.__close_action, + self.__menubar_action, + self.__quick_help_action, + self.__toggle_control_area_action, + ) + self.addActions(list(filter(None, actions))) + if self.controlArea is not None: # Otherwise, the first control has focus self.controlArea.setFocus(Qt.OtherFocusReason) From 1c9592f475accb449e4d65fed60d54f307f474a0 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Fri, 13 Jun 2025 09:47:19 +0200 Subject: [PATCH 61/62] Release 4.26.0 [FIX] Widget shortcuts now work even if menu bar is not visible Bump minimum python version to 3.10 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 530679813..fced7d3db 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ NAME = 'orange-widget-base' VERSION = '4.26.0' -ISRELEASED = False +ISRELEASED = True # full version identifier including a git revision identifier for development # build/releases (this is filled/updated in `write_version_py`) FULLVERSION = VERSION From aeef18661f60c7116c76a0771c10594b1735404e Mon Sep 17 00:00:00 2001 From: payno Date: Thu, 22 Jun 2023 11:14:42 +0200 Subject: [PATCH 62/62] add processing message state # Conflicts: # orangewidget/utils/messagewidget.py # Conflicts: # orangewidget/utils/messagewidget.py --- orangewidget/utils/messages.py | 12 ++++++++++++ orangewidget/utils/messagewidget.py | 4 ++++ orangewidget/widget.py | 1 + 3 files changed, 17 insertions(+) diff --git a/orangewidget/utils/messages.py b/orangewidget/utils/messages.py index 5c804a116..0179e51d3 100644 --- a/orangewidget/utils/messages.py +++ b/orangewidget/utils/messages.py @@ -342,6 +342,14 @@ class Information(MessageGroup): bar_background = "#ceceff" bar_icon = QStyle.SP_MessageBoxInformation + class Processing(MessageGroup): + """Base class for groups of processing messages in widgets""" + severity = 0 + icon_path = None + bar_background = "#ceceff" + bar_icon = QStyle.SP_MessageBoxInformation + + def __init__(self): super().__init__() self.message_bar = None @@ -417,3 +425,7 @@ def warning(self, text_or_id=None, text=None, shown=True): def error(self, text_or_id=None, text=None, shown=True): self._warn_obsolete(text_or_id, "error") self.Error._add_general(text_or_id, text, shown) + + def processing_info(self, text_or_id=None, text=None, shown=True): + self._warn_obsolete(text_or_id, "processing") + self.Processing._add_general(text_or_id, text, shown) diff --git a/orangewidget/utils/messagewidget.py b/orangewidget/utils/messagewidget.py index 70d11718d..8a74b3b5a 100644 --- a/orangewidget/utils/messagewidget.py +++ b/orangewidget/utils/messagewidget.py @@ -58,6 +58,8 @@ class Severity(enum.IntEnum): Warning = 2 # == QMessageBox.Warning #: An error message severity. Error = 3 # == QMessageBox.Critical + #: A processing + Processing = 0 class Message( @@ -100,6 +102,7 @@ class Message( Warning = Severity.Warning #: Alias for :attr:`Severity.Error` Error = Severity.Error + Processing = Severity.Processing def __new__(cls, severity=Severity.Information, icon=QIcon(), text="", informativeText="", detailedText="", textFormat=Qt.PlainText): @@ -181,6 +184,7 @@ def standard_pixmap(severity): Severity.Information: QStyle.SP_MessageBoxInformation, Severity.Warning: QStyle.SP_MessageBoxWarning, Severity.Error: QStyle.SP_MessageBoxCritical, + Severity.Processing: QStyle.SP_MessageBoxInformation, } return mapping[severity] diff --git a/orangewidget/widget.py b/orangewidget/widget.py index 10f27f787..06711091d 100755 --- a/orangewidget/widget.py +++ b/orangewidget/widget.py @@ -82,6 +82,7 @@ class Message: Information = QStyle.SP_MessageBoxInformation Warning = QStyle.SP_MessageBoxWarning Critical = QStyle.SP_MessageBoxCritical + Processing = 0 def __init__(self, text, persistent_id, icon=None, moreurl=None): assert isinstance(text, str)