From 7830dfc0ef74102b4f4f1ce4a58d6c318a598a9d Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 11 May 2026 18:19:26 -0700 Subject: [PATCH 01/26] core: guard copy_or_move when no location path matches dupe parent Signed-off-by: SAY-5 --- core/app.py | 2 +- core/tests/app_test.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/app.py b/core/app.py index 7cde6317..35f55c86 100644 --- a/core/app.py +++ b/core/app.py @@ -433,7 +433,7 @@ def copy_or_move(self, dupe, copy: bool, destination: str, dest_type: DestType): if dest_type in {DestType.RELATIVE, DestType.ABSOLUTE}: # no filename, no windows drive letter source_base = source_path.relative_to(source_path.anchor).parent - if dest_type == DestType.RELATIVE: + if dest_type == DestType.RELATIVE and location_path is not None: source_base = source_base.relative_to(location_path.relative_to(location_path.anchor)) dest_path = dest_path.joinpath(source_base) if not dest_path.exists(): diff --git a/core/tests/app_test.py b/core/tests/app_test.py index 6d6581aa..114d9fda 100644 --- a/core/tests/app_test.py +++ b/core/tests/app_test.py @@ -90,6 +90,26 @@ def test_copy_or_move_clean_empty_dirs(self, tmpdir, monkeypatch): eq_(1, len(calls)) eq_(sourcepath, calls[0]["path"]) + def test_copy_or_move_no_location_path(self, tmpdir, monkeypatch): + # dupe path has no parent in self.directories (e.g. directory removed + # between scan and copy); previously raised AttributeError on None + p = Path(str(tmpdir)) + p.joinpath("foo").touch() + monkeypatch.setattr( + hscommon.conflict, + "smart_copy", + log_calls(lambda source_path, dest_path: None), + ) + monkeypatch.setattr(app, "smart_copy", hscommon.conflict.smart_copy) + monkeypatch.setattr(os, "makedirs", lambda path: None) + dgapp = TestApp().app + dgapp.directories.add_path(p) + [f] = dgapp.directories.get_files() + dgapp.directories._dirs.clear() + with tempfile.TemporaryDirectory() as tmp_dir: + dgapp.copy_or_move(f, True, tmp_dir, 1) + eq_(1, len(hscommon.conflict.smart_copy.calls)) + def test_scan_with_objects_evaluating_to_false(self): class FakeFile(fs.File): def __bool__(self): From 2520c4259300edf65c79d25a6ba67ba69ae3cf0a Mon Sep 17 00:00:00 2001 From: "Nathaniel M. Beaver" Date: Sat, 9 May 2026 22:37:18 -0400 Subject: [PATCH 02/26] Set the application DesktopFileName This sets the desktop filename so the application name in the dock is "dupeguru" instead of just "python3". See also: https://github.com/qutebrowser/qutebrowser/pull/4153/files https://tickets.metabrainz.org/browse/PICARD-1502 https://github.com/arsenetar/dupeguru/pull/1390 --- run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/run.py b/run.py index d6b5d236..079fd155 100644 --- a/run.py +++ b/run.py @@ -48,6 +48,7 @@ def setup_signals(): def main(): app = QApplication(sys.argv) + app.setDesktopFileName("dupeguru") QCoreApplication.setOrganizationName("Hardcoded Software") QCoreApplication.setApplicationName(__appname__) QCoreApplication.setApplicationVersion(__version__) From bf0a1d10613eae25d120447d9b2d77e37beb2228 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Wed, 6 Jul 2022 22:40:36 -0500 Subject: [PATCH 03/26] Start PyQt5 -> PyQt6 migration --- Makefile | 4 ++-- README.md | 7 ++----- Windows.md | 2 +- macos.md | 8 ++++---- qt/util.py | 42 +++++++++++++++++++----------------------- requirements.txt | 2 +- 6 files changed, 29 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index c6a1d6b7..f9b66917 100644 --- a/Makefile +++ b/Makefile @@ -60,8 +60,8 @@ ifndef NO_VENV @${PYTHON} -m venv -h > /dev/null || \ echo "Creation of our virtualenv failed. If you're on Ubuntu, you probably need python3-venv." endif - @${PYTHON} -c 'import PyQt5' >/dev/null 2>&1 || \ - { echo "PyQt 5.4+ required. Install it and try again. Aborting"; exit 1; } + @${PYTHON} -c 'import PyQt6' >/dev/null 2>&1 || \ + { echo "PyQt 6.3+ required. Install it and try again. Aborting"; exit 1; } env: | reqs ifndef NO_VENV diff --git a/README.md b/README.md index f574dee0..ea2ca84d 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,15 @@ For macos instructions (qt version) see the [macOS Instructions](macos.md). ### Prerequisites * [Python 3.7+][python] -* PyQt5 +* PyQt6 ### System Setup When running in a linux based environment the following system packages or equivalents are needed to build: -* python3-pyqt5 -* pyqt5-dev-tools (on some systems, see note) +* python3-pyqt6 * python3-venv (only if using a virtual environment) * python3-dev * build-essential -Note: On some linux systems pyrcc5 is not put on the path when installing python3-pyqt5, this will cause some issues with the resource files (and icons). These systems should have a respective pyqt5-dev-tools package, which should also be installed. The presence of pyrcc5 can be checked with `which pyrcc5`. Debian based systems need the extra package, and Arch does not. - To create packages the following are also needed: * python3-setuptools * debhelper diff --git a/Windows.md b/Windows.md index e54737b7..f185f3cc 100644 --- a/Windows.md +++ b/Windows.md @@ -28,7 +28,7 @@ To build with a different python version 3.7 vs 3.8 or 32 bit vs 64 bit specify ### With makefile It is possible to build dupeGuru with the makefile on windows using a compatable POSIX environment. The following steps have been tested using [msys2][msys2]. Before running make: 1. Install msys2 or other POSIX environment -2. Install PyQt5 globally via pip +2. Install PyQt6 globally via pip 3. Use the respective console for msys2 it is `msys2 msys` Then the following execution of the makefile should work. Pass the correct value for PYTHON to the makefile if not on the path as python3. diff --git a/macos.md b/macos.md index 1270666d..f8392991 100644 --- a/macos.md +++ b/macos.md @@ -7,19 +7,19 @@ These instructions are for the Qt version of the UI on macOS. - [Python 3.7+][python] - [Xcode 12.3][xcode] or just Xcode command line tools (older versions can be used if not interested in arm macs) - [Homebrew][homebrew] -- [qt5](https://www.qt.io/) +- [qt6](https://www.qt.io/) #### Prerequisite setup 1. Install Xcode if desired 2. Install [Homebrew][homebrew], if not on the path after install (arm based Macs) create `~/.zshrc` with `export PATH="/opt/homebrew/bin:$PATH"`. Will need to reload terminal or source the file to take effect. -3. Install qt5 with `brew`. If you are using a version of macos without system python 3.7+ then you will +3. Install qt6 with `brew`. If you are using a version of macos without system python 3.7+ then you will also need to install that via brew or with pyenv. - $ brew install qt5 + $ brew install qt6 - NOTE: Using `brew` to install qt5 is to allow pyqt5 to build without a native wheel + NOTE: Using `brew` to install qt6 is to allow pyqt6 to build without a native wheel available. If you are using an intel based mac you can probably skip this step. 4. May need to launch a new terminal to have everything working. diff --git a/qt/util.py b/qt/util.py index ba6e0eb6..46c2894f 100644 --- a/qt/util.py +++ b/qt/util.py @@ -11,22 +11,18 @@ import os.path as op import os import logging +from typing import List, Union from core.util import executable_folder from hscommon.util import first from hscommon.plat import ISWINDOWS -from PyQt5.QtCore import QStandardPaths, QSettings -from PyQt5.QtGui import QPixmap, QIcon, QGuiApplication -from PyQt5.QtWidgets import ( - QSpacerItem, - QSizePolicy, - QAction, - QHBoxLayout, -) +from PyQt6.QtCore import QStandardPaths, QSettings +from PyQt6.QtGui import QPixmap, QIcon, QGuiApplication, QAction +from PyQt6.QtWidgets import QSpacerItem, QSizePolicy, QHBoxLayout, QWidget -def move_to_screen_center(widget): +def move_to_screen_center(widget: QWidget) -> None: frame = widget.frameGeometry() if QGuiApplication.screenAt(frame.center()) is None: # if center not on any screen use default screen @@ -43,21 +39,21 @@ def move_to_screen_center(widget): widget.move(frame.topLeft()) -def vertical_spacer(size=None): +def vertical_spacer(size: Union[int, None] = None) -> QSpacerItem: if size: - return QSpacerItem(1, size, QSizePolicy.Fixed, QSizePolicy.Fixed) + return QSpacerItem(1, size, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) else: - return QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) + return QSpacerItem(1, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.MinimumExpanding) -def horizontal_spacer(size=None): +def horizontal_spacer(size: Union[int, None] = None) -> QSpacerItem: if size: - return QSpacerItem(size, 1, QSizePolicy.Fixed, QSizePolicy.Fixed) + return QSpacerItem(size, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) else: - return QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) + return QSpacerItem(1, 1, QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed) -def horizontal_wrap(widgets): +def horizontal_wrap(widgets: List[Union[QWidget, int, None]]) -> QHBoxLayout: """Wrap all widgets in `widgets` in a horizontal layout. If, instead of placing a widget in your list, you place an int or None, an horizontal spacer @@ -77,7 +73,7 @@ def create_actions(actions, target): for name, shortcut, icon, desc, func in actions: action = QAction(target) if icon: - action.setIcon(QIcon(QPixmap(":/" + icon))) + action.setIcon(QIcon(QPixmap(":/" + icon))) # TODO stop using qrc file path if shortcut: action.setShortcut(shortcut) action.setText(desc) @@ -100,11 +96,11 @@ def set_accel_keys(menu): action.setText(newtext) -def get_appdata(portable=False): +def get_appdata(portable: bool = False) -> str: if portable: return op.join(executable_folder(), "data") else: - return QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)[0] + return QStandardPaths.standardLocations(QStandardPaths.StandardLocation.AppDataLocation)[0] class SysWrapper(io.IOBase): @@ -140,18 +136,18 @@ def escape_amp(s): return s.replace("&", "&&") -def create_qsettings(): +def create_qsettings() -> QSettings: # Create a QSettings instance with the correct arguments. config_location = op.join(executable_folder(), "settings.ini") if op.isfile(config_location): - settings = QSettings(config_location, QSettings.IniFormat) + settings = QSettings(config_location, QSettings.Format.IniFormat) settings.setValue("Portable", True) elif ISWINDOWS: # On windows use an ini file in the AppDataLocation instead of registry if possible as it # makes it easier for a user to clear it out when there are issues. - locations = QStandardPaths.standardLocations(QStandardPaths.AppDataLocation) + locations = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.AppDataLocation) if locations: - settings = QSettings(op.join(locations[0], "settings.ini"), QSettings.IniFormat) + settings = QSettings(op.join(locations[0], "settings.ini"), QSettings.Format.IniFormat) else: settings = QSettings() settings.setValue("Portable", False) diff --git a/requirements.txt b/requirements.txt index 9618b5b1..9c8b7c01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ distro>=1.8.0,<2.0.0 mutagen>=1.46.0,<2.0.0 polib>=1.1.0,<2.0.0 -PyQt5 >=5.15.0,<6.0; sys_platform != 'linux' +PyQt6 >=6.3,<7.0; sys_platform != 'linux' pywin32>=304; sys_platform == 'win32' semantic-version>=2.0.0,<3.0.0 Send2Trash>=1.8.2,<2.0.0 From 05cd156c32b6d55a3b759ef310ad70f800f36a08 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Thu, 7 Jul 2022 21:26:45 -0500 Subject: [PATCH 04/26] More updates for pyqt6 --- qt/pe/block.pyi | 2 +- qt/problem_dialog.py | 81 ++++++++++++++++++++++++-------------------- run.py | 8 ++--- setup.cfg | 4 +-- setup.nsi | 2 +- 5 files changed, 52 insertions(+), 45 deletions(-) diff --git a/qt/pe/block.pyi b/qt/pe/block.pyi index 17954a92..ba576467 100644 --- a/qt/pe/block.pyi +++ b/qt/pe/block.pyi @@ -1,5 +1,5 @@ from typing import Tuple, List, Union -from PyQt5.QtGui import QImage +from PyQt6.QtGui import QImage _block = Tuple[int, int, int] diff --git a/qt/problem_dialog.py b/qt/problem_dialog.py index 38c0515f..24803199 100644 --- a/qt/problem_dialog.py +++ b/qt/problem_dialog.py @@ -6,8 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QShowEvent +from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, @@ -17,8 +18,10 @@ QLabel, QTableView, QAbstractItemView, + QWidget, ) +from core.gui.problem_dialog import ProblemDialog as ProblemDiaglogModel from qt.util import move_to_screen_center from hscommon.trans import trget from qt.problem_table import ProblemTable @@ -27,52 +30,56 @@ class ProblemDialog(QDialog): - def __init__(self, parent, model, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + def __init__(self, parent: QWidget, model: ProblemDiaglogModel, **kwargs) -> None: + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) - self._setupUi() self.model = model - self.model.view = self - self.table = ProblemTable(self.model.problem_table, view=self.tableView) - - self.revealButton.clicked.connect(self.model.reveal_selected_dupe) - self.closeButton.clicked.connect(self.accept) + self.table_view = QTableView(self) + self.table = ProblemTable(self.model.problem_table, view=self.table_view) + self._setupUi() - def _setupUi(self): + def _setupUi(self) -> None: self.setWindowTitle(tr("Problems!")) self.resize(413, 323) - self.verticalLayout = QVBoxLayout(self) - self.label = QLabel(self) + main_layout = QVBoxLayout(self) + notice_label = QLabel(self) msg = tr( "There were problems processing some (or all) of the files. The cause of " "these problems are described in the table below. Those files were not " "removed from your results." ) - self.label.setText(msg) - self.label.setWordWrap(True) - self.verticalLayout.addWidget(self.label) - self.tableView = QTableView(self) - self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.tableView.setSelectionMode(QAbstractItemView.SingleSelection) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) - self.tableView.setShowGrid(False) - self.tableView.horizontalHeader().setStretchLastSection(True) - self.tableView.verticalHeader().setDefaultSectionSize(18) - self.tableView.verticalHeader().setHighlightSections(False) - self.verticalLayout.addWidget(self.tableView) - self.horizontalLayout = QHBoxLayout() - self.revealButton = QPushButton(self) - self.revealButton.setText(tr("Reveal Selected")) - self.horizontalLayout.addWidget(self.revealButton) - spacer_item = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacer_item) - self.closeButton = QPushButton(self) - self.closeButton.setText(tr("Close")) - self.closeButton.setDefault(True) - self.horizontalLayout.addWidget(self.closeButton) - self.verticalLayout.addLayout(self.horizontalLayout) + notice_label.setText(msg) + notice_label.setWordWrap(True) + main_layout.addWidget(notice_label) + + self.table_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + self.table_view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.table_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.table_view.setShowGrid(False) + self.table_view.horizontalHeader().setStretchLastSection(True) + self.table_view.verticalHeader().setDefaultSectionSize(18) + self.table_view.verticalHeader().setHighlightSections(False) + main_layout.addWidget(self.table_view) + + button_layout = QHBoxLayout() + reveal_button = QPushButton(self) + reveal_button.setText(tr("Reveal Selected")) + button_layout.addWidget(reveal_button) + + spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + button_layout.addItem(spacer_item) + + close_button = QPushButton(self) + close_button.setText(tr("Close")) + close_button.setDefault(True) + button_layout.addWidget(close_button) + + main_layout.addLayout(button_layout) + + reveal_button.clicked.connect(self.model.reveal_selected_dupe) + close_button.clicked.connect(self.accept) - def showEvent(self, event): + def showEvent(self, event: QShowEvent) -> None: # have to do this here as the frameGeometry is not correct until shown move_to_screen_center(self) super().showEvent(event) diff --git a/run.py b/run.py index 079fd155..37bab1b6 100644 --- a/run.py +++ b/run.py @@ -9,9 +9,9 @@ import os.path as op import gc -from PyQt5.QtCore import QCoreApplication -from PyQt5.QtGui import QIcon, QPixmap -from PyQt5.QtWidgets import QApplication +from PyQt6.QtCore import QCoreApplication +from PyQt6.QtGui import QIcon, QPixmap +from PyQt6.QtWidgets import QApplication from hscommon.trans import install_gettext_trans_under_qt from qt.error_report_dialog import install_excepthook @@ -62,7 +62,7 @@ def main(): # Let the Python interpreter runs every 500ms to handle signals. This is # required because Python cannot handle signals while the Qt event loop is # running. - from PyQt5.QtCore import QTimer + from PyQt6.QtCore import QTimer timer = QTimer() timer.start(500) diff --git a/setup.cfg b/setup.cfg index bc12410b..4c400731 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,12 +32,12 @@ install_requires = Send2Trash>=1.8.2,<2.0.0 mutagen>=1.46.0,<2.0.0 distro>=1.8.0,<2.0.0 - PyQt5 >=5.15.0,<6.0; sys_platform != 'linux' + PyQt6 >=6.3.0,<7.0; sys_platform != 'linux' pywin32>=228; sys_platform == 'win32' semantic-version>=2.0.0,<3.0.0 xxhash>=3.0.0,<4.0.0 setup_requires = - sphinx>=3.0.0 + sphinx>=5.0.0 polib>=1.1.0 tests_require = pytest >=6,<7 diff --git a/setup.nsi b/setup.nsi index a6809cf7..3ec789bb 100644 --- a/setup.nsi +++ b/setup.nsi @@ -245,7 +245,7 @@ Section "Uninstall" ; Remove Files & Folders in Install Folder RMDir /r "$INSTDIR\core" RMDir /r "$INSTDIR\help" - RMDir /r "$INSTDIR\PyQt5" + RMDir /r "$INSTDIR\PyQt6" RMDir /r "$INSTDIR\qt" RMDir /r "$INSTDIR\locale" Delete "$INSTDIR\*.exe" From a1a044762f22a7fb73dfb380ba3bab328fed7d9e Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Fri, 8 Jul 2022 22:17:42 -0500 Subject: [PATCH 05/26] Next batch of qt6 upgrades --- .github/workflows/codeql-analysis.yml | 2 +- hscommon/desktop.py | 6 +-- hscommon/trans.py | 6 +-- macos.md | 2 +- pkg/debian/control | 2 +- qt/about_box.py | 55 ++++++++++++++-------- qt/error_report_dialog.py | 66 +++++++++++++++------------ run.py | 13 +++--- 8 files changed, 91 insertions(+), 61 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f89fcb94..9f2ec6d8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: name: Build Cpp run: | sudo apt-get update - sudo apt-get install python3-pyqt5 + sudo apt-get install python3-pyqt6 make modules - if: matrix.language == 'python' name: Autobuild diff --git a/hscommon/desktop.py b/hscommon/desktop.py index 09cbdc7d..d96a4704 100644 --- a/hscommon/desktop.py +++ b/hscommon/desktop.py @@ -44,8 +44,8 @@ def special_folder_path(special_folder: SpecialFolder, portable: bool = False) - try: - from PyQt5.QtCore import QUrl, QStandardPaths - from PyQt5.QtGui import QDesktopServices + from PyQt6.QtCore import QUrl, QStandardPaths + from PyQt6.QtGui import QDesktopServices from qt.util import get_appdata from core.util import executable_folder from hscommon.plat import ISWINDOWS, ISOSX @@ -71,7 +71,7 @@ def _special_folder_path(special_folder: SpecialFolder, portable: bool = False) if ISWINDOWS and portable: folder = op.join(executable_folder(), "cache") else: - folder = QStandardPaths.standardLocations(QStandardPaths.CacheLocation)[0] + folder = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.CacheLocation)[0] else: folder = get_appdata(portable) return folder diff --git a/hscommon/trans.py b/hscommon/trans.py index 694a36ba..52f8bc6d 100644 --- a/hscommon/trans.py +++ b/hscommon/trans.py @@ -83,7 +83,7 @@ def get_locale_name(lang: str) -> Union[str, None]: # --- Qt def install_qt_trans(lang: str = None) -> None: - from PyQt5.QtCore import QCoreApplication, QTranslator, QLocale + from PyQt6.QtCore import QCoreApplication, QTranslator, QLocale if not lang: lang = str(QLocale.system().name())[:2] @@ -140,7 +140,7 @@ def install_gettext_trans_under_qt(base_folder: os.PathLike, lang: str = None) - # So, we install the gettext locale, great, but we also should try to install qt_*.qm if # available so that strings that are inside Qt itself over which I have no control are in the # right language. - from PyQt5.QtCore import QCoreApplication, QTranslator, QLocale, QLibraryInfo + from PyQt6.QtCore import QCoreApplication, QTranslator, QLocale, QLibraryInfo if not lang: lang = str(QLocale.system().name())[:2] @@ -156,7 +156,7 @@ def install_gettext_trans_under_qt(base_folder: os.PathLike, lang: str = None) - if ISLINUX: # Under linux, a full Qt installation is already available in the system, we didn't bundle # up the qm files in our package, so we have to load translations from the system. - qmpath = op.join(QLibraryInfo.location(QLibraryInfo.TranslationsPath), qmname) + qmpath = op.join(QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath), qmname) else: qmpath = op.join(base_folder, qmname) qtr = QTranslator(QCoreApplication.instance()) diff --git a/macos.md b/macos.md index f8392991..bf54be51 100644 --- a/macos.md +++ b/macos.md @@ -27,7 +27,7 @@ also need to install that via brew or with pyenv. ### With build.py OSX comes with a version of python 3 by default in newer versions of OSX. To produce universal builds either the 3.8 version shipped in macos or 3.9.1 or newer needs to be used. If needing to -build pyqt5 from source then the first line below is needed, else it may be omitted. (Path shown is +build pyqt6 from source then the first line below is needed, else it may be omitted. (Path shown is for an arm mac.) $ export PATH="/opt/homebrew/opt/qt/bin:$PATH" diff --git a/pkg/debian/control b/pkg/debian/control index 1f84eac4..e6ec1e2d 100644 --- a/pkg/debian/control +++ b/pkg/debian/control @@ -10,7 +10,7 @@ Vcs-Git: https://github.com/arsenetar/dupeguru.git Package: {pkgname} Architecture: {arch} -Depends: ${shlibs:Depends}, python3 (>=3.7), python3 (<<3.12), python3-pyqt5, python3-mutagen, python3-semantic-version +Depends: ${shlibs:Depends}, python3 (>=3.7), python3 (<<3.12), python3-pyqt6, python3-mutagen, python3-semantic-version Provides: dupeguru-se, dupeguru-me, dupeguru-pe Replaces: dupeguru-se, dupeguru-me, dupeguru-pe Conflicts: dupeguru-se, dupeguru-me, dupeguru-pe diff --git a/qt/about_box.py b/qt/about_box.py index 07039cc3..6d69e961 100644 --- a/qt/about_box.py +++ b/qt/about_box.py @@ -6,36 +6,46 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QCoreApplication, QTimer -from PyQt5.QtGui import QPixmap, QFont -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QHBoxLayout, QVBoxLayout, QLabel +from PyQt6.QtCore import Qt, QCoreApplication, QTimer +from PyQt6.QtGui import QPixmap, QFont, QShowEvent +from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QHBoxLayout, QVBoxLayout, QLabel, QWidget from core.util import check_for_update from qt.util import move_to_screen_center from hscommon.trans import trget +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from qt.app import DupeGuru + tr = trget("ui") class AboutBox(QDialog): - def __init__(self, parent, app, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint + def __init__(self, parent: QWidget, app: "DupeGuru", **kwargs) -> None: + flags = ( + Qt.WindowType.CustomizeWindowHint + | Qt.WindowType.WindowTitleHint + | Qt.WindowType.WindowSystemMenuHint + | Qt.WindowType.MSWindowsFixedSizeDialogHint + ) super().__init__(parent, flags, **kwargs) self.app = app self._setupUi() - self.button_box.accepted.connect(self.accept) - self.button_box.rejected.connect(self.reject) - - def _setupUi(self): + def _setupUi(self) -> None: self.setWindowTitle(tr("About {}").format(QCoreApplication.instance().applicationName())) - size_policy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + size_policy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.setSizePolicy(size_policy) main_layout = QHBoxLayout(self) + logo_label = QLabel() - logo_label.setPixmap(QPixmap(":/%s_big" % self.app.LOGO_NAME)) + logo_label.setPixmap(QPixmap(f"images:{self.app.LOGO_NAME}_128.png")) main_layout.addWidget(logo_label) + detail_layout = QVBoxLayout() + name_label = QLabel() font = QFont() font.setWeight(75) @@ -43,26 +53,35 @@ def _setupUi(self): name_label.setFont(font) name_label.setText(QCoreApplication.instance().applicationName()) detail_layout.addWidget(name_label) + version_label = QLabel() version_label.setText(tr("Version {}").format(QCoreApplication.instance().applicationVersion())) detail_layout.addWidget(version_label) + self.update_label = QLabel(tr("Checking for updates...")) - self.update_label.setTextInteractionFlags(Qt.TextBrowserInteraction) + self.update_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) self.update_label.setOpenExternalLinks(True) detail_layout.addWidget(self.update_label) + license_label = QLabel() license_label.setText(tr("Licensed under GPLv3")) detail_layout.addWidget(license_label) + spacer_label = QLabel() spacer_label.setFont(font) detail_layout.addWidget(spacer_label) - self.button_box = QDialogButtonBox() - self.button_box.setOrientation(Qt.Horizontal) - self.button_box.setStandardButtons(QDialogButtonBox.Ok) - detail_layout.addWidget(self.button_box) + + button_box = QDialogButtonBox() + button_box.setOrientation(Qt.Orientation.Horizontal) + button_box.setStandardButtons(QDialogButtonBox.StandardButton.Ok) + detail_layout.addWidget(button_box) + main_layout.addLayout(detail_layout) - def _check_for_update(self): + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + def _check_for_update(self) -> None: update = check_for_update(QCoreApplication.instance().applicationVersion(), include_prerelease=False) if update is None: self.update_label.setText(tr("No update available.")) @@ -71,7 +90,7 @@ def _check_for_update(self): tr('New version {} available, download here.').format(update["version"], update["url"]) ) - def showEvent(self, event): + def showEvent(self, event: QShowEvent) -> None: self.update_label.setText(tr("Checking for updates...")) # have to do this here as the frameGeometry is not correct until shown move_to_screen_center(self) diff --git a/qt/error_report_dialog.py b/qt/error_report_dialog.py index acf8f15e..9efdcc77 100644 --- a/qt/error_report_dialog.py +++ b/qt/error_report_dialog.py @@ -11,8 +11,8 @@ import os import platform -from PyQt5.QtCore import Qt, QCoreApplication, QSize -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import Qt, QCoreApplication, QSize +from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, @@ -30,7 +30,7 @@ class ErrorReportDialog(QDialog): def __init__(self, parent, github_url, error, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) self._setupUi() name = QCoreApplication.applicationName() @@ -40,7 +40,7 @@ def __init__(self, parent, github_url, error, **kwargs): ) # Under windows, we end up with an error report without linesep if we don't mangle it error_text = error_text.replace("\n", os.linesep) - self.errorTextEdit.setPlainText(error_text) + self.error_text_edit.setPlainText(error_text) self.github_url = github_url self.sendButton.clicked.connect(self.goToGitHub) @@ -49,14 +49,17 @@ def __init__(self, parent, github_url, error, **kwargs): def _setupUi(self): self.setWindowTitle(tr("Error Report")) self.resize(553, 349) - self.verticalLayout = QVBoxLayout(self) - self.label = QLabel(self) - self.label.setText(tr("Something went wrong. How about reporting the error?")) - self.label.setWordWrap(True) - self.verticalLayout.addWidget(self.label) - self.errorTextEdit = QPlainTextEdit(self) - self.errorTextEdit.setReadOnly(True) - self.verticalLayout.addWidget(self.errorTextEdit) + main_layout = QVBoxLayout(self) + + title_label = QLabel(self) + title_label.setText(tr("Something went wrong. How about reporting the error?")) + title_label.setWordWrap(True) + main_layout.addWidget(title_label) + + self.error_text_edit = QPlainTextEdit(self) + self.error_text_edit.setReadOnly(True) + main_layout.addWidget(self.error_text_edit) + msg = tr( "Error reports should be reported as GitHub issues. You can copy the error traceback " "above and paste it in a new issue.\n\nPlease make sure to run a search for any already " @@ -67,21 +70,28 @@ def _setupUi(self): "Although the application should continue to run after this error, it may be in an " "unstable state, so it is recommended that you restart the application." ) - self.label2 = QLabel(msg) - self.label2.setWordWrap(True) - self.verticalLayout.addWidget(self.label2) - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.addItem(horizontal_spacer()) - self.dontSendButton = QPushButton(self) - self.dontSendButton.setText(tr("Close")) - self.dontSendButton.setMinimumSize(QSize(110, 0)) - self.horizontalLayout.addWidget(self.dontSendButton) - self.sendButton = QPushButton(self) - self.sendButton.setText(tr("Go to GitHub")) - self.sendButton.setMinimumSize(QSize(110, 0)) - self.sendButton.setDefault(True) - self.horizontalLayout.addWidget(self.sendButton) - self.verticalLayout.addLayout(self.horizontalLayout) + instructions_label = QLabel(msg) + instructions_label.setWordWrap(True) + main_layout.addWidget(instructions_label) + + button_layout = QHBoxLayout() + button_layout.addItem(horizontal_spacer()) + + close_button = QPushButton(self) + close_button.setText(tr("Close")) + close_button.setMinimumSize(QSize(110, 0)) + button_layout.addWidget(close_button) + + report_button = QPushButton(self) + report_button.setText(tr("Go to GitHub")) + report_button.setMinimumSize(QSize(110, 0)) + report_button.setDefault(True) + button_layout.addWidget(report_button) + + main_layout.addLayout(button_layout) + + report_button.clicked.connect(self.goToGithub) + close_button.clicked.connect(self.reject) def goToGitHub(self): open_url(self.github_url) @@ -91,6 +101,6 @@ def install_excepthook(github_url): def my_excepthook(exctype, value, tb): s = "".join(traceback.format_exception(exctype, value, tb)) dialog = ErrorReportDialog(None, github_url, s) - dialog.exec_() + dialog.exec() sys.excepthook = my_excepthook diff --git a/run.py b/run.py index 37bab1b6..5fd48d95 100644 --- a/run.py +++ b/run.py @@ -9,8 +9,8 @@ import os.path as op import gc -from PyQt6.QtCore import QCoreApplication -from PyQt6.QtGui import QIcon, QPixmap +from PyQt6.QtCore import QDir +from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QApplication from hscommon.trans import install_gettext_trans_under_qt @@ -49,9 +49,10 @@ def setup_signals(): def main(): app = QApplication(sys.argv) app.setDesktopFileName("dupeguru") - QCoreApplication.setOrganizationName("Hardcoded Software") - QCoreApplication.setApplicationName(__appname__) - QCoreApplication.setApplicationVersion(__version__) + QApplication.setOrganizationName("Hardcoded Software") + QApplication.setApplicationName(__appname__) + QApplication.setApplicationVersion(__version__) + QDir.addSearchPath("images", op.join(BASE_PATH, "images")) setup_qt_logging() settings = create_qsettings() lang = settings.value("Language") @@ -71,7 +72,7 @@ def main(): # has been installed from qt.app import DupeGuru - app.setWindowIcon(QIcon(QPixmap(f":/{DupeGuru.LOGO_NAME}"))) + app.setWindowIcon(QIcon(f"images:{DupeGuru.LOGO_NAME}_32.png")) global dgapp dgapp = DupeGuru() install_excepthook("https://github.com/arsenetar/dupeguru/issues") From 0cc04c2dde90d9188d2481efa44da3922d5d4a14 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Fri, 8 Jul 2022 23:15:59 -0500 Subject: [PATCH 06/26] More updates mainly in preferences --- qt/app.py | 106 +++++++++++++++++++----------------- qt/me/details_dialog.py | 8 +-- qt/me/preferences_dialog.py | 23 +++----- qt/pe/preferences_dialog.py | 10 ++-- qt/preferences.py | 23 ++++---- qt/preferences_dialog.py | 95 +++++++++++++++----------------- qt/se/preferences_dialog.py | 32 +++++------ 7 files changed, 143 insertions(+), 154 deletions(-) diff --git a/qt/app.py b/qt/app.py index 30312cd8..9447809b 100644 --- a/qt/app.py +++ b/qt/app.py @@ -6,15 +6,18 @@ import sys import os.path as op +from typing import Type -from PyQt5.QtCore import QTimer, QObject, QUrl, pyqtSignal, Qt -from PyQt5.QtGui import QColor, QDesktopServices, QPalette -from PyQt5.QtWidgets import QApplication, QFileDialog, QDialog, QMessageBox, QStyleFactory, QToolTip +from PyQt6.QtCore import QTimer, QObject, QUrl, pyqtSignal, Qt +from PyQt6.QtGui import QColor, QDesktopServices, QPalette +from PyQt6.QtWidgets import QApplication, QFileDialog, QDialog, QMessageBox, QStyleFactory, QToolTip from hscommon.trans import trget from hscommon import desktop, plat from qt.about_box import AboutBox +from qt.details_dialog import DetailsDialog +from qt.preferences_dialog import PreferencesDialogBase from qt.recent import Recent from qt.util import create_actions from qt.progress_window import ProgressWindow @@ -42,10 +45,10 @@ class DupeGuru(QObject): - LOGO_NAME = "logo_se" + LOGO_NAME = "dgse_logo" NAME = "dupeGuru" - def __init__(self, **kwargs): + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.prefs = Preferences() self.prefs.load() @@ -56,7 +59,7 @@ def __init__(self, **kwargs): self._setup() # --- Private - def _setup(self): + def _setup(self) -> None: core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = PlatSpecificPhoto self._setupActions() self.details_dialog = None @@ -108,7 +111,7 @@ def _setup(self): # that the application haven't launched. QTimer.singleShot(0, self.finishedLaunching) - def _setupActions(self): + def _setupActions(self) -> None: # Setup actions that are common to both the directory dialog and the results window. # (name, shortcut, icon, desc, func) ACTIONS = [ @@ -154,7 +157,7 @@ def _setupActions(self): ] create_actions(ACTIONS, self) - def _update_options(self): + def _update_options(self) -> None: self.model.options["mix_file_kind"] = self.prefs.mix_file_kind self.model.options["escape_filter_regexp"] = not self.prefs.use_regexp self.model.options["clean_empty_dirs"] = self.prefs.remove_empty_folders @@ -202,7 +205,7 @@ def _update_options(self): self._set_style("dark" if self.prefs.use_dark_style else "light") # --- Private - def _get_details_dialog_class(self): + def _get_details_dialog_class(self) -> Type[DetailsDialog]: if self.model.app_mode == AppMode.PICTURE: return DetailsDialogPicture elif self.model.app_mode == AppMode.MUSIC: @@ -210,7 +213,7 @@ def _get_details_dialog_class(self): else: return DetailsDialogStandard - def _get_preferences_dialog_class(self): + def _get_preferences_dialog_class(self) -> Type[PreferencesDialogBase]: if self.model.app_mode == AppMode.PICTURE: return PreferencesDialogPicture elif self.model.app_mode == AppMode.MUSIC: @@ -218,7 +221,7 @@ def _get_preferences_dialog_class(self): else: return PreferencesDialogStandard - def _set_style(self, style="light"): + def _set_style(self, style: str = "light") -> None: # Only support this feature on windows for now if not plat.ISWINDOWS: return @@ -226,18 +229,18 @@ def _set_style(self, style="light"): QApplication.setStyle(QStyleFactory.create("Fusion")) palette = QApplication.style().standardPalette() palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53)) - palette.setColor(QPalette.ColorRole.WindowText, Qt.white) + palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white) palette.setColor(QPalette.ColorRole.Base, QColor(25, 25, 25)) palette.setColor(QPalette.ColorRole.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.ColorRole.ToolTipBase, QColor(53, 53, 53)) - palette.setColor(QPalette.ColorRole.ToolTipText, Qt.white) - palette.setColor(QPalette.ColorRole.Text, Qt.white) + palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.white) palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53)) - palette.setColor(QPalette.ColorRole.ButtonText, Qt.white) - palette.setColor(QPalette.ColorRole.BrightText, Qt.red) + palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white) + palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red) palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218)) palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218)) - palette.setColor(QPalette.ColorRole.HighlightedText, Qt.black) + palette.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.black) palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor(164, 166, 168)) palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor(164, 166, 168)) palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor(164, 166, 168)) @@ -252,29 +255,31 @@ def _set_style(self, style="light"): QApplication.setPalette(palette) # --- Public - def add_selected_to_ignore_list(self): + def add_selected_to_ignore_list(self) -> None: self.model.add_selected_to_ignore_list() - def remove_selected(self): - self.model.remove_selected(self) + def remove_selected(self) -> None: + self.model.remove_selected() - def confirm(self, title, msg, default_button=QMessageBox.Yes): + def confirm( + self, title: str, msg: str, default_button: QMessageBox.StandardButton = QMessageBox.StandardButton.Yes + ) -> bool: active = QApplication.activeWindow() - buttons = QMessageBox.Yes | QMessageBox.No + buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No answer = QMessageBox.question(active, title, msg, buttons, default_button) - return answer == QMessageBox.Yes + return answer == QMessageBox.StandardButton.Yes - def invokeCustomCommand(self): + def invokeCustomCommand(self) -> None: self.model.invoke_custom_command() - def show_details(self): + def show_details(self) -> None: if self.details_dialog is not None: if not self.details_dialog.isVisible(): self.details_dialog.show() else: self.details_dialog.hide() - def showResultsWindow(self): + def showResultsWindow(self) -> None: if self.resultWindow is not None: if self.use_tabs: if self.main_window.indexOfWidget(self.resultWindow) < 0: @@ -284,14 +289,14 @@ def showResultsWindow(self): else: self.resultWindow.show() - def showDirectoriesWindow(self): + def showDirectoriesWindow(self) -> None: if self.directories_dialog is not None: if self.use_tabs: self.main_window.showTab(self.directories_dialog) else: self.directories_dialog.show() - def shutdown(self): + def shutdown(self) -> None: self.willSavePrefs.emit() self.prefs.save() self.model.save() @@ -306,7 +311,7 @@ def shutdown(self): SIGTERM = pyqtSignal() # --- Events - def finishedLaunching(self): + def finishedLaunching(self) -> None: if sys.getfilesystemencoding() == "ascii": # No need to localize this, it's a debugging message. msg = ( @@ -326,28 +331,28 @@ def finishedLaunching(self): self.model.load_from(results) self.recentResults.insertItem(results) - def clearCacheTriggered(self): + def clearCacheTriggered(self) -> None: title = tr("Clear Cache") msg = tr("Do you really want to clear the cache? This will remove all cached file hashes and picture analysis.") - if self.confirm(title, msg, QMessageBox.No): + if self.confirm(title, msg, QMessageBox.StandardButton.No): self.model.clear_picture_cache() self.model.clear_hash_cache() active = QApplication.activeWindow() QMessageBox.information(active, title, tr("Cache cleared.")) - def ignoreListTriggered(self): + def ignoreListTriggered(self) -> None: if self.use_tabs: self.showTriggeredTabbedDialog(self.ignoreListDialog, tr("Ignore List")) else: # floating windows self.model.ignore_list_dialog.show() - def excludeListTriggered(self): + def excludeListTriggered(self) -> None: if self.use_tabs: self.showTriggeredTabbedDialog(self.excludeListDialog, tr("Exclusion Filters")) else: # floating windows self.model.exclude_list_dialog.show() - def showTriggeredTabbedDialog(self, dialog, desc_string): + def showTriggeredTabbedDialog(self, dialog, desc_string: str) -> None: """Add tab for dialog, name the tab with desc_string, then show it.""" index = self.main_window.indexOfWidget(dialog) # Create the tab if it doesn't exist already @@ -356,23 +361,22 @@ def showTriggeredTabbedDialog(self, dialog, desc_string): # Show the tab for that widget self.main_window.setCurrentIndex(index) - def openDebugLogTriggered(self): + def openDebugLogTriggered(self) -> None: debug_log_path = op.join(self.model.appdata, "debug.log") desktop.open_path(debug_log_path) - def preferencesTriggered(self): + def preferencesTriggered(self) -> None: preferences_dialog = self._get_preferences_dialog_class()( self.main_window if self.main_window else self.directories_dialog, self ) preferences_dialog.load() result = preferences_dialog.exec() - if result == QDialog.Accepted: + if result == QDialog.DialogCode.Accepted: preferences_dialog.save() self.prefs.save() self._update_options() - preferences_dialog.setParent(None) - def quitTriggered(self): + def quitTriggered(self) -> None: if self.details_dialog is not None: self.details_dialog.close() @@ -381,10 +385,10 @@ def quitTriggered(self): else: self.directories_dialog.close() - def showAboutBoxTriggered(self): + def showAboutBoxTriggered(self) -> None: self.about_box.show() - def showHelpTriggered(self): + def showHelpTriggered(self) -> None: base_path = platform.HELP_PATH help_path = op.abspath(op.join(base_path, "index.html")) if op.exists(help_path): @@ -393,7 +397,7 @@ def showHelpTriggered(self): url = QUrl("https://dupeguru.voltaicideas.net/help/en/") QDesktopServices.openUrl(url) - def handleSIGTERM(self): + def handleSIGTERM(self) -> None: self.shutdown() # --- model --> view @@ -403,20 +407,20 @@ def get_default(self, key): def set_default(self, key, value): self.prefs.set_value(key, value) - def show_message(self, msg): + def show_message(self, msg: str) -> None: window = QApplication.activeWindow() QMessageBox.information(window, "", msg) - def ask_yes_no(self, prompt): + def ask_yes_no(self, prompt: str) -> bool: return self.confirm("", prompt) - def create_results_window(self): + def create_results_window(self) -> None: """Creates resultWindow and details_dialog depending on the selected ``app_mode``.""" if self.details_dialog is not None: # The object is not deleted entirely, avoid saving its geometry in the future # self.willSavePrefs.disconnect(self.details_dialog.appWillSavePrefs) # or simply delete it on close which is probably cleaner: - self.details_dialog.setAttribute(Qt.WA_DeleteOnClose) + self.details_dialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.details_dialog.close() # if we don't do the following, Qt will crash when we recreate the Results dialog self.details_dialog.setParent(None) @@ -431,17 +435,17 @@ def create_results_window(self): self.directories_dialog._updateActionsState() self.details_dialog = self._get_details_dialog_class()(self.resultWindow, self) - def show_results_window(self): + def show_results_window(self) -> None: self.showResultsWindow() - def show_problem_dialog(self): + def show_problem_dialog(self) -> None: self.problemDialog.show() - def select_dest_folder(self, prompt): - flags = QFileDialog.ShowDirsOnly + def select_dest_folder(self, prompt: str) -> str: + flags = QFileDialog.Option.ShowDirsOnly return QFileDialog.getExistingDirectory(self.resultWindow, prompt, "", flags) - def select_dest_file(self, prompt, extension): + def select_dest_file(self, prompt: str, extension: str) -> str: files = tr("{} file (*.{})").format(extension.upper(), extension) destination, chosen_filter = QFileDialog.getSaveFileName(self.resultWindow, prompt, "", files) if not destination.endswith(f".{extension}"): diff --git a/qt/me/details_dialog.py b/qt/me/details_dialog.py index 43619af6..4b47d277 100644 --- a/qt/me/details_dialog.py +++ b/qt/me/details_dialog.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import QAbstractItemView +from PyQt6.QtCore import QSize +from PyQt6.QtWidgets import QAbstractItemView from hscommon.trans import trget from qt.details_dialog import DetailsDialog as DetailsDialogBase @@ -15,12 +15,12 @@ class DetailsDialog(DetailsDialogBase): - def _setupUi(self): + def _setupUi(self) -> None: self.setWindowTitle(tr("Details")) self.resize(502, 295) self.setMinimumSize(QSize(250, 250)) self.tableView = DetailsTable(self) self.tableView.setAlternatingRowColors(True) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.tableView.setShowGrid(False) self.setWidget(self.tableView) diff --git a/qt/me/preferences_dialog.py b/qt/me/preferences_dialog.py index abcd7a16..61ba91d5 100644 --- a/qt/me/preferences_dialog.py +++ b/qt/me/preferences_dialog.py @@ -4,27 +4,22 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import ( - QVBoxLayout, - QHBoxLayout, - QLabel, - QSizePolicy, - QSpacerItem, - QWidget, -) +from typing import Callable +from PyQt6.QtCore import QSize +from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget, QCheckBox from hscommon.trans import trget from core.app import AppMode from core.scanner import ScanType +from qt.preferences import Preferences -from qt.preferences_dialog import PreferencesDialogBase +from qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") class PreferencesDialog(PreferencesDialogBase): - def _setupPreferenceWidgets(self): + def _setupPreferenceWidgets(self) -> None: self._setupFilterHardnessBox() self.widgetsVLayout.addLayout(self.filterHardnessHLayout) self.widget = QWidget(self) @@ -37,7 +32,7 @@ def _setupPreferenceWidgets(self): self.verticalLayout_4.addWidget(self.label_6) self.horizontalLayout_2 = QHBoxLayout() self.horizontalLayout_2.setSpacing(0) - spacer_item = QSpacerItem(15, 20, QSizePolicy.Fixed, QSizePolicy.Minimum) + spacer_item = QSpacerItem(15, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum) self.horizontalLayout_2.addItem(spacer_item) self._setupAddCheckbox("tagTrackBox", tr("Track"), self.widget) self.horizontalLayout_2.addWidget(self.tagTrackBox) @@ -70,7 +65,7 @@ def _setupPreferenceWidgets(self): self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches) self._setupBottomPart() - def _load(self, prefs, setchecked, section): + def _load(self, prefs: Preferences, setchecked: Callable[[QCheckBox, bool], None], section: Sections) -> None: setchecked(self.tagTrackBox, prefs.scan_tag_track) setchecked(self.tagArtistBox, prefs.scan_tag_artist) setchecked(self.tagAlbumBox, prefs.scan_tag_album) @@ -99,7 +94,7 @@ def _load(self, prefs, setchecked, section): self.tagGenreBox.setEnabled(tag_based) self.tagYearBox.setEnabled(tag_based) - def _save(self, prefs, ischecked): + def _save(self, prefs: Preferences, ischecked: Callable[[QCheckBox], bool]) -> None: prefs.scan_tag_track = ischecked(self.tagTrackBox) prefs.scan_tag_artist = ischecked(self.tagArtistBox) prefs.scan_tag_album = ischecked(self.tagAlbumBox) diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index 735d4eda..67d35613 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -10,13 +10,13 @@ from core.scanner import ScanType from core.app import AppMode -from qt.preferences_dialog import PreferencesDialogBase +from qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") class PreferencesDialog(PreferencesDialogBase): - def _setupPreferenceWidgets(self): + def _setupPreferenceWidgets(self) -> None: self._setupFilterHardnessBox() self.widgetsVLayout.addLayout(self.filterHardnessHLayout) self._setupAddCheckbox("matchScaledBox", tr("Match pictures of different dimensions")) @@ -37,7 +37,7 @@ def _setupPreferenceWidgets(self): self._setupBottomPart() - def _setupDisplayPage(self): + def _setupDisplayPage(self) -> None: super()._setupDisplayPage() self._setupAddCheckbox("details_dialog_override_theme_icons", tr("Override theme icons in viewer toolbar")) self.details_dialog_override_theme_icons.setToolTip( @@ -57,7 +57,7 @@ def _setupDisplayPage(self): ) self.details_groupbox_layout.insertWidget(index + 2, self.details_dialog_viewers_show_scrollbars) - def _load(self, prefs, setchecked, section): + def _load(self, prefs: Preferences, setchecked: Callable[[QCheckBox, bool], None], section: Sections) -> None: setchecked(self.matchScaledBox, prefs.match_scaled) setchecked(self.matchRotatedBox, prefs.match_rotated) @@ -68,7 +68,7 @@ def _load(self, prefs, setchecked, section): setchecked(self.details_dialog_override_theme_icons, prefs.details_dialog_override_theme_icons) setchecked(self.details_dialog_viewers_show_scrollbars, prefs.details_dialog_viewers_show_scrollbars) - def _save(self, prefs, ischecked): + def _save(self, prefs: Preferences, ischecked: Callable[[QCheckBox], bool]) -> None: prefs.match_scaled = ischecked(self.matchScaledBox) prefs.match_rotated = ischecked(self.matchRotatedBox) prefs.details_dialog_override_theme_icons = ischecked(self.details_dialog_override_theme_icons) diff --git a/qt/preferences.py b/qt/preferences.py index 1f88cc31..546f1b54 100644 --- a/qt/preferences.py +++ b/qt/preferences.py @@ -4,9 +4,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtWidgets import QApplication, QDockWidget -from PyQt5.QtCore import Qt, QRect, QObject, pyqtSignal -from PyQt5.QtGui import QColor +from typing import Any, Tuple +from PyQt6.QtWidgets import QApplication, QDockWidget +from PyQt6.QtCore import Qt, QRect, QObject, pyqtSignal +from PyQt6.QtGui import QColor from hscommon import trans from hscommon.plat import ISLINUX @@ -126,7 +127,7 @@ def set_rect(self, name, r): def set_value(self, name, value): self._settings.setValue(name, _normalize_for_serialization(value)) - def saveGeometry(self, name, widget): + def saveGeometry(self, name, widget) -> None: # We save geometry under a 7-sized int array: first item is a flag # for whether the widget is maximized, second item is a flag for whether # the widget is docked, third item is a Qt::DockWidgetArea enum value, @@ -138,12 +139,12 @@ def saveGeometry(self, name, widget): rect_as_list = [r.x(), r.y(), r.width(), r.height()] self.set_value(name, [m, d, area] + rect_as_list) - def restoreGeometry(self, name, widget): + def restoreGeometry(self, name, widget) -> Tuple[bool, Any]: geometry = self.get_value(name) if geometry and len(geometry) == 7: m, d, area, x, y, w, h = geometry if m: - widget.setWindowState(Qt.WindowMaximized) + widget.setWindowState(Qt.WindowState.WindowMaximized) else: r = QRect(x, y, w, h) widget.setGeometry(r) @@ -154,7 +155,7 @@ def restoreGeometry(self, name, widget): class Preferences(PreferencesBase): - def _load_values(self, settings): + def _load_values(self, settings) -> None: get = self.get_value self.filter_hardness = get("FilterHardness", self.filter_hardness) self.mix_file_kind = get("MixFileKind", self.mix_file_kind) @@ -227,7 +228,7 @@ def _load_values(self, settings): self.match_scaled = get("MatchScaled", self.match_scaled) self.match_rotated = get("MatchRotated", self.match_rotated) - def reset(self): + def reset(self) -> None: self.filter_hardness = 95 self.mix_file_kind = True self.use_regexp = False @@ -251,8 +252,8 @@ def reset(self): # By default use internal icons on platforms other than Linux for now self.details_dialog_override_theme_icons = False if not ISLINUX else True self.details_dialog_viewers_show_scrollbars = True - self.result_table_ref_foreground_color = QColor(Qt.blue) - self.result_table_ref_background_color = QColor(Qt.lightGray) + self.result_table_ref_foreground_color = QColor(Qt.GlobalColor.blue) + self.result_table_ref_background_color = QColor(Qt.GlobalColor.lightGray) self.result_table_delta_foreground_color = QColor(255, 142, 40) # orange self.resultWindowIsMaximized = False self.resultWindowRect = None @@ -280,7 +281,7 @@ def reset(self): self.match_scaled = False self.match_rotated = False - def _save_values(self, settings): + def _save_values(self, settings) -> None: set_ = self.set_value set_("FilterHardness", self.filter_hardness) set_("MixFileKind", self.mix_file_kind) diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index 216d3f7e..6f6e19e5 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -4,8 +4,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QSize, pyqtSlot -from PyQt5.QtWidgets import ( +from typing import Union +from PyQt6.QtCore import Qt, QSize, pyqtSlot +from PyQt6.QtWidgets import ( QDialog, QDialogButtonBox, QVBoxLayout, @@ -28,7 +29,7 @@ QGroupBox, QFormLayout, ) -from PyQt5.QtGui import QPixmap, QIcon +from PyQt6.QtGui import QPixmap, QIcon, QShowEvent from hscommon import desktop, plat from hscommon.trans import trget @@ -39,6 +40,11 @@ from qt.preferences import Preferences +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from qt.app import DupeGuru + tr = trget("ui") @@ -53,8 +59,8 @@ class Sections(Flag): class PreferencesDialogBase(QDialog): - def __init__(self, parent, app, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + def __init__(self, parent: QWidget, app: "DupeGuru", **kwargs): + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) self.app = app self.supportedLanguages = dict(sorted(get_langnames().items(), key=lambda item: item[1])) @@ -66,7 +72,7 @@ def __init__(self, parent, app, **kwargs): self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) - def _setupFilterHardnessBox(self): + def _setupFilterHardnessBox(self) -> None: self.filterHardnessHLayout = QHBoxLayout() self.filterHardnessLabel = QLabel(self) self.filterHardnessLabel.setText(tr("Filter Hardness:")) @@ -77,7 +83,7 @@ def _setupFilterHardnessBox(self): self.filterHardnessHLayoutSub1 = QHBoxLayout() self.filterHardnessHLayoutSub1.setSpacing(12) self.filterHardnessSlider = QSlider(self) - size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + size_policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.filterHardnessSlider.sizePolicy().hasHeightForWidth()) @@ -85,7 +91,7 @@ def _setupFilterHardnessBox(self): self.filterHardnessSlider.setMinimum(1) self.filterHardnessSlider.setMaximum(100) self.filterHardnessSlider.setTracking(True) - self.filterHardnessSlider.setOrientation(Qt.Horizontal) + self.filterHardnessSlider.setOrientation(Qt.Orientation.Horizontal) self.filterHardnessHLayoutSub1.addWidget(self.filterHardnessSlider) self.filterHardnessLabel = QLabel(self) self.filterHardnessLabel.setText("100") @@ -97,7 +103,7 @@ def _setupFilterHardnessBox(self): self.moreResultsLabel = QLabel(self) self.moreResultsLabel.setText(tr("More Results")) self.filterHardnessHLayoutSub2.addWidget(self.moreResultsLabel) - spacer_item = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.filterHardnessHLayoutSub2.addItem(spacer_item) self.fewerResultsLabel = QLabel(self) self.fewerResultsLabel.setText(tr("Fewer Results")) @@ -105,7 +111,7 @@ def _setupFilterHardnessBox(self): self.filterHardnessVLayout.addLayout(self.filterHardnessHLayoutSub2) self.filterHardnessHLayout.addLayout(self.filterHardnessVLayout) - def _setupBottomPart(self): + def _setupBottomPart(self) -> None: # The bottom part of the pref panel is always the same in all editions. self.copyMoveLabel = QLabel(self) self.copyMoveLabel.setText(tr("Copy and Move:")) @@ -121,7 +127,7 @@ def _setupBottomPart(self): self.customCommandEdit = QLineEdit(self) self.widgetsVLayout.addWidget(self.customCommandEdit) - def _setupDisplayPage(self): + def _setupDisplayPage(self) -> None: self.ui_groupbox = QGroupBox("&" + tr("General Interface")) layout = QVBoxLayout() self.languageLabel = QLabel(tr("Language:"), self) @@ -173,7 +179,7 @@ def _setupDisplayPage(self): formlayout.addRow(tr("Reference background color:"), self.result_table_ref_background_color) self.result_table_delta_foreground_color = ColorPickerButton(self) formlayout.addRow(tr("Delta foreground color:"), self.result_table_delta_foreground_color) - formlayout.setLabelAlignment(Qt.AlignLeft) + formlayout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) # Keep same vertical spacing as parent layout for consistency formlayout.setVerticalSpacing(self.displayVLayout.spacing()) @@ -215,21 +221,7 @@ def _setupDisplayPage(self): details_groupbox.setLayout(self.details_groupbox_layout) self.displayVLayout.addWidget(details_groupbox) - def _setup_advanced_page(self): - tab_label = QLabel( - tr( - "These options are for advanced users or for very specific situations, \ -most users should not have to modify these." - ), - wordWrap=True, - ) - self.advanced_vlayout.addWidget(tab_label) - self._setupAddCheckbox("include_exists_check_box", tr("Include existence check after scan completion")) - self.advanced_vlayout.addWidget(self.include_exists_check_box) - self._setupAddCheckbox("rehash_ignore_mtime_box", tr("Ignore difference in mtime when loading cached digests")) - self.advanced_vlayout.addWidget(self.rehash_ignore_mtime_box) - - def _setupDebugPage(self): + def _setupDebugPage(self) -> None: self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)")) self._setupAddCheckbox("profile_scan_box", tr("Profile scan operation")) self.profile_scan_box.setToolTip(tr("Profile the scan operation and save logs for optimization.")) @@ -241,7 +233,7 @@ def _setupDebugPage(self): ) self.debugVLayout.addWidget(self.debug_location_label) - def _setupAddCheckbox(self, name, label, parent=None): + def _setupAddCheckbox(self, name: str, label: str, parent: Union[QWidget, None] = None) -> None: if parent is None: parent = self cb = QCheckBox(parent) @@ -252,7 +244,7 @@ def _setupPreferenceWidgets(self): # Edition-specific pass - def _setupUi(self): + def _setupUi(self) -> None: self.setWindowTitle(tr("Options")) self.setSizeGripEnabled(False) self.setModal(True) @@ -278,11 +270,13 @@ def _setupUi(self): # self.mainVLayout.addLayout(self.widgetsVLayout) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons( - QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.RestoreDefaults + QDialogButtonBox.StandardButton.Cancel + | QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.RestoreDefaults ) self.mainVLayout.addWidget(self.tabwidget) self.mainVLayout.addWidget(self.buttonBox) - self.layout().setSizeConstraint(QLayout.SetFixedSize) + self.layout().setSizeConstraint(QLayout.SizeConstraint.SetFixedSize) self.tabwidget.addTab(self.page_general, tr("General")) self.tabwidget.addTab(self.page_display, tr("Display")) self.tabwidget.addTab(self.page_advanced, tr("Advanced")) @@ -292,20 +286,20 @@ def _setupUi(self): self.advanced_vlayout.addStretch(0) self.debugVLayout.addStretch(0) - def _load(self, prefs, setchecked, section): + def _load(self, prefs, setchecked, section) -> None: # Edition-specific pass - def _save(self, prefs, ischecked): + def _save(self, prefs, ischecked) -> None: # Edition-specific pass - def load(self, prefs=None, section=Sections.ALL): + def load(self, prefs: Preferences = None, section: Sections = Sections.ALL) -> None: if prefs is None: prefs = self.app.prefs - def setchecked(cb, b): - cb.setCheckState(Qt.Checked if b else Qt.Unchecked) + def setchecked(cb: QCheckBox, b: bool) -> None: + cb.setCheckState(Qt.CheckState.Checked if b else Qt.CheckState.Unchecked) if section & Sections.GENERAL: self.filterHardnessSlider.setValue(prefs.filter_hardness) @@ -348,12 +342,12 @@ def setchecked(cb, b): setchecked(self.profile_scan_box, prefs.profile_scan) self._load(prefs, setchecked, section) - def save(self): + def save(self) -> None: prefs = self.app.prefs prefs.filter_hardness = self.filterHardnessSlider.value() - def ischecked(cb): - return cb.checkState() == Qt.Checked + def ischecked(cb: QCheckBox) -> bool: + return cb.checkState() == Qt.CheckState.Checked prefs.mix_file_kind = ischecked(self.mixFileKindBox) prefs.use_regexp = ischecked(self.useRegexpBox) @@ -390,13 +384,13 @@ def ischecked(cb): self.app.prefs.language = lang_code self._save(prefs, ischecked) - def resetToDefaults(self, section_to_update): + def resetToDefaults(self, section_to_update: Sections) -> None: self.load(Preferences(), section_to_update) # --- Events - def buttonClicked(self, button): + def buttonClicked(self, button: QPushButton) -> None: role = self.buttonBox.buttonRole(button) - if role == QDialogButtonBox.ResetRole: + if role == QDialogButtonBox.ButtonRole.ResetRole: current_tab = self.tabwidget.currentWidget() section_to_update = Sections.ALL if current_tab is self.page_general: @@ -407,30 +401,31 @@ def buttonClicked(self, button): section_to_update = Sections.DEBUG self.resetToDefaults(section_to_update) - def showEvent(self, event): + def showEvent(self, event: QShowEvent) -> None: # have to do this here as the frameGeometry is not correct until shown move_to_screen_center(self) super().showEvent(event) class ColorPickerButton(QPushButton): - def __init__(self, parent): + def __init__(self, parent: QWidget) -> None: super().__init__(parent) - self.parent = parent self.color = None self.clicked.connect(self.onClicked) @pyqtSlot() - def onClicked(self): - color = QColorDialog.getColor(self.color if self.color is not None else Qt.white, self.parent) + def onClicked(self) -> None: + color = QColorDialog.getColor( + self.color if self.color is not None else Qt.GlobalColor.white, self.parentWidget() + ) self.setColor(color) - def setColor(self, color): + def setColor(self, color) -> None: size = QSize(16, 16) px = QPixmap(size) if color is None: - size.width = 0 - size.height = 0 + size.setWidth(0) + size.setHeight(0) elif not color.isValid(): return else: diff --git a/qt/se/preferences_dialog.py b/qt/se/preferences_dialog.py index d3a69fe5..dbe76021 100644 --- a/qt/se/preferences_dialog.py +++ b/qt/se/preferences_dialog.py @@ -4,29 +4,23 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import ( - QSpinBox, - QVBoxLayout, - QHBoxLayout, - QLabel, - QSizePolicy, - QSpacerItem, - QWidget, -) +from typing import Callable +from PyQt6.QtCore import QSize +from PyQt6.QtWidgets import QSpinBox, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget, QCheckBox from hscommon.trans import trget from core.app import AppMode from core.scanner import ScanType +from qt.preferences import Preferences -from qt.preferences_dialog import PreferencesDialogBase +from qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") class PreferencesDialog(PreferencesDialogBase): - def _setupPreferenceWidgets(self): + def _setupPreferenceWidgets(self) -> None: self._setupFilterHardnessBox() self.widgetsVLayout.addLayout(self.filterHardnessHLayout) self.widget = QWidget(self) @@ -50,7 +44,7 @@ def _setupPreferenceWidgets(self): self._setupAddCheckbox("ignoreSmallFilesBox", tr("Ignore files smaller than"), self.widget) self.horizontalLayout_2.addWidget(self.ignoreSmallFilesBox) self.sizeThresholdSpinBox = QSpinBox(self.widget) - size_policy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) + size_policy = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth(self.sizeThresholdSpinBox.sizePolicy().hasHeightForWidth()) @@ -61,14 +55,14 @@ def _setupPreferenceWidgets(self): self.label_6 = QLabel(self.widget) self.label_6.setText(tr("KB")) self.horizontalLayout_2.addWidget(self.label_6) - spacer_item1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + spacer_item1 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout_2.addItem(spacer_item1) self.verticalLayout_4.addLayout(self.horizontalLayout_2) self.horizontalLayout_2a = QHBoxLayout() self._setupAddCheckbox("ignoreLargeFilesBox", tr("Ignore files larger than"), self.widget) self.horizontalLayout_2a.addWidget(self.ignoreLargeFilesBox) self.sizeSaturationSpinBox = QSpinBox(self.widget) - size_policy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) + size_policy = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) self.sizeSaturationSpinBox.setSizePolicy(size_policy) self.sizeSaturationSpinBox.setMaximumSize(QSize(300, 16777215)) self.sizeSaturationSpinBox.setRange(0, 1000000) @@ -76,7 +70,7 @@ def _setupPreferenceWidgets(self): self.label_6a = QLabel(self.widget) self.label_6a.setText(tr("MB")) self.horizontalLayout_2a.addWidget(self.label_6a) - spacer_item3 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + spacer_item3 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout_2a.addItem(spacer_item3) self.verticalLayout_4.addLayout(self.horizontalLayout_2a) self.horizontalLayout_2b = QHBoxLayout() @@ -94,7 +88,7 @@ def _setupPreferenceWidgets(self): self.label_6b = QLabel(self.widget) self.label_6b.setText(tr("MB")) self.horizontalLayout_2b.addWidget(self.label_6b) - spacer_item2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + spacer_item2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout_2b.addItem(spacer_item2) self.verticalLayout_4.addLayout(self.horizontalLayout_2b) self._setupAddCheckbox( @@ -106,7 +100,7 @@ def _setupPreferenceWidgets(self): self.widgetsVLayout.addWidget(self.widget) self._setupBottomPart() - def _load(self, prefs, setchecked, section): + def _load(self, prefs: Preferences, setchecked: Callable[[QCheckBox, bool], None], section: Sections) -> None: setchecked(self.matchSimilarBox, prefs.match_similar) setchecked(self.wordWeightingBox, prefs.word_weighting) setchecked(self.ignoreSmallFilesBox, prefs.ignore_small_files) @@ -123,7 +117,7 @@ def _load(self, prefs, setchecked, section): self.matchSimilarBox.setEnabled(word_based) self.wordWeightingBox.setEnabled(word_based) - def _save(self, prefs, ischecked): + def _save(self, prefs: Preferences, ischecked: Callable[[QCheckBox], bool]) -> None: prefs.match_similar = ischecked(self.matchSimilarBox) prefs.word_weighting = ischecked(self.wordWeightingBox) prefs.ignore_small_files = ischecked(self.ignoreSmallFilesBox) From d43b0b2aa536dd202bdc7576ba8582912b4bf65c Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sat, 9 May 2026 09:26:46 +0200 Subject: [PATCH 07/26] Port Qt frontend from PyQt5 to PyQt6 - Update all PyQt5 imports to PyQt6 - Move QAction from QtWidgets to QtGui - Replace removed QDesktopWidget with QApplication.screenAt() - Update all Qt enum accesses to fully-qualified namespaced form - Replace exec_() calls with exec() - Replace pyrcc5 with rcc -g python --- Makefile | 5 +- build.py | 2 +- hscommon/build.py | 3 +- qt/column.py | 10 ++-- qt/deletion_options.py | 12 ++--- qt/details_dialog.py | 10 ++-- qt/details_table.py | 32 ++++++----- qt/directories_dialog.py | 40 +++++++------- qt/directories_model.py | 40 +++++++------- qt/error_report_dialog.py | 2 +- qt/exclude_list_dialog.py | 26 ++++----- qt/exclude_list_table.py | 35 ++++++------ qt/ignore_list_dialog.py | 12 ++--- qt/pe/details_dialog.py | 18 +++---- qt/pe/image_viewer.py | 105 ++++++++++++++++++++---------------- qt/pe/photo.py | 4 +- qt/pe/preferences_dialog.py | 5 ++ qt/preferences_dialog.py | 4 ++ qt/prioritize_dialog.py | 28 +++++----- qt/progress_window.py | 8 +-- qt/radio_box.py | 4 +- qt/recent.py | 4 +- qt/result_window.py | 27 +++++----- qt/results_model.py | 36 ++++++------- qt/se/details_dialog.py | 6 +-- qt/search_edit.py | 22 ++++---- qt/selectable_list.py | 8 +-- qt/tabbed_window.py | 14 ++--- qt/table.py | 24 ++++----- qt/tree_model.py | 2 +- requirements.txt | 2 +- 31 files changed, 293 insertions(+), 257 deletions(-) diff --git a/Makefile b/Makefile index f9b66917..4331b78a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PYTHON ?= python3 PYTHON_VERSION_MINOR := $(shell ${PYTHON} -c "import sys; print(sys.version_info.minor)") -PYRCC5 ?= pyrcc5 +RCC ?= rcc REQ_MINOR_VERSION = 7 PREFIX ?= /usr/local @@ -77,7 +77,8 @@ build/help: | env $(VENV_PYTHON) build.py --doc qt/dg_rc.py: qt/dg.qrc - $(PYRCC5) qt/dg.qrc > qt/dg_rc.py + $(RCC) -g python qt/dg.qrc -o qt/dg_rc.py + sed -i 's/from PySide6/from PyQt6/' qt/dg_rc.py i18n: $(mofiles) diff --git a/build.py b/build.py index ac650b20..fda8d0c4 100644 --- a/build.py +++ b/build.py @@ -130,7 +130,7 @@ def build_normal(): build_localizations() print("Building Qt stuff") Path("qt", "dg_rc.py").unlink(missing_ok=True) - print_and_do("pyrcc5 {} > {}".format(Path("qt", "dg.qrc"), Path("qt", "dg_rc.py"))) + print_and_do("rcc -g python {} -o {}".format(Path("qt", "dg.qrc"), Path("qt", "dg_rc.py"))) fix_qt_resource_file(Path("qt", "dg_rc.py")) build_help() diff --git a/hscommon/build.py b/hscommon/build.py index bf1e4660..625a3d23 100644 --- a/hscommon/build.py +++ b/hscommon/build.py @@ -309,12 +309,13 @@ def iter_by_three(it): def fix_qt_resource_file(path: os.PathLike) -> None: - # pyrcc5 under Windows, if the locale is non-english, can produce a source file with a date + # rcc under Windows, if the locale is non-english, can produce a source file with a date # containing accented characters. If it does, the encoding is wrong and it prevents the file # from being correctly frozen by cx_freeze. To work around that, we open the file, strip all # comments, and save. with open(path, "rb") as fp: contents = fp.read() + contents = contents.replace(b"PySide6", b"PyQt6") lines = contents.split(b"\n") lines = [line for line in lines if not line.startswith(b"#")] with open(path, "wb") as fp: diff --git a/qt/column.py b/qt/column.py index 44d3bb0a..824f49ac 100644 --- a/qt/column.py +++ b/qt/column.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QHeaderView +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QHeaderView class Column: @@ -16,7 +16,7 @@ def __init__( attrname, default_width, editor=None, - alignment=Qt.AlignLeft, + alignment=Qt.AlignmentFlag.AlignLeft, cant_truncate=False, painter=None, resize_to_fit=False, @@ -37,7 +37,7 @@ class Columns: def __init__(self, model, columns, header_view): self.model = model self._header_view = header_view - self._header_view.setDefaultAlignment(Qt.AlignLeft) + self._header_view.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft) def setspecs(col, modelcol): modelcol.default_width = col.default_width @@ -62,7 +62,7 @@ def setspecs(col, modelcol): # See moneyguru #14 and #15. This was added in order to allow automatic resizing of columns. for column in self.model.column_list: if column.resize_to_fit: - self._header_view.setSectionResizeMode(column.logical_index, QHeaderView.ResizeToContents) + self._header_view.setSectionResizeMode(column.logical_index, QHeaderView.ResizeMode.ResizeToContents) # --- Public def set_columns_width(self, widths): diff --git a/qt/deletion_options.py b/qt/deletion_options.py index 2f4ef1b6..d12e63dc 100644 --- a/qt/deletion_options.py +++ b/qt/deletion_options.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QCheckBox, QDialogButtonBox +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QCheckBox, QDialogButtonBox from hscommon.trans import trget from qt.radio_box import RadioBox @@ -17,7 +17,7 @@ class DeletionOptions(QDialog): def __init__(self, parent, model, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) self.model = model self._setupUi() @@ -57,8 +57,8 @@ def _setupUi(self): self.directMessageLabel.setWordWrap(True) self.verticalLayout.addWidget(self.directMessageLabel) self.buttonBox = QDialogButtonBox() - self.buttonBox.addButton(tr("Proceed"), QDialogButtonBox.AcceptRole) - self.buttonBox.addButton(tr("Cancel"), QDialogButtonBox.RejectRole) + self.buttonBox.addButton(tr("Proceed"), QDialogButtonBox.ButtonRole.AcceptRole) + self.buttonBox.addButton(tr("Cancel"), QDialogButtonBox.ButtonRole.RejectRole) self.verticalLayout.addWidget(self.buttonBox) # --- Signals @@ -77,7 +77,7 @@ def show(self): self.model.link_deleted = self.linkCheckbox.isChecked() self.model.use_hardlinks = self.linkTypeRadio.selected_index == 1 self.model.direct = self.directCheckbox.isChecked() - return result == QDialog.Accepted + return result == QDialog.DialogCode.Accepted def set_hardlink_option_enabled(self, is_enabled: bool): self.linkTypeRadio.setEnabled(is_enabled) diff --git a/qt/details_dialog.py b/qt/details_dialog.py index 63e41c62..6b838116 100644 --- a/qt/details_dialog.py +++ b/qt/details_dialog.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDockWidget, QWidget +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QDockWidget, QWidget from qt.util import move_to_screen_center from qt.details_table import DetailsModel @@ -16,11 +16,11 @@ class DetailsDialog(QDockWidget): def __init__(self, parent, app, **kwargs): - super().__init__(parent, Qt.Tool, **kwargs) + super().__init__(parent, Qt.WindowType.Tool, **kwargs) self.parent = parent self.app = app self.model = app.model.details_panel - self.setAllowedAreas(Qt.AllDockWidgetAreas) + self.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas) self._setupUi() # To avoid saving uninitialized geometry on appWillSavePrefs, we track whether our dialog # has been shown. If it has, we know that our geometry should be saved. @@ -32,7 +32,7 @@ def __init__(self, parent, app, **kwargs): self.model.view = self self.app.willSavePrefs.connect(self.appWillSavePrefs) # self.setAttribute(Qt.WA_DeleteOnClose) - parent.addDockWidget(area if self._wasDocked else Qt.BottomDockWidgetArea, self) + parent.addDockWidget(area if self._wasDocked else Qt.DockWidgetArea.BottomDockWidgetArea, self) def _setupUi(self): # Virtual pass diff --git a/qt/details_table.py b/qt/details_table.py index 56d51bda..5e88c453 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -6,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QAbstractTableModel -from PyQt5.QtWidgets import QHeaderView, QTableView -from PyQt5.QtGui import QFont, QBrush +from PyQt6.QtCore import Qt, QAbstractTableModel +from PyQt6.QtWidgets import QHeaderView, QTableView +from PyQt6.QtGui import QFont, QBrush from hscommon.trans import trget @@ -39,24 +39,28 @@ def data(self, index, role): or self.model.row(row)[1] == "---" or self.model.row(row)[2] == "---" ): - if role != Qt.DisplayRole: + if role != Qt.ItemDataRole.DisplayRole: return None return self.model.row(row)[column] - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return self.model.row(row)[column] - if role == Qt.ForegroundRole and self.model.row(row)[1] != self.model.row(row)[2]: + if role == Qt.ItemDataRole.ForegroundRole and self.model.row(row)[1] != self.model.row(row)[2]: return QBrush(self.prefs.details_table_delta_foreground_color) - if role == Qt.FontRole and self.model.row(row)[1] != self.model.row(row)[2]: + if role == Qt.ItemDataRole.FontRole and self.model.row(row)[1] != self.model.row(row)[2]: font = QFont(self.model.view.font()) # or simply QFont() font.setBold(True) return font return None # QVariant() def headerData(self, section, orientation, role): - if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADER): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole and section < len(HEADER): return HEADER[section] - elif orientation == Qt.Vertical and role == Qt.DisplayRole and section < self.model.row_count(): + elif ( + orientation == Qt.Orientation.Vertical + and role == Qt.ItemDataRole.DisplayRole + and section < self.model.row_count() + ): # Read "Attribute" cell for horizontal header return self.model.row(section)[0] return None @@ -69,8 +73,8 @@ class DetailsTable(QTableView): def __init__(self, *args): QTableView.__init__(self, *args) self.setAlternatingRowColors(True) - self.setSelectionBehavior(QTableView.SelectRows) - self.setSelectionMode(QTableView.NoSelection) + self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) + self.setSelectionMode(QTableView.SelectionMode.NoSelection) self.setShowGrid(False) self.setWordWrap(False) self.setCornerButtonEnabled(False) @@ -80,12 +84,12 @@ def setModel(self, model): # The model needs to be set to set header stuff hheader = self.horizontalHeader() hheader.setHighlightSections(False) - hheader.setSectionResizeMode(0, QHeaderView.Stretch) - hheader.setSectionResizeMode(1, QHeaderView.Stretch) + hheader.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + hheader.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) vheader = self.verticalHeader() vheader.setVisible(True) vheader.setDefaultSectionSize(18) # hardcoded value above is not ideal, perhaps resize to contents first? # vheader.setSectionResizeMode(QHeaderView.ResizeToContents) - vheader.setSectionResizeMode(QHeaderView.Fixed) + vheader.setSectionResizeMode(QHeaderView.ResizeMode.Fixed) vheader.setSectionsMovable(True) diff --git a/qt/directories_dialog.py b/qt/directories_dialog.py index 272a7514..b0aec8aa 100644 --- a/qt/directories_dialog.py +++ b/qt/directories_dialog.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QRect, Qt -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import QRect, Qt +from PyQt6.QtWidgets import ( QListView, QWidget, QFileDialog, @@ -23,7 +23,7 @@ QLabel, QComboBox, ) -from PyQt5.QtGui import QPixmap, QIcon +from PyQt6.QtGui import QPixmap, QIcon from hscommon.trans import trget from core.app import AppMode @@ -167,36 +167,38 @@ def _setupUi(self): self.verticalLayout.setSpacing(0) hl = QHBoxLayout() label = QLabel(tr("Application Mode:"), self) - label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) hl.addWidget(label) self.appModeRadioBox = RadioBox(self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False) hl.addWidget(self.appModeRadioBox) self.verticalLayout.addLayout(hl) hl = QHBoxLayout() - hl.setAlignment(Qt.AlignLeft) + hl.setAlignment(Qt.AlignmentFlag.AlignLeft) label = QLabel(tr("Scan Type:"), self) - label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) hl.addWidget(label) self.scanTypeComboBox = QComboBox(self) - self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) + self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)) self.scanTypeComboBox.setMaximumWidth(400) hl.addWidget(self.scanTypeComboBox) self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget) - self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.showPreferencesButton.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) hl.addWidget(self.showPreferencesButton) self.verticalLayout.addLayout(hl) self.promptLabel = QLabel(tr('Select folders to scan and press "Scan".'), self.centralwidget) self.verticalLayout.addWidget(self.promptLabel) self.treeView = QTreeView(self.centralwidget) - self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.treeView.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.treeView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.treeView.setAcceptDrops(True) triggers = ( - QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked + QAbstractItemView.EditTrigger.DoubleClicked + | QAbstractItemView.EditTrigger.EditKeyPressed + | QAbstractItemView.EditTrigger.SelectedClicked ) self.treeView.setEditTriggers(triggers) self.treeView.setDragDropOverwriteMode(True) - self.treeView.setDragDropMode(QAbstractItemView.DropOnly) + self.treeView.setDragDropMode(QAbstractItemView.DragDropMode.DropOnly) self.treeView.setUniformRowHeights(True) self.verticalLayout.addWidget(self.treeView) self.horizontalLayout = QHBoxLayout() @@ -207,7 +209,7 @@ def _setupUi(self): self.addFolderButton = QPushButton(self.centralwidget) self.addFolderButton.setIcon(QIcon(QPixmap(":/plus"))) self.horizontalLayout.addWidget(self.addFolderButton) - spacer_item = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(spacer_item) self.loadResultsButton = QPushButton(self.centralwidget) self.loadResultsButton.setText(tr("Load Results")) @@ -230,8 +232,8 @@ def _setupUi(self): def _setupColumns(self): header = self.treeView.header() header.setStretchLastSection(False) - header.setSectionResizeMode(0, QHeaderView.Stretch) - header.setSectionResizeMode(1, QHeaderView.Fixed) + header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 100) def _updateActionsState(self): @@ -289,15 +291,15 @@ def addFolderTriggered(self): no_native = not self.app.prefs.use_native_dialogs title = tr("Select a folder to add to the scanning list") file_dialog = QFileDialog(self, title, self.lastAddedFolder) - file_dialog.setFileMode(QFileDialog.DirectoryOnly) - file_dialog.setOption(QFileDialog.DontUseNativeDialog, no_native) + file_dialog.setFileMode(QFileDialog.FileMode.Directory) + file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog, no_native) if no_native: file_view = file_dialog.findChild(QListView, "listView") if file_view: - file_view.setSelectionMode(QAbstractItemView.MultiSelection) + file_view.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) f_tree_view = file_dialog.findChild(QTreeView) if f_tree_view: - f_tree_view.setSelectionMode(QAbstractItemView.MultiSelection) + f_tree_view.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) if not file_dialog.exec(): return diff --git a/qt/directories_model.py b/qt/directories_model.py index 29ca8cc7..e67aa9b2 100644 --- a/qt/directories_model.py +++ b/qt/directories_model.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import pyqtSignal, Qt, QRect, QUrl, QModelIndex, QItemSelection -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import pyqtSignal, Qt, QRect, QUrl, QModelIndex, QItemSelection +from PyQt6.QtWidgets import ( QComboBox, QStyledItemDelegate, QStyle, @@ -15,7 +15,7 @@ QStyleOptionViewItem, QApplication, ) -from PyQt5.QtGui import QBrush +from PyQt6.QtGui import QBrush from hscommon.trans import trget from qt.tree_model import RefNode, TreeModel @@ -36,28 +36,28 @@ def paint(self, painter, option, index): self.initStyleOption(option, index) # No idea why, but this cast is required if we want to have access to the V4 valuess option = QStyleOptionViewItem(option) - if (index.column() == 1) and (option.state & QStyle.State_Selected): + if (index.column() == 1) and (option.state & QStyle.StateFlag.State_Selected): cboption = QStyleOptionComboBox() cboption.rect = option.rect # On OS X (with Qt4.6.0), adding State_Enabled to the flags causes the whole drawing to # fail (draw nothing), but it's an OS X only glitch. On Windows, it works alright. - cboption.state |= QStyle.State_Enabled - QApplication.style().drawComplexControl(QStyle.CC_ComboBox, cboption, painter) + cboption.state |= QStyle.StateFlag.State_Enabled + QApplication.style().drawComplexControl(QStyle.ComplexControl.CC_ComboBox, cboption, painter) painter.setBrush(option.palette.text()) rect = QRect(option.rect) rect.setLeft(rect.left() + 4) - painter.drawText(rect, Qt.AlignLeft, option.text) + painter.drawText(rect, Qt.AlignmentFlag.AlignLeft, option.text) else: super().paint(painter, option, index) def setEditorData(self, editor, index): - value = index.model().data(index, Qt.EditRole) + value = index.model().data(index, Qt.ItemDataRole.EditRole) editor.setCurrentIndex(value) editor.showPopup() def setModelData(self, editor, model, index): value = editor.currentIndex() - model.setData(index, value, Qt.EditRole) + model.setData(index, value, Qt.ItemDataRole.EditRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) @@ -89,19 +89,19 @@ def data(self, index, role): return None node = index.internalPointer() ref = node.ref - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: if index.column() == 0: return ref.name else: return STATES[ref.state] - elif role == Qt.EditRole and index.column() == 1: + elif role == Qt.ItemDataRole.EditRole and index.column() == 1: return ref.state - elif role == Qt.ForegroundRole: + elif role == Qt.ItemDataRole.ForegroundRole: state = ref.state if state == 1: - return QBrush(Qt.blue) + return QBrush(Qt.GlobalColor.blue) elif state == 2: - return QBrush(Qt.red) + return QBrush(Qt.GlobalColor.red) return None def dropMimeData(self, mime_data, action, row, column, parent_index): @@ -119,14 +119,14 @@ def dropMimeData(self, mime_data, action, row, column, parent_index): def flags(self, index): if not index.isValid(): - return Qt.ItemIsEnabled | Qt.ItemIsDropEnabled - result = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDropEnabled + return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsDropEnabled + result = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsDropEnabled if index.column() == 1: - result |= Qt.ItemIsEditable + result |= Qt.ItemFlag.ItemIsEditable return result def headerData(self, section, orientation, role): - if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADERS): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole and section < len(HEADERS): return HEADERS[section] return None @@ -134,7 +134,7 @@ def mimeTypes(self): return [self.MIME_TYPE_FORMAT] def setData(self, index, value, role): - if not index.isValid() or role != Qt.EditRole or index.column() != 1: + if not index.isValid() or role != Qt.ItemDataRole.EditRole or index.column() != 1: return False node = index.internalPointer() ref = node.ref @@ -144,7 +144,7 @@ def setData(self, index, value, role): def supportedDropActions(self): # Normally, the correct action should be ActionLink, but the drop doesn't work. It doesn't # work with ActionMove either. So screw that, and accept anything. - return Qt.ActionMask + return Qt.DropAction.ActionMask # --- Events def selectionChanged(self, selected, deselected): diff --git a/qt/error_report_dialog.py b/qt/error_report_dialog.py index 9efdcc77..fabd2e97 100644 --- a/qt/error_report_dialog.py +++ b/qt/error_report_dialog.py @@ -90,7 +90,7 @@ def _setupUi(self): main_layout.addLayout(button_layout) - report_button.clicked.connect(self.goToGithub) + report_button.clicked.connect(self.goToGitHub) close_button.clicked.connect(self.reject) def goToGitHub(self): diff --git a/qt/exclude_list_dialog.py b/qt/exclude_list_dialog.py index 43747b5d..0c0e218a 100644 --- a/qt/exclude_list_dialog.py +++ b/qt/exclude_list_dialog.py @@ -3,8 +3,8 @@ # http://www.gnu.org/licenses/gpl-3.0.html import re -from PyQt5.QtCore import Qt, pyqtSlot -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import Qt, pyqtSlot +from PyQt6.QtWidgets import ( QPushButton, QLineEdit, QVBoxLayout, @@ -26,7 +26,7 @@ class ExcludeListDialog(QDialog): def __init__(self, app, parent, model, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) self.app = app self.specific_actions = frozenset() @@ -60,29 +60,31 @@ def _setupUI(self): self.testLine = QLineEdit() self.tableView = QTableView() triggers = ( - QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked + QAbstractItemView.EditTrigger.DoubleClicked + | QAbstractItemView.EditTrigger.EditKeyPressed + | QAbstractItemView.EditTrigger.SelectedClicked ) self.tableView.setEditTriggers(triggers) - self.tableView.setSelectionMode(QTableView.ExtendedSelection) - self.tableView.setSelectionBehavior(QTableView.SelectRows) + self.tableView.setSelectionMode(QTableView.SelectionMode.ExtendedSelection) + self.tableView.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) self.tableView.setShowGrid(False) vheader = self.tableView.verticalHeader() vheader.setSectionsMovable(True) vheader.setVisible(False) hheader = self.tableView.horizontalHeader() hheader.setSectionsMovable(False) - hheader.setSectionResizeMode(QHeaderView.Fixed) + hheader.setSectionResizeMode(QHeaderView.ResizeMode.Fixed) hheader.setStretchLastSection(True) hheader.setHighlightSections(False) hheader.setVisible(True) gridlayout.addWidget(self.inputLine, 0, 0) - gridlayout.addWidget(self.buttonAdd, 0, 1, Qt.AlignLeft) - gridlayout.addWidget(self.buttonRemove, 1, 1, Qt.AlignLeft) - gridlayout.addWidget(self.buttonRestore, 2, 1, Qt.AlignLeft) - gridlayout.addWidget(self.buttonHelp, 3, 1, Qt.AlignLeft) + gridlayout.addWidget(self.buttonAdd, 0, 1, Qt.AlignmentFlag.AlignLeft) + gridlayout.addWidget(self.buttonRemove, 1, 1, Qt.AlignmentFlag.AlignLeft) + gridlayout.addWidget(self.buttonRestore, 2, 1, Qt.AlignmentFlag.AlignLeft) + gridlayout.addWidget(self.buttonHelp, 3, 1, Qt.AlignmentFlag.AlignLeft) gridlayout.addWidget(self.buttonClose, 4, 1) gridlayout.addWidget(self.tableView, 1, 0, 6, 1) - gridlayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 4, 1) + gridlayout.addItem(QSpacerItem(0, 0, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding), 4, 1) gridlayout.addWidget(self.buttonTestString, 6, 1) gridlayout.addWidget(self.testLine, 6, 0) diff --git a/qt/exclude_list_table.py b/qt/exclude_list_table.py index f07d8cf0..8efe353c 100644 --- a/qt/exclude_list_table.py +++ b/qt/exclude_list_table.py @@ -2,8 +2,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QFont, QFontMetrics, QIcon, QColor +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QFont, QFontMetrics, QIcon, QColor from qt.column import Column from qt.table import Table @@ -28,38 +28,43 @@ def __init__(self, app, view, **kwargs): def _getData(self, row, column, role): if column.name == "marked": - if role == Qt.CheckStateRole and row.markable: - return Qt.Checked if row.marked else Qt.Unchecked - if role == Qt.ToolTipRole and not row.markable: + if role == Qt.ItemDataRole.CheckStateRole and row.markable: + return Qt.CheckState.Checked if row.marked else Qt.CheckState.Unchecked + if role == Qt.ItemDataRole.ToolTipRole and not row.markable: return tr("Compilation error: ") + row.get_cell_value("error") - if role == Qt.DecorationRole and not row.markable: + if role == Qt.ItemDataRole.DecorationRole and not row.markable: return QIcon.fromTheme("dialog-error", QIcon(":/error")) return None - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return row.data[column.name] - elif role == Qt.FontRole: + elif role == Qt.ItemDataRole.FontRole: return QFont(self.view.font()) - elif role == Qt.BackgroundRole and column.name == "regex": + elif role == Qt.ItemDataRole.BackgroundRole and column.name == "regex": if row.highlight: return QColor(10, 200, 10) # green - elif role == Qt.EditRole and column.name == "regex": + elif role == Qt.ItemDataRole.EditRole and column.name == "regex": return row.data[column.name] return None def _getFlags(self, row, column): - flags = Qt.ItemIsEnabled + flags = Qt.ItemFlag.ItemIsEnabled if column.name == "marked": if row.markable: - flags |= Qt.ItemIsUserCheckable + flags |= Qt.ItemFlag.ItemIsUserCheckable elif column.name == "regex": - flags |= Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled + flags |= ( + Qt.ItemFlag.ItemIsEditable + | Qt.ItemFlag.ItemIsSelectable + | Qt.ItemFlag.ItemIsDragEnabled + | Qt.ItemFlag.ItemIsDropEnabled + ) return flags def _setData(self, row, column, value, role): - if role == Qt.CheckStateRole: + if role == Qt.ItemDataRole.CheckStateRole: if column.name == "marked": row.marked = bool(value) return True - elif role == Qt.EditRole and column.name == "regex": + elif role == Qt.ItemDataRole.EditRole and column.name == "regex": return self.model.rename_selected(value) return False diff --git a/qt/ignore_list_dialog.py b/qt/ignore_list_dialog.py index 1ccdcc47..3a65b3eb 100644 --- a/qt/ignore_list_dialog.py +++ b/qt/ignore_list_dialog.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QPushButton, @@ -24,7 +24,7 @@ class IgnoreListDialog(QDialog): def __init__(self, parent, model, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) self.specific_actions = frozenset() self._setupUi() @@ -42,9 +42,9 @@ def _setupUi(self): self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.tableView = QTableView() - self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableView.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + self.tableView.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.tableView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.tableView.setShowGrid(False) self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.verticalHeader().setDefaultSectionSize(18) diff --git a/qt/pe/details_dialog.py b/qt/pe/details_dialog.py index 9f6083b6..6bcebe15 100644 --- a/qt/pe/details_dialog.py +++ b/qt/pe/details_dialog.py @@ -4,9 +4,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot -from PyQt5.QtWidgets import QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame -from PyQt5.QtGui import QResizeEvent +from PyQt6.QtCore import Qt, QSize, pyqtSignal, pyqtSlot +from PyQt6.QtWidgets import QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame +from PyQt6.QtGui import QResizeEvent from hscommon.trans import trget from qt.details_dialog import DetailsDialog as DetailsDialogBase from qt.details_table import DetailsTable @@ -24,9 +24,9 @@ def _setupUi(self): self.setWindowTitle(tr("Details")) self.resize(502, 502) self.setMinimumSize(QSize(250, 250)) - self.splitter = QSplitter(Qt.Vertical) + self.splitter = QSplitter(Qt.Orientation.Vertical) self.topFrame = EmittingFrame() - self.topFrame.setFrameShape(QFrame.StyledPanel) + self.topFrame.setFrameShape(QFrame.Shape.StyledPanel) self.horizontalLayout = QGridLayout() # Minimum width for the toolbar in the middle: self.horizontalLayout.setColumnMinimumWidth(1, 10) @@ -47,8 +47,8 @@ def _setupUi(self): self.vController = ScrollAreaController(self) self.verticalToolBar = ViewerToolBar(self, self.vController) - self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical)) - self.horizontalLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter) + self.verticalToolBar.setOrientation(Qt.Orientation.Vertical) + self.horizontalLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignmentFlag.AlignCenter) self.referenceImageViewer = ScrollAreaImageViewer(self, "referenceImage") self.horizontalLayout.addWidget(self.referenceImageViewer, 0, 2, 3, 1) @@ -57,12 +57,12 @@ def _setupUi(self): self.splitter.setStretchFactor(0, 8) self.tableView = DetailsTable(self) - size_policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) + size_policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) self.tableView.setSizePolicy(size_policy) self.tableView.setAlternatingRowColors(True) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.tableView.setShowGrid(False) self.splitter.addWidget(self.tableView) self.splitter.setStretchFactor(1, 1) diff --git a/qt/pe/image_viewer.py b/qt/pe/image_viewer.py index 06d849af..cf4f3ad5 100644 --- a/qt/pe/image_viewer.py +++ b/qt/pe/image_viewer.py @@ -2,15 +2,14 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QObject, Qt, QSize, QRectF, QPointF, QPoint, pyqtSlot, pyqtSignal, QEvent -from PyQt5.QtGui import QPixmap, QPainter, QPalette, QCursor, QIcon, QKeySequence -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import QObject, Qt, QSize, QRectF, QPointF, QPoint, pyqtSlot, pyqtSignal, QEvent +from PyQt6.QtGui import QPixmap, QPainter, QPalette, QCursor, QIcon, QKeySequence, QAction +from PyQt6.QtWidgets import ( QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QToolBar, QToolButton, - QAction, QWidget, QScrollArea, QApplication, @@ -57,7 +56,7 @@ def setupActions(self, controller): ACTIONS = [ ( "actionZoomIn", - QKeySequence.ZoomIn, + QKeySequence.StandardKey.ZoomIn, ( QIcon.fromTheme("zoom-in") if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons @@ -68,7 +67,7 @@ def setupActions(self, controller): ), ( "actionZoomOut", - QKeySequence.ZoomOut, + QKeySequence.StandardKey.ZoomOut, ( QIcon.fromTheme("zoom-out") if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons @@ -106,9 +105,9 @@ def setupActions(self, controller): def createButtons(self): self.buttonImgSwap = QToolButton(self) - self.buttonImgSwap.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.buttonImgSwap.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.buttonImgSwap.setIcon( - QIcon.fromTheme("view-refresh", self.style().standardIcon(QStyle.SP_BrowserReload)) + QIcon.fromTheme("view-refresh", self.style().standardIcon(QStyle.StandardPixmap.SP_BrowserReload)) if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons else QIcon(QPixmap(":/" + "exchange")) ) @@ -118,22 +117,22 @@ def createButtons(self): self.buttonImgSwap.released.connect(self.controller.swapImages) self.buttonZoomIn = QToolButton(self) - self.buttonZoomIn.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.buttonZoomIn.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.buttonZoomIn.setDefaultAction(self.actionZoomIn) self.buttonZoomIn.setEnabled(False) self.buttonZoomOut = QToolButton(self) - self.buttonZoomOut.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.buttonZoomOut.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.buttonZoomOut.setDefaultAction(self.actionZoomOut) self.buttonZoomOut.setEnabled(False) self.buttonNormalSize = QToolButton(self) - self.buttonNormalSize.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.buttonNormalSize.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.buttonNormalSize.setDefaultAction(self.actionNormalSize) self.buttonNormalSize.setEnabled(True) self.buttonBestFit = QToolButton(self) - self.buttonBestFit.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.buttonBestFit.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.buttonBestFit.setDefaultAction(self.actionBestFit) self.buttonBestFit.setEnabled(False) @@ -228,10 +227,14 @@ def _updateImage(self, pixmap, viewer, same_group=False): return target_size # zoomed in state, expand # only if not same_group, we need full update - scaledpixmap = pixmap.scaled(target_size, Qt.KeepAspectRatioByExpanding, Qt.FastTransformation) + scaledpixmap = pixmap.scaled( + target_size, Qt.AspectRatioMode.KeepAspectRatioByExpanding, Qt.TransformationMode.FastTransformation + ) else: # best fit, keep ratio always - scaledpixmap = pixmap.scaled(target_size, Qt.KeepAspectRatio, Qt.FastTransformation) + scaledpixmap = pixmap.scaled( + target_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation + ) viewer.setImage(scaledpixmap) return target_size @@ -740,7 +743,7 @@ def resetCenter(self): self.update() def changeEvent(self, event): - if event.type() == QEvent.EnabledChange: + if event.type() == QEvent.Type.EnabledChange: if self.isEnabled(): self.connectMouseSignals() return @@ -754,7 +757,7 @@ def mousePressEvent(self, event): if self.bestFit or not self.isEnabled(): event.ignore() return - if event.button() & (Qt.LeftButton | Qt.MidButton | Qt.RightButton): + if event.button() & (Qt.MouseButton.LeftButton | Qt.MouseButton.MiddleButton | Qt.MouseButton.RightButton): self._drag = True else: self._drag = False @@ -762,7 +765,7 @@ def mousePressEvent(self, event): return self._lastMouseClickPoint = event.pos() - self._app.setOverrideCursor(Qt.ClosedHandCursor) + self._app.setOverrideCursor(Qt.CursorShape.ClosedHandCursor) self.setMouseTracking(True) event.accept() @@ -893,18 +896,18 @@ def __init__(self, parent, name=""): self.label = ScalablePixmap(self) # This is to avoid sending signals twice on scrollbar updates self.ignore_signal = False - self.setBackgroundRole(QPalette.Dark) + self.setBackgroundRole(QPalette.ColorRole.Dark) self.setWidgetResizable(False) - self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) - self.setAlignment(Qt.AlignCenter) + self.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents) + self.setAlignment(Qt.AlignmentFlag.AlignCenter) self._verticalScrollBar = self.verticalScrollBar() self._horizontalScrollBar = self.horizontalScrollBar() if self.prefs.details_dialog_viewers_show_scrollbars: self.toggleScrollBars() else: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setWidget(self.label) self.setVisible(True) @@ -916,14 +919,14 @@ def toggleScrollBars(self, force_on=False): if not self.prefs.details_dialog_viewers_show_scrollbars: return # Ensure that it's off on the first run - if self.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded: + if self.horizontalScrollBarPolicy() == Qt.ScrollBarPolicy.ScrollBarAsNeeded: if force_on: return - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) else: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) def connectMouseSignals(self): if not self._dragConnection: @@ -942,8 +945,12 @@ def disconnectMouseSignals(self): def connectScrollBars(self): """Only call once controller is connected.""" # Cyclic connections are handled by Qt - self._verticalScrollBar.valueChanged.connect(self.controller.onVScrollBarChanged, Qt.UniqueConnection) - self._horizontalScrollBar.valueChanged.connect(self.controller.onHScrollBarChanged, Qt.UniqueConnection) + self._verticalScrollBar.valueChanged.connect( + self.controller.onVScrollBarChanged, Qt.ConnectionType.UniqueConnection + ) + self._horizontalScrollBar.valueChanged.connect( + self.controller.onHScrollBarChanged, Qt.ConnectionType.UniqueConnection + ) def contextMenuEvent(self, event): """Block parent's (main window) context menu on right click.""" @@ -956,14 +963,14 @@ def mousePressEvent(self, event): if self.bestFit: event.ignore() return - if event.button() & (Qt.LeftButton | Qt.MidButton | Qt.RightButton): + if event.button() & (Qt.MouseButton.LeftButton | Qt.MouseButton.MiddleButton | Qt.MouseButton.RightButton): self._drag = True else: self._drag = False event.ignore() return self._lastMouseClickPoint = event.pos() - self._app.setOverrideCursor(Qt.ClosedHandCursor) + self._app.setOverrideCursor(Qt.CursorShape.ClosedHandCursor) self.setMouseTracking(True) super().mousePressEvent(event) @@ -1128,7 +1135,7 @@ def __init__(self, parent, name=""): self.other_viewer = None # specific to this class self._scene = QGraphicsScene() - self._scene.setBackgroundBrush(Qt.black) + self._scene.setBackgroundBrush(Qt.GlobalColor.black) self._item = QGraphicsPixmapItem() self.setScene(self._scene) self._scene.addItem(self._item) @@ -1140,12 +1147,12 @@ def __init__(self, parent, name=""): if self.prefs.details_dialog_viewers_show_scrollbars: self.toggleScrollBars() else: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) - self.setResizeAnchor(QGraphicsView.AnchorViewCenter) - self.setAlignment(Qt.AlignCenter) - self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) + self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter) + self.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.FullViewportUpdate) self.setMouseTracking(True) def connectMouseSignals(self): @@ -1165,21 +1172,25 @@ def disconnectMouseSignals(self): def connectScrollBars(self): """Only call once controller is connected.""" # Cyclic connections are handled by Qt - self._verticalScrollBar.valueChanged.connect(self.controller.onVScrollBarChanged, Qt.UniqueConnection) - self._horizontalScrollBar.valueChanged.connect(self.controller.onHScrollBarChanged, Qt.UniqueConnection) + self._verticalScrollBar.valueChanged.connect( + self.controller.onVScrollBarChanged, Qt.ConnectionType.UniqueConnection + ) + self._horizontalScrollBar.valueChanged.connect( + self.controller.onHScrollBarChanged, Qt.ConnectionType.UniqueConnection + ) def toggleScrollBars(self, force_on=False): if not self.prefs.details_dialog_viewers_show_scrollbars: return # Ensure that it's off on the first run - if self.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded: + if self.horizontalScrollBarPolicy() == Qt.ScrollBarPolicy.ScrollBarAsNeeded: if force_on: return - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) else: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) def contextMenuEvent(self, event): """Block parent's (main window) context menu on right click.""" @@ -1189,14 +1200,14 @@ def mousePressEvent(self, event): if self.bestFit: event.ignore() return - if event.button() & (Qt.LeftButton | Qt.MidButton | Qt.RightButton): + if event.button() & (Qt.MouseButton.LeftButton | Qt.MouseButton.MiddleButton | Qt.MouseButton.RightButton): self._drag = True else: self._drag = False event.ignore() return self._lastMouseClickPoint = event.pos() - self._app.setOverrideCursor(Qt.ClosedHandCursor) + self._app.setOverrideCursor(Qt.CursorShape.ClosedHandCursor) self.setMouseTracking(True) # We need to propagate to scrollbars, so we send back up super().mousePressEvent(event) @@ -1299,7 +1310,7 @@ def resetScale(self): def fitScale(self): self.bestFit = True - super().fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio) + super().fitInView(self._scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio) self.setNewCenter(self._scene.sceneRect().center()) @pyqtSlot() diff --git a/qt/pe/photo.py b/qt/pe/photo.py index e0d0b96e..bd52db7e 100644 --- a/qt/pe/photo.py +++ b/qt/pe/photo.py @@ -6,7 +6,7 @@ import logging -from PyQt5.QtGui import QImage, QImageReader, QTransform +from PyQt6.QtGui import QImage, QImageReader, QTransform from core.pe.photo import Photo as PhotoBase @@ -28,7 +28,7 @@ def _plat_get_dimensions(self): def _plat_get_blocks(self, block_count_per_side, orientation): image = QImage(str(self.path)) - image = image.convertToFormat(QImage.Format_RGB888) + image = image.convertToFormat(QImage.Format.Format_RGB888) if not isinstance(orientation, int): logging.warning( "Orientation for file '%s' was a %s '%s', not an int.", diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index 67d35613..d56ba237 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -5,11 +5,16 @@ # http://www.gnu.org/licenses/gpl-3.0.html +from typing import Callable + +from PyQt6.QtWidgets import QCheckBox + from hscommon.trans import trget from hscommon.plat import ISLINUX from core.scanner import ScanType from core.app import AppMode +from qt.preferences import Preferences from qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index 6f6e19e5..94443280 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -244,6 +244,10 @@ def _setupPreferenceWidgets(self): # Edition-specific pass + def _setup_advanced_page(self) -> None: + # Edition-specific + pass + def _setupUi(self) -> None: self.setWindowTitle(tr("Options")) self.setSizeGripEnabled(False) diff --git a/qt/prioritize_dialog.py b/qt/prioritize_dialog.py index d63778fb..180c6a55 100644 --- a/qt/prioritize_dialog.py +++ b/qt/prioritize_dialog.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QMimeData, QByteArray -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import Qt, QMimeData, QByteArray +from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, @@ -36,8 +36,8 @@ class PrioritizationList(ListviewModel): def flags(self, index): if not index.isValid(): - return Qt.ItemIsEnabled | Qt.ItemIsDropEnabled - return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled + return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsDropEnabled + return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsDragEnabled # --- Drag & Drop def dropMimeData(self, mime_data, action, row, column, parent_index): @@ -70,12 +70,12 @@ def mimeTypes(self): return [MIME_INDEXES] def supportedDropActions(self): - return Qt.MoveAction + return Qt.DropAction.MoveAction class PrioritizeDialog(QDialog): def __init__(self, parent, app, **kwargs): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint super().__init__(parent, flags, **kwargs) self._setupUi() self.model = PrioritizeDialogModel(app=app.model) @@ -107,24 +107,24 @@ def _setupUi(self): self.promptLabel.setWordWrap(True) self.categoryCombobox = QComboBox() self.criteriaListView = QListView() - self.criteriaListView.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.addCriteriaButton = QPushButton(self.style().standardIcon(QStyle.SP_ArrowRight), "") - self.removeCriteriaButton = QPushButton(self.style().standardIcon(QStyle.SP_ArrowLeft), "") + self.criteriaListView.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.addCriteriaButton = QPushButton(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowRight), "") + self.removeCriteriaButton = QPushButton(self.style().standardIcon(QStyle.StandardPixmap.SP_ArrowLeft), "") self.prioritizationListView = QListView() self.prioritizationListView.setAcceptDrops(True) self.prioritizationListView.setDragEnabled(True) - self.prioritizationListView.setDragDropMode(QAbstractItemView.InternalMove) - self.prioritizationListView.setSelectionBehavior(QAbstractItemView.SelectRows) - self.prioritizationListView.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.prioritizationListView.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) + self.prioritizationListView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.prioritizationListView.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.buttonBox = QDialogButtonBox() - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok) # layout self.mainLayout = QVBoxLayout(self) self.mainLayout.addWidget(self.promptLabel) self.splitter = QSplitter() sp = self.splitter.sizePolicy() - sp.setVerticalPolicy(QSizePolicy.Expanding) + sp.setVerticalPolicy(QSizePolicy.Policy.Expanding) self.splitter.setSizePolicy(sp) self.leftSide = QWidget() self.leftWidgetsLayout = QVBoxLayout() diff --git a/qt/progress_window.py b/qt/progress_window.py index ad67dd03..083c961e 100644 --- a/qt/progress_window.py +++ b/qt/progress_window.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtWidgets import QDialog, QMessageBox, QVBoxLayout, QLabel, QProgressBar, QPushButton +from PyQt6.QtCore import Qt, QTimer +from PyQt6.QtWidgets import QDialog, QMessageBox, QVBoxLayout, QLabel, QProgressBar, QPushButton from hscommon.trans import tr @@ -36,7 +36,7 @@ def set_progress(self, last_progress): self._progress_bar.setValue(last_progress) def show(self): - flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint + flags = Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint | Qt.WindowType.WindowSystemMenuHint self._window = QDialog(self.parent, flags) self._setup_ui() self._window.setModal(True) @@ -67,7 +67,7 @@ def cancel(self): self._window, ) confirm_dialog.setDefaultButton(QMessageBox.StandardButton.No) - result = confirm_dialog.exec_() + result = confirm_dialog.exec() if result != QMessageBox.StandardButton.Yes: return self.close() diff --git a/qt/radio_box.py b/qt/radio_box.py index f59ba7e9..7c69fdf5 100644 --- a/qt/radio_box.py +++ b/qt/radio_box.py @@ -5,8 +5,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QRadioButton +from PyQt6.QtCore import pyqtSignal +from PyQt6.QtWidgets import QWidget, QHBoxLayout, QRadioButton from qt.util import horizontal_spacer diff --git a/qt/recent.py b/qt/recent.py index 3fb5d136..91acc8f0 100644 --- a/qt/recent.py +++ b/qt/recent.py @@ -8,8 +8,8 @@ from collections import namedtuple -from PyQt5.QtCore import pyqtSignal, QObject -from PyQt5.QtWidgets import QAction +from PyQt6.QtCore import pyqtSignal, QObject +from PyQt6.QtGui import QAction from hscommon.trans import trget from hscommon.util import dedupe diff --git a/qt/result_window.py b/qt/result_window.py index 284391e3..d96806be 100644 --- a/qt/result_window.py +++ b/qt/result_window.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QRect -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import Qt, QRect +from PyQt6.QtWidgets import ( QMainWindow, QMenu, QLabel, @@ -20,7 +20,7 @@ QDialog, QPushButton, QCheckBox, - QDesktopWidget, + QApplication, ) from hscommon.trans import trget @@ -170,7 +170,7 @@ def _setupActions(self): ), ( "actionMarkSelected", - Qt.Key_Space, + Qt.Key.Key_Space, "", tr("Mark Selected"), self.markSelectedTriggered, @@ -350,8 +350,8 @@ def _setupUi(self): self.horizontalLayout.setSpacing(8) self.verticalLayout.addLayout(self.horizontalLayout) self.resultsView = ResultsView(self.centralwidget) - self.resultsView.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.resultsView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.resultsView.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.resultsView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.resultsView.setSortingEnabled(True) self.resultsView.setWordWrap(False) self.resultsView.verticalHeader().setVisible(False) @@ -359,7 +359,7 @@ def _setupUi(self): h.setHighlightSections(False) h.setSectionsMovable(True) h.setStretchLastSection(False) - h.setDefaultAlignment(Qt.AlignLeft) + h.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft) self.verticalLayout.addWidget(self.resultsView) self.setCentralWidget(self.centralwidget) self._setupActions() @@ -371,17 +371,18 @@ def _setupUi(self): self.statusbar.addPermanentWidget(self.statusLabel, 1) if self.app.prefs.resultWindowIsMaximized: - self.setWindowState(self.windowState() | Qt.WindowMaximized) + self.setWindowState(self.windowState() | Qt.WindowState.WindowMaximized) else: if self.app.prefs.resultWindowRect is not None: self.setGeometry(self.app.prefs.resultWindowRect) # if not on any screen move to center of default screen # moves to center of closest screen if partially off screen frame = self.frameGeometry() - if QDesktopWidget().screenNumber(self) == -1: + screen = QApplication.screenAt(self.geometry().center()) + if screen is None: move_to_screen_center(self) - elif QDesktopWidget().availableGeometry(self).contains(frame) is False: - frame.moveCenter(QDesktopWidget().availableGeometry(self).center()) + elif screen.availableGeometry().contains(frame) is False: + frame.moveCenter(screen.availableGeometry().center()) self.move(frame.topLeft()) else: move_to_screen_center(self) @@ -458,7 +459,7 @@ def renameTriggered(self): def reprioritizeTriggered(self): dlg = PrioritizeDialog(self, self.app) result = dlg.exec() - if result == QDialog.Accepted: + if result == QDialog.DialogCode.Accepted: dlg.model.perform_reprioritization() def revealTriggered(self): @@ -490,7 +491,7 @@ def columnToggled(self, action): action.setChecked(visible) def contextMenuEvent(self, event): - self.actionActions.menu().exec_(event.globalPos()) + self.actionActions.menu().exec(event.globalPos()) def resultsDoubleClicked(self, model_index): self.app.model.open_selected() diff --git a/qt/results_model.py b/qt/results_model.py index 54fb9c1f..5219761d 100644 --- a/qt/results_model.py +++ b/qt/results_model.py @@ -6,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, pyqtSignal, QModelIndex -from PyQt5.QtGui import QBrush, QFont, QFontMetrics -from PyQt5.QtWidgets import QTableView +from PyQt6.QtCore import Qt, pyqtSignal, QModelIndex +from PyQt6.QtGui import QBrush, QFont, QFontMetrics +from PyQt6.QtWidgets import QTableView from qt.table import Table @@ -17,7 +17,7 @@ class ResultsModel(Table): def __init__(self, app, view, **kwargs): model = app.model.result_table super().__init__(model, view, **kwargs) - view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder) + view.horizontalHeader().setSortIndicator(1, Qt.SortOrder.AscendingOrder) font = view.font() font.setPointSize(app.prefs.tableFontSize) view.setFont(font) @@ -29,52 +29,52 @@ def __init__(self, app, view, **kwargs): def _getData(self, row, column, role): if column.name == "marked": - if role == Qt.BackgroundRole and row.isref: + if role == Qt.ItemDataRole.BackgroundRole and row.isref: return QBrush(self.prefs.result_table_ref_background_color) - if role == Qt.CheckStateRole and row.markable: - return Qt.Checked if row.marked else Qt.Unchecked + if role == Qt.ItemDataRole.CheckStateRole and row.markable: + return Qt.CheckState.Checked if row.marked else Qt.CheckState.Unchecked return None - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: data = row.data_delta if self.model.delta_values else row.data return data[column.name] - elif role == Qt.ForegroundRole: + elif role == Qt.ItemDataRole.ForegroundRole: if row.isref: return QBrush(self.prefs.result_table_ref_foreground_color) elif row.is_cell_delta(column.name): return QBrush(self.prefs.result_table_delta_foreground_color) - elif role == Qt.BackgroundRole: + elif role == Qt.ItemDataRole.BackgroundRole: if row.isref: return QBrush(self.prefs.result_table_ref_background_color) - elif role == Qt.FontRole: + elif role == Qt.ItemDataRole.FontRole: font = QFont(self.view.font()) if self.prefs.reference_bold_font: font.setBold(row.isref) return font - elif role == Qt.EditRole and column.name == "name": + elif role == Qt.ItemDataRole.EditRole and column.name == "name": return row.data[column.name] return None def _getFlags(self, row, column): - flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable + flags = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable if column.name == "marked": if row.markable: - flags |= Qt.ItemIsUserCheckable + flags |= Qt.ItemFlag.ItemIsUserCheckable elif column.name == "name": - flags |= Qt.ItemIsEditable + flags |= Qt.ItemFlag.ItemIsEditable return flags def _setData(self, row, column, value, role): - if role == Qt.CheckStateRole: + if role == Qt.ItemDataRole.CheckStateRole: if column.name == "marked": row.marked = bool(value) return True - elif role == Qt.EditRole and column.name == "name": + elif role == Qt.ItemDataRole.EditRole and column.name == "name": return self.model.rename_selected(value) return False def sort(self, column, order): column = self.model.COLUMNS[column] - self.model.sort(column.name, order == Qt.AscendingOrder) + self.model.sort(column.name, order == Qt.SortOrder.AscendingOrder) # --- Properties @property diff --git a/qt/se/details_dialog.py b/qt/se/details_dialog.py index ea59c7c2..fd8f8877 100644 --- a/qt/se/details_dialog.py +++ b/qt/se/details_dialog.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import QAbstractItemView +from PyQt6.QtCore import QSize +from PyQt6.QtWidgets import QAbstractItemView from hscommon.trans import trget from qt.details_dialog import DetailsDialog as DetailsDialogBase @@ -21,6 +21,6 @@ def _setupUi(self): self.setMinimumSize(QSize(200, 0)) self.tableView = DetailsTable(self) self.tableView.setAlternatingRowColors(True) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.tableView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.tableView.setShowGrid(False) self.setWidget(self.tableView) diff --git a/qt/search_edit.py b/qt/search_edit.py index 79c61083..b235e79f 100644 --- a/qt/search_edit.py +++ b/qt/search_edit.py @@ -6,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QIcon, QPixmap, QPainter, QPalette -from PyQt5.QtWidgets import QToolButton, QLineEdit, QStyle, QStyleOptionFrame +from PyQt6.QtCore import pyqtSignal, Qt +from PyQt6.QtGui import QIcon, QPixmap, QPainter, QPalette +from PyQt6.QtWidgets import QToolButton, QLineEdit, QStyle, QStyleOptionFrame from hscommon.trans import trget @@ -24,8 +24,8 @@ def __init__(self, parent, **kwargs): pixmap = QPixmap(":/search_clear_13") self.setIcon(QIcon(pixmap)) self.setIconSize(pixmap.size()) - self.setCursor(Qt.ArrowCursor) - self.setPopupMode(QToolButton.InstantPopup) + self.setCursor(Qt.CursorShape.ArrowCursor) + self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) stylesheet = "QToolButton { border: none; padding: 0px; }" self.setStyleSheet(stylesheet) @@ -36,7 +36,7 @@ def __init__(self, parent=None, is_clearable=True, **kwargs): self._is_clearable = is_clearable if is_clearable: self._clearButton = LineEditButton(self) - frame_width = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) + frame_width = self.style().pixelMetric(QStyle.PixelMetric.PM_DefaultFrameWidth) padding_right = self._clearButton.sizeHint().width() + frame_width + 1 stylesheet = f"QLineEdit {{ padding-right:{padding_right}px; }}" self.setStyleSheet(stylesheet) @@ -58,7 +58,7 @@ def _hasClearableContent(self): # --- QLineEdit overrides def resizeEvent(self, event): if self._is_clearable: - frame_width = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) + frame_width = self.style().pixelMetric(QStyle.PixelMetric.PM_DefaultFrameWidth) rect = self.rect() right_hint = self._clearButton.sizeHint() right_x = rect.right() - frame_width - right_hint.width() @@ -92,7 +92,7 @@ def _textChanged(self, text): def keyPressEvent(self, event): key = event.key() - if key == Qt.Key_Escape: + if key == Qt.Key.Key_Escape: self._clearSearch() else: ClearableEdit.keyPressEvent(self, event) @@ -102,14 +102,14 @@ def paintEvent(self, event): if not bool(self.text()) and self.inactiveText and not self.hasFocus(): panel = QStyleOptionFrame() self.initStyleOption(panel) - text_rect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self) + text_rect = self.style().subElementRect(QStyle.SubElement.SE_LineEditContents, panel, self) left_margin = 2 right_margin = self._clearButton.iconSize().width() text_rect.adjust(left_margin, 0, -right_margin, 0) painter = QPainter(self) - disabled_color = self.palette().brush(QPalette.Disabled, QPalette.Text).color() + disabled_color = self.palette().brush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text).color() painter.setPen(disabled_color) - painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, self.inactiveText) + painter.drawText(text_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, self.inactiveText) # --- Event Handlers def _returnPressed(self): diff --git a/qt/selectable_list.py b/qt/selectable_list.py index f1bbc817..e97a879c 100644 --- a/qt/selectable_list.py +++ b/qt/selectable_list.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import Qt, QAbstractListModel, QItemSelection, QItemSelectionModel +from PyQt6.QtCore import Qt, QAbstractListModel, QItemSelection, QItemSelectionModel class SelectableList(QAbstractListModel): @@ -23,7 +23,7 @@ def data(self, index, role): if not index.isValid(): return None # We need EditRole for QComboBoxes with setEditable(True) - if role in {Qt.DisplayRole, Qt.EditRole}: + if role in {Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole}: return self.model[index.row()] return None @@ -88,10 +88,10 @@ def _restoreSelection(self): new_selection = QItemSelection() for index in self.model.selected_indexes: new_selection.select(self.createIndex(index, 0), self.createIndex(index, 0)) - self.view.selectionModel().select(new_selection, QItemSelectionModel.ClearAndSelect) + self.view.selectionModel().select(new_selection, QItemSelectionModel.SelectionFlag.ClearAndSelect) if len(new_selection.indexes()): current_index = new_selection.indexes()[0] - self.view.selectionModel().setCurrentIndex(current_index, QItemSelectionModel.Current) + self.view.selectionModel().setCurrentIndex(current_index, QItemSelectionModel.SelectionFlag.Current) self.view.scrollTo(current_index) # --- Events diff --git a/qt/tabbed_window.py b/qt/tabbed_window.py index 58ac996d..f751f9d3 100644 --- a/qt/tabbed_window.py +++ b/qt/tabbed_window.py @@ -2,8 +2,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from PyQt5.QtCore import QRect, pyqtSlot, Qt, QEvent -from PyQt5.QtWidgets import ( +from PyQt6.QtCore import QRect, pyqtSlot, Qt, QEvent +from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, @@ -175,7 +175,7 @@ def addTab(self, page, title, switch=False): # Warning: this supposedly takes ownership of the page index = self.tabWidget.addTab(page, title) if isinstance(page, DirectoriesDialog): - self.tabWidget.tabBar().setTabButton(index, QTabBar.RightSide, None) + self.tabWidget.tabBar().setTabButton(index, QTabBar.ButtonPosition.RightSide, None) if switch: self.setCurrentIndex(index) return index @@ -221,7 +221,7 @@ def showEvent(self, event): super().showEvent(event) def changeEvent(self, event): - if event.type() == QEvent.WindowStateChange and not self.isMaximized(): + if event.type() == QEvent.Type.WindowStateChange and not self.isMaximized(): move_to_screen_center(self) super().changeEvent(event) @@ -278,8 +278,8 @@ def _setupUi(self): self.stackedWidget = QStackedWidget() self.centralWidget.setLayout(self.verticalLayout) self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.addWidget(self.menubar, 0, Qt.AlignTop) - self.horizontalLayout.addWidget(self.tabBar, 0, Qt.AlignTop) + self.horizontalLayout.addWidget(self.menubar, 0, Qt.AlignmentFlag.AlignTop) + self.horizontalLayout.addWidget(self.tabBar, 0, Qt.AlignmentFlag.AlignTop) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout.addWidget(self.stackedWidget) @@ -297,7 +297,7 @@ def addTab(self, page, title, switch=True): self.tabBar.insertTab(stack_index, title) if isinstance(page, DirectoriesDialog): - self.tabBar.setTabButton(stack_index, QTabBar.RightSide, None) + self.tabBar.setTabButton(stack_index, QTabBar.ButtonPosition.RightSide, None) if switch: # switch to the added tab immediately upon creation self.setTabIndex(stack_index) return stack_index diff --git a/qt/table.py b/qt/table.py index 17aeb1a3..612279ac 100644 --- a/qt/table.py +++ b/qt/table.py @@ -7,7 +7,7 @@ # http://www.gnu.org/licenses/gpl-3.0.html import typing -from PyQt5.QtCore import ( +from PyQt6.QtCore import ( Qt, QAbstractTableModel, QModelIndex, @@ -49,32 +49,32 @@ def _updateViewSelection(self): column_count = self.columnCount(QModelIndex()) for index in self.model.selected_indexes: new_selection.select(self.createIndex(index, 0), self.createIndex(index, column_count - 1)) - self.view.selectionModel().select(new_selection, QItemSelectionModel.ClearAndSelect) + self.view.selectionModel().select(new_selection, QItemSelectionModel.SelectionFlag.ClearAndSelect) if len(new_selection.indexes()): current_index = new_selection.indexes()[0] - self.view.selectionModel().setCurrentIndex(current_index, QItemSelectionModel.Current) + self.view.selectionModel().setCurrentIndex(current_index, QItemSelectionModel.SelectionFlag.Current) self.view.scrollTo(current_index) # --- Data Model methods # Virtual def _getData(self, row, column, role): - if role in (Qt.DisplayRole, Qt.EditRole): + if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole): attrname = column.name return row.get_cell_value(attrname) - elif role == Qt.TextAlignmentRole: + elif role == Qt.ItemDataRole.TextAlignmentRole: return column.alignment return None # Virtual def _getFlags(self, row, column): - flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable + flags = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable if row.can_edit_cell(column.name): - flags |= Qt.ItemIsEditable + flags |= Qt.ItemFlag.ItemIsEditable return flags # Virtual def _setData(self, row, column, value, role): - if role == Qt.EditRole: + if role == Qt.ItemDataRole.EditRole: attrname = column.name if attrname == "from": attrname = "from_" @@ -100,14 +100,14 @@ def flags(self, index): return self._getFlags(row, column) def headerData(self, section, orientation, role): - if orientation != Qt.Horizontal: + if orientation != Qt.Orientation.Horizontal: return None if section >= self.model._columns.columns_count(): return None column = self.model._columns.column_by_index(section) - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return column.display - elif role == Qt.TextAlignmentRole: + elif role == Qt.ItemDataRole.TextAlignmentRole: return column.alignment else: return None @@ -130,7 +130,7 @@ def setData(self, index, value, role): def sort(self, section, order): column = self.model._columns.column_by_index(section) attrname = column.name - self.model.sort_by(attrname, desc=order == Qt.DescendingOrder) + self.model.sort_by(attrname, desc=order == Qt.SortOrder.DescendingOrder) def submit(self): self.model.save_edits() diff --git a/qt/tree_model.py b/qt/tree_model.py index 07f66d0a..42fad8bd 100644 --- a/qt/tree_model.py +++ b/qt/tree_model.py @@ -8,7 +8,7 @@ import logging -from PyQt5.QtCore import QAbstractItemModel, QModelIndex +from PyQt6.QtCore import QAbstractItemModel, QModelIndex class NodeContainer: diff --git a/requirements.txt b/requirements.txt index 9c8b7c01..ad1a01cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ distro>=1.8.0,<2.0.0 mutagen>=1.46.0,<2.0.0 polib>=1.1.0,<2.0.0 -PyQt6 >=6.3,<7.0; sys_platform != 'linux' +PyQt6 >=6.3,<7.0 pywin32>=304; sys_platform == 'win32' semantic-version>=2.0.0,<3.0.0 Send2Trash>=1.8.2,<2.0.0 From a47665d6d26c88dce8e19813d906f46de971adab Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Sun, 2 Aug 2026 13:24:01 -0600 Subject: [PATCH 08/26] Patches from in the comments for 1392 pull request --- Makefile | 1 + hscommon/build.py | 3 +++ qt/details_dialog.py | 17 +++++++++++++---- qt/error_report_dialog.py | 3 --- qt/pe/image_viewer.py | 2 +- qt/preferences_dialog.py | 20 ++++++++++++++++++-- 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 4331b78a..04e09407 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,7 @@ build/help: | env qt/dg_rc.py: qt/dg.qrc $(RCC) -g python qt/dg.qrc -o qt/dg_rc.py + sed -i 's/from PySide2/from PyQt6/' qt/dg_rc.py sed -i 's/from PySide6/from PyQt6/' qt/dg_rc.py i18n: $(mofiles) diff --git a/hscommon/build.py b/hscommon/build.py index 625a3d23..22640f74 100644 --- a/hscommon/build.py +++ b/hscommon/build.py @@ -315,6 +315,9 @@ def fix_qt_resource_file(path: os.PathLike) -> None: # comments, and save. with open(path, "rb") as fp: contents = fp.read() + # Depending on the Qt toolchain version, rcc can generate imports for + # either PySide2 or PySide6. dupeGuru uses PyQt6 at runtime. + contents = contents.replace(b"PySide2", b"PyQt6") contents = contents.replace(b"PySide6", b"PyQt6") lines = contents.split(b"\n") lines = [line for line in lines if not line.startswith(b"#")] diff --git a/qt/details_dialog.py b/qt/details_dialog.py index 6b838116..f5773b72 100644 --- a/qt/details_dialog.py +++ b/qt/details_dialog.py @@ -32,7 +32,15 @@ def __init__(self, parent, app, **kwargs): self.model.view = self self.app.willSavePrefs.connect(self.appWillSavePrefs) # self.setAttribute(Qt.WA_DeleteOnClose) - parent.addDockWidget(area if self._wasDocked else Qt.DockWidgetArea.BottomDockWidgetArea, self) + dock_area = Qt.DockWidgetArea.BottomDockWidgetArea + if self._wasDocked: + # PyQt6 is strict here: addDockWidget() requires Qt.DockWidgetArea, + # while persisted prefs can deserialize as int/str. + try: + dock_area = Qt.DockWidgetArea(int(area)) + except (TypeError, ValueError): + dock_area = Qt.DockWidgetArea.BottomDockWidgetArea + parent.addDockWidget(dock_area, self) def _setupUi(self): # Virtual pass @@ -60,10 +68,11 @@ def update_options(self): self.setTitleBarWidget(QWidget()) features = self.features() + vertical_titlebar_feature = QDockWidget.DockWidgetFeature.DockWidgetVerticalTitleBar if self.app.prefs.details_dialog_vertical_titlebar: - self.setFeatures(features | QDockWidget.DockWidgetVerticalTitleBar) - elif features & QDockWidget.DockWidgetVerticalTitleBar: - self.setFeatures(features ^ QDockWidget.DockWidgetVerticalTitleBar) + self.setFeatures(features | vertical_titlebar_feature) + elif features & vertical_titlebar_feature: + self.setFeatures(features ^ vertical_titlebar_feature) # --- Events def appWillSavePrefs(self): diff --git a/qt/error_report_dialog.py b/qt/error_report_dialog.py index fabd2e97..c1b3e9cd 100644 --- a/qt/error_report_dialog.py +++ b/qt/error_report_dialog.py @@ -43,9 +43,6 @@ def __init__(self, parent, github_url, error, **kwargs): self.error_text_edit.setPlainText(error_text) self.github_url = github_url - self.sendButton.clicked.connect(self.goToGitHub) - self.dontSendButton.clicked.connect(self.reject) - def _setupUi(self): self.setWindowTitle(tr("Error Report")) self.resize(553, 349) diff --git a/qt/pe/image_viewer.py b/qt/pe/image_viewer.py index cf4f3ad5..66de2521 100644 --- a/qt/pe/image_viewer.py +++ b/qt/pe/image_viewer.py @@ -1007,7 +1007,7 @@ def wheelEvent(self, event): if old_scale == self.current_scale: return - delta_to_pos = (event.position() / old_scale) - (self.label.pos() / old_scale) + delta_to_pos = (event.position() / old_scale) - (QPointF(self.label.pos()) / old_scale) delta = (delta_to_pos * self.current_scale) - (delta_to_pos * old_scale) self.mouseWheeled.emit(self.current_scale, delta) diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index 94443280..478bf084 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -245,8 +245,24 @@ def _setupPreferenceWidgets(self): pass def _setup_advanced_page(self) -> None: - # Edition-specific - pass + tab_label = QLabel( + tr( + "These options are for advanced users or for very specific situations, \ +most users should not have to modify these." + ), + wordWrap=True, + ) + self.advanced_vlayout.addWidget(tab_label) + self._setupAddCheckbox( + "rehash_ignore_mtime_box", + tr("Ignore difference in mtime when loading cached digests"), + ) + self.advanced_vlayout.addWidget(self.rehash_ignore_mtime_box) + self._setupAddCheckbox( + "include_exists_check_box", + tr("Include existence check after scan completion"), + ) + self.advanced_vlayout.addWidget(self.include_exists_check_box) def _setupUi(self) -> None: self.setWindowTitle(tr("Options")) From a7a5a176ebfcd6d9b489c03f48bd6f85777208d7 Mon Sep 17 00:00:00 2001 From: Nathaniel Morck Beaver Date: Tue, 5 May 2026 17:21:31 -0400 Subject: [PATCH 09/26] Add StartupWMClass to desktop files to ensure DupeGuru icon is shown in launcher. Background: https://discussion.fedoraproject.org/t/how-to-ensure-a-launcher-icon-gets-linked-to-the-application-it-launches/67688 https://specifications.freedesktop.org/desktop-entry/latest/recognized-keys.html#StartupWMClass --- pkg/arch/dupeguru.desktop | 1 + pkg/debian/dupeguru.desktop | 1 + pkg/dupeguru.desktop | 1 + 3 files changed, 3 insertions(+) diff --git a/pkg/arch/dupeguru.desktop b/pkg/arch/dupeguru.desktop index 50f5f607..210b3ba8 100644 --- a/pkg/arch/dupeguru.desktop +++ b/pkg/arch/dupeguru.desktop @@ -6,3 +6,4 @@ Icon={iconpath} Terminal=false Type=Application Categories=Utility; +StartupWMClass=dupeguru diff --git a/pkg/debian/dupeguru.desktop b/pkg/debian/dupeguru.desktop index 50f5f607..210b3ba8 100644 --- a/pkg/debian/dupeguru.desktop +++ b/pkg/debian/dupeguru.desktop @@ -6,3 +6,4 @@ Icon={iconpath} Terminal=false Type=Application Categories=Utility; +StartupWMClass=dupeguru diff --git a/pkg/dupeguru.desktop b/pkg/dupeguru.desktop index 91e8bf6e..a627644b 100644 --- a/pkg/dupeguru.desktop +++ b/pkg/dupeguru.desktop @@ -7,3 +7,4 @@ Terminal=false Type=Application Categories=Utility; Keywords=file manager;gui; +StartupWMClass=dupeguru From eea76f484bad616097bef4e2087121b08cdaf017 Mon Sep 17 00:00:00 2001 From: romain Date: Sun, 15 Feb 2026 12:28:36 +0100 Subject: [PATCH 10/26] perf: 16x faster picture scanning via pre-scaling and parallel preparation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pre-scale images to 450x450 before block extraction (qt/pe/photo.py) instead of processing full-resolution images (e.g., 4000x3000) - Parallelize image preparation using ThreadPoolExecutor across all CPU cores instead of sequential single-threaded processing - Enable SQLite WAL mode for better concurrent cache access Benchmarked: ~2 img/s → 33 img/s on real photos (16x speedup). Co-Authored-By: Claude Opus 4.6 --- core/pe/cache_sqlite.py | 1 + core/pe/matchblock.py | 103 ++++++++++++++++++++++++---------------- qt/pe/photo.py | 4 ++ 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/core/pe/cache_sqlite.py b/core/pe/cache_sqlite.py index f7020e46..9cbe6072 100644 --- a/core/pe/cache_sqlite.py +++ b/core/pe/cache_sqlite.py @@ -102,6 +102,7 @@ def __setitem__(self, path_str, blocks): def _create_con(self, second_try=False): try: self.con = sqlite.connect(self.dbname, isolation_level=None) + self.con.execute("PRAGMA journal_mode=WAL") self._check_upgrade() except sqlite.DatabaseError as e: # corrupted db if second_try: diff --git a/core/pe/matchblock.py b/core/pe/matchblock.py index 0aa0d253..f8a747ad 100644 --- a/core/pe/matchblock.py +++ b/core/pe/matchblock.py @@ -8,6 +8,8 @@ import logging import multiprocessing +import os +from concurrent.futures import ThreadPoolExecutor, as_completed from itertools import combinations from hscommon.util import extract, iterconsume @@ -54,50 +56,71 @@ def get_cache(cache_path, readonly=False): return SqliteCache(cache_path, readonly=readonly) +def _extract_blocks(picture, with_dimensions, match_rotated): + """Extract blocks for one picture. Returns (picture, blocks) or (picture, None) on error.""" + if not picture.path: + logging.warning("We have a picture with a null path here") + return (picture, None) + logging.debug("Analyzing picture at %s", picture.unicode_path) + if with_dimensions: + picture.dimensions # pre-read dimensions + try: + if match_rotated: + blocks = [picture.get_blocks(BLOCK_COUNT_PER_SIDE, orientation) for orientation in range(1, 9)] + else: + blocks = [[]] * 8 + blocks[max(picture.get_orientation() - 1, 0)] = picture.get_blocks(BLOCK_COUNT_PER_SIDE) + return (picture, blocks) + except (OSError, ValueError) as e: + logging.warning(str(e)) + return (picture, None) + except MemoryError: + logging.warning( + "Ran out of memory while reading %s of size %d", + picture.unicode_path, + picture.size, + ) + if picture.size < 10 * 1024 * 1024: + raise + return (picture, None) + + def prepare_pictures(pictures, cache_path, with_dimensions, match_rotated, j=job.nulljob): - # The MemoryError handlers in there use logging without first caring about whether or not - # there is enough memory left to carry on the operation because it is assumed that the - # MemoryError happens when trying to read an image file, which is freed from memory by the - # time that MemoryError is raised. cache = get_cache(cache_path) cache.purge_outdated() - prepared = [] # only pictures for which there was no error getting blocks - try: - for picture in j.iter_with_progress(pictures, tr("Analyzed %d/%d pictures")): - if not picture.path: - # XXX Find the root cause of this. I've received reports of crashes where we had - # "Analyzing picture at " (without a path) in the debug log. It was an iPhoto scan. - # For now, I'm simply working around the crash by ignoring those, but it would be - # interesting to know exactly why this happens. I'm suspecting a malformed - # entry in iPhoto library. - logging.warning("We have a picture with a null path here") - continue - logging.debug("Analyzing picture at %s", picture.unicode_path) - if with_dimensions: - picture.dimensions # pre-read dimensions - try: - if picture.unicode_path not in cache or ( - match_rotated and any(block == [] for block in cache[picture.unicode_path]) - ): - if match_rotated: - blocks = [picture.get_blocks(BLOCK_COUNT_PER_SIDE, orientation) for orientation in range(1, 9)] - else: - blocks = [[]] * 8 - blocks[max(picture.get_orientation() - 1, 0)] = picture.get_blocks(BLOCK_COUNT_PER_SIDE) - cache[picture.unicode_path] = blocks + prepared = [] + needs_extraction = [] + for picture in pictures: + if not picture.path: + continue + try: + if picture.unicode_path not in cache or ( + match_rotated and any(block == [] for block in cache[picture.unicode_path]) + ): + needs_extraction.append(picture) + else: prepared.append(picture) - except (OSError, ValueError) as e: - logging.warning(str(e)) - except MemoryError: - logging.warning( - "Ran out of memory while reading %s of size %d", - picture.unicode_path, - picture.size, - ) - if picture.size < 10 * 1024 * 1024: # We're really running out of memory - raise - except MemoryError: - logging.warning("Ran out of memory while preparing pictures") + except (OSError, KeyError): + needs_extraction.append(picture) + if needs_extraction: + try: + with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor: + futures = { + executor.submit(_extract_blocks, pic, with_dimensions, match_rotated): pic + for pic in needs_extraction + } + total = len(futures) + for i, future in enumerate(as_completed(futures)): + j.set_progress( + (i + 1) * 100 // total, + tr("Analyzed %d/%d pictures") % (i + 1, total), + ) + picture, blocks = future.result() + if blocks is not None: + cache[picture.unicode_path] = blocks + prepared.append(picture) + except MemoryError: + logging.warning("Ran out of memory while preparing pictures") cache.close() return prepared diff --git a/qt/pe/photo.py b/qt/pe/photo.py index bd52db7e..297586ff 100644 --- a/qt/pe/photo.py +++ b/qt/pe/photo.py @@ -6,6 +6,7 @@ import logging +from PyQt6.QtCore import Qt from PyQt6.QtGui import QImage, QImageReader, QTransform from core.pe.photo import Photo as PhotoBase @@ -69,4 +70,7 @@ def _plat_get_blocks(self, block_count_per_side, orientation): elif orientation == 8: t.rotate(270) image = image.transformed(t) + target_size = block_count_per_side * 30 + if image.width() > target_size or image.height() > target_size: + image = image.scaled(target_size, target_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) return getblocks(image, block_count_per_side) From d0ac873df46c4f879de47a81af96c8bac5b1f611 Mon Sep 17 00:00:00 2001 From: romain Date: Sun, 15 Feb 2026 19:05:02 +0100 Subject: [PATCH 11/26] feat: add scan speed preference for picture mode (Accurate/Balanced/Turbo) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a user-facing "Scan speed" dropdown in the Picture preferences dialog that controls how aggressively images are pre-scaled before block extraction: - Accurate (slow): no pre-scaling, processes full-resolution images - Balanced (recommended): pre-scale to 450px — good accuracy/speed trade-off - Turbo: pre-scale to 150px — fastest, suitable for large collections Each mode uses a separate cache file so switching modes doesn't require re-scanning. The preference is persisted across sessions. Also improves Filter Hardness UI by disabling all related labels (not just the slider) when the control is inactive. Co-Authored-By: Claude Opus 4.6 --- core/app.py | 5 ++++- core/pe/matchblock.py | 13 ++++++++----- core/pe/scanner.py | 2 ++ qt/app.py | 1 + qt/me/preferences_dialog.py | 2 +- qt/pe/photo.py | 8 +++++--- qt/pe/preferences_dialog.py | 13 ++++++++++++- qt/preferences.py | 3 +++ qt/preferences_dialog.py | 15 +++++++++++---- qt/se/preferences_dialog.py | 2 +- 10 files changed, 48 insertions(+), 16 deletions(-) diff --git a/core/app.py b/core/app.py index 35f55c86..97682550 100644 --- a/core/app.py +++ b/core/app.py @@ -182,7 +182,10 @@ def _recreate_result_table(self): self.view.create_results_window() def _get_picture_cache_path(self): - cache_name = "cached_pictures.db" + prescale = self.options.get("picture_prescale", 1) + # Balanced (1) = no suffix for backward compat with existing caches + suffixes = ["_accurate", "", "_turbo"] + cache_name = f"cached_pictures{suffixes[prescale]}.db" return op.join(self.appdata, cache_name) def _get_dupe_sort_key(self, dupe, get_group, key, delta): diff --git a/core/pe/matchblock.py b/core/pe/matchblock.py index f8a747ad..0ed09a74 100644 --- a/core/pe/matchblock.py +++ b/core/pe/matchblock.py @@ -39,6 +39,7 @@ BLOCK_COUNT_PER_SIDE = 15 DEFAULT_CHUNK_SIZE = 1000 MIN_CHUNK_SIZE = 100 +PRESCALE_MULTIPLIERS = [0, 30, 10] # 0 = no scaling, 30 = 450px, 10 = 150px # Enough so that we're sure that the main thread will not wait after a result.get() call # cpucount+1 should be enough to be sure that the spawned process will not wait after the results @@ -56,8 +57,9 @@ def get_cache(cache_path, readonly=False): return SqliteCache(cache_path, readonly=readonly) -def _extract_blocks(picture, with_dimensions, match_rotated): +def _extract_blocks(picture, with_dimensions, match_rotated, prescale_multiplier): """Extract blocks for one picture. Returns (picture, blocks) or (picture, None) on error.""" + picture._prescale_multiplier = prescale_multiplier if not picture.path: logging.warning("We have a picture with a null path here") return (picture, None) @@ -85,7 +87,8 @@ def _extract_blocks(picture, with_dimensions, match_rotated): return (picture, None) -def prepare_pictures(pictures, cache_path, with_dimensions, match_rotated, j=job.nulljob): +def prepare_pictures(pictures, cache_path, with_dimensions, match_rotated, picture_prescale=1, j=job.nulljob): + prescale_multiplier = PRESCALE_MULTIPLIERS[picture_prescale] cache = get_cache(cache_path) cache.purge_outdated() prepared = [] @@ -106,7 +109,7 @@ def prepare_pictures(pictures, cache_path, with_dimensions, match_rotated, j=job try: with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor: futures = { - executor.submit(_extract_blocks, pic, with_dimensions, match_rotated): pic + executor.submit(_extract_blocks, pic, with_dimensions, match_rotated, prescale_multiplier): pic for pic in needs_extraction } total = len(futures) @@ -191,7 +194,7 @@ def async_compare(ref_ids, other_ids, dbname, threshold, picinfo, match_rotated= return results -def getmatches(pictures, cache_path, threshold, match_scaled=False, match_rotated=False, j=job.nulljob): +def getmatches(pictures, cache_path, threshold, match_scaled=False, match_rotated=False, picture_prescale=1, j=job.nulljob): def get_picinfo(p): if match_scaled: return ((None, None), p.is_ref) @@ -216,7 +219,7 @@ def collect_results(collect_all=False): j.set_progress(comparison_count, progress_msg) j = j.start_subjob([3, 7]) - pictures = prepare_pictures(pictures, cache_path, not match_scaled, match_rotated, j=j) + pictures = prepare_pictures(pictures, cache_path, not match_scaled, match_rotated, picture_prescale, j=j) j = j.start_subjob([9, 1], tr("Preparing for matching")) cache = get_cache(cache_path) id2picture = {} diff --git a/core/pe/scanner.py b/core/pe/scanner.py index 8a1d53d2..fa8eb578 100644 --- a/core/pe/scanner.py +++ b/core/pe/scanner.py @@ -15,6 +15,7 @@ class ScannerPE(Scanner): cache_path = None match_scaled = False match_rotated = False + picture_prescale = 1 @staticmethod def get_scan_options(): @@ -31,6 +32,7 @@ def _getmatches(self, files, j): threshold=self.min_match_percentage, match_scaled=self.match_scaled, match_rotated=self.match_rotated, + picture_prescale=self.picture_prescale, j=j, ) elif self.scan_type == ScanType.EXIFTIMESTAMP: diff --git a/qt/app.py b/qt/app.py index 9447809b..fe9635ff 100644 --- a/qt/app.py +++ b/qt/app.py @@ -196,6 +196,7 @@ def _update_options(self) -> None: self.model.options["scanned_tags"] = scanned_tags self.model.options["match_scaled"] = self.prefs.match_scaled self.model.options["match_rotated"] = self.prefs.match_rotated + self.model.options["picture_prescale"] = self.prefs.picture_prescale self.model.options["include_exists_check"] = self.prefs.include_exists_check self.model.options["rehash_ignore_mtime"] = self.prefs.rehash_ignore_mtime diff --git a/qt/me/preferences_dialog.py b/qt/me/preferences_dialog.py index 61ba91d5..35e8b38b 100644 --- a/qt/me/preferences_dialog.py +++ b/qt/me/preferences_dialog.py @@ -84,7 +84,7 @@ def _load(self, prefs: Preferences, setchecked: Callable[[QCheckBox, bool], None ScanType.TAG, ) tag_based = scan_type == ScanType.TAG - self.filterHardnessSlider.setEnabled(word_based) + self._setFilterHardnessEnabled(word_based) self.matchSimilarBox.setEnabled(word_based) self.wordWeightingBox.setEnabled(word_based) self.tagTrackBox.setEnabled(tag_based) diff --git a/qt/pe/photo.py b/qt/pe/photo.py index 297586ff..2aa9704e 100644 --- a/qt/pe/photo.py +++ b/qt/pe/photo.py @@ -70,7 +70,9 @@ def _plat_get_blocks(self, block_count_per_side, orientation): elif orientation == 8: t.rotate(270) image = image.transformed(t) - target_size = block_count_per_side * 30 - if image.width() > target_size or image.height() > target_size: - image = image.scaled(target_size, target_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) + prescale_multiplier = getattr(self, '_prescale_multiplier', 30) + if prescale_multiplier > 0: + target_size = block_count_per_side * prescale_multiplier + if image.width() > target_size or image.height() > target_size: + image = image.scaled(target_size, target_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) return getblocks(image, block_count_per_side) diff --git a/qt/pe/preferences_dialog.py b/qt/pe/preferences_dialog.py index d56ba237..fa657b1d 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -9,6 +9,8 @@ from PyQt6.QtWidgets import QCheckBox +from PyQt6.QtWidgets import QLabel, QComboBox + from hscommon.trans import trget from hscommon.plat import ISLINUX from core.scanner import ScanType @@ -28,6 +30,13 @@ def _setupPreferenceWidgets(self) -> None: self.widgetsVLayout.addWidget(self.matchScaledBox) self._setupAddCheckbox("matchRotatedBox", tr("Match pictures of different rotations")) self.widgetsVLayout.addWidget(self.matchRotatedBox) + self.prescaleLabel = QLabel(tr("Scan speed:")) + self.widgetsVLayout.addWidget(self.prescaleLabel) + self.prescaleComboBox = QComboBox(self) + self.prescaleComboBox.addItem(tr("Accurate (slow)")) + self.prescaleComboBox.addItem(tr("Balanced (recommended)")) + self.prescaleComboBox.addItem(tr("Turbo")) + self.widgetsVLayout.addWidget(self.prescaleComboBox) self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind")) self.widgetsVLayout.addWidget(self.mixFileKindBox) self._setupAddCheckbox("useRegexpBox", tr("Use regular expressions when filtering")) @@ -65,16 +74,18 @@ def _setupDisplayPage(self) -> None: def _load(self, prefs: Preferences, setchecked: Callable[[QCheckBox, bool], None], section: Sections) -> None: setchecked(self.matchScaledBox, prefs.match_scaled) setchecked(self.matchRotatedBox, prefs.match_rotated) + self.prescaleComboBox.setCurrentIndex(prefs.picture_prescale) # Update UI state based on selected scan type scan_type = prefs.get_scan_type(AppMode.PICTURE) fuzzy_scan = scan_type == ScanType.FUZZYBLOCK - self.filterHardnessSlider.setEnabled(fuzzy_scan) + self._setFilterHardnessEnabled(fuzzy_scan) setchecked(self.details_dialog_override_theme_icons, prefs.details_dialog_override_theme_icons) setchecked(self.details_dialog_viewers_show_scrollbars, prefs.details_dialog_viewers_show_scrollbars) def _save(self, prefs: Preferences, ischecked: Callable[[QCheckBox], bool]) -> None: prefs.match_scaled = ischecked(self.matchScaledBox) prefs.match_rotated = ischecked(self.matchRotatedBox) + prefs.picture_prescale = self.prescaleComboBox.currentIndex() prefs.details_dialog_override_theme_icons = ischecked(self.details_dialog_override_theme_icons) prefs.details_dialog_viewers_show_scrollbars = ischecked(self.details_dialog_viewers_show_scrollbars) diff --git a/qt/preferences.py b/qt/preferences.py index 546f1b54..1b69c30d 100644 --- a/qt/preferences.py +++ b/qt/preferences.py @@ -227,6 +227,7 @@ def _load_values(self, settings) -> None: self.scan_tag_year = get("ScanTagYear", self.scan_tag_year) self.match_scaled = get("MatchScaled", self.match_scaled) self.match_rotated = get("MatchRotated", self.match_rotated) + self.picture_prescale = get("PicturePrescale", self.picture_prescale) def reset(self) -> None: self.filter_hardness = 95 @@ -280,6 +281,7 @@ def reset(self) -> None: self.scan_tag_year = False self.match_scaled = False self.match_rotated = False + self.picture_prescale = 1 # 0=accurate, 1=balanced, 2=turbo def _save_values(self, settings) -> None: set_ = self.set_value @@ -334,6 +336,7 @@ def _save_values(self, settings) -> None: set_("ScanTagYear", self.scan_tag_year) set_("MatchScaled", self.match_scaled) set_("MatchRotated", self.match_rotated) + set_("PicturePrescale", self.picture_prescale) # scan_type is special because we save it immediately when we set it. def get_scan_type(self, app_mode): diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index 478bf084..2c8eaa42 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -74,10 +74,10 @@ def __init__(self, parent: QWidget, app: "DupeGuru", **kwargs): def _setupFilterHardnessBox(self) -> None: self.filterHardnessHLayout = QHBoxLayout() - self.filterHardnessLabel = QLabel(self) - self.filterHardnessLabel.setText(tr("Filter Hardness:")) - self.filterHardnessLabel.setMinimumSize(QSize(0, 0)) - self.filterHardnessHLayout.addWidget(self.filterHardnessLabel) + self.filterHardnessTitleLabel = QLabel(self) + self.filterHardnessTitleLabel.setText(tr("Filter Hardness:")) + self.filterHardnessTitleLabel.setMinimumSize(QSize(0, 0)) + self.filterHardnessHLayout.addWidget(self.filterHardnessTitleLabel) self.filterHardnessVLayout = QVBoxLayout() self.filterHardnessVLayout.setSpacing(0) self.filterHardnessHLayoutSub1 = QHBoxLayout() @@ -111,6 +111,13 @@ def _setupFilterHardnessBox(self) -> None: self.filterHardnessVLayout.addLayout(self.filterHardnessHLayoutSub2) self.filterHardnessHLayout.addLayout(self.filterHardnessVLayout) + def _setFilterHardnessEnabled(self, enabled): + self.filterHardnessSlider.setEnabled(enabled) + self.filterHardnessLabel.setEnabled(enabled) + self.filterHardnessTitleLabel.setEnabled(enabled) + self.moreResultsLabel.setEnabled(enabled) + self.fewerResultsLabel.setEnabled(enabled) + def _setupBottomPart(self) -> None: # The bottom part of the pref panel is always the same in all editions. self.copyMoveLabel = QLabel(self) diff --git a/qt/se/preferences_dialog.py b/qt/se/preferences_dialog.py index dbe76021..f881b0b6 100644 --- a/qt/se/preferences_dialog.py +++ b/qt/se/preferences_dialog.py @@ -113,7 +113,7 @@ def _load(self, prefs: Preferences, setchecked: Callable[[QCheckBox, bool], None # Update UI state based on selected scan type scan_type = prefs.get_scan_type(AppMode.STANDARD) word_based = scan_type == ScanType.FILENAME - self.filterHardnessSlider.setEnabled(word_based) + self._setFilterHardnessEnabled(word_based) self.matchSimilarBox.setEnabled(word_based) self.wordWeightingBox.setEnabled(word_based) From 418a6e64533689f392960a3e28847dee42bc0626 Mon Sep 17 00:00:00 2001 From: romain Date: Sat, 14 Feb 2026 13:28:02 +0100 Subject: [PATCH 12/26] fix: prevent SQL injection in cache modules Replace string interpolation with parameterized queries in cache_sqlite.py and add whitelist validation in fs.py to prevent SQL injection vulnerabilities. - Use parameterized placeholders in get_multiple() - Use parameterized placeholders in purge_outdated() - Add VALID_KEYS whitelist in FilesDB class - Add key validation in get() and put() methods Co-Authored-By: Claude Opus 4.6 --- core/fs.py | 7 +++++++ core/pe/cache_sqlite.py | 11 ++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/fs.py b/core/fs.py index a62d29ce..01512da6 100644 --- a/core/fs.py +++ b/core/fs.py @@ -112,6 +112,9 @@ class FilesDB: ON CONFLICT(path) DO UPDATE SET size=:size, mtime_ns=:mtime_ns, entry_dt=datetime('now'), {key}=:value; """ + # Whitelist of valid column names to prevent SQL injection via .format(key=key) + VALID_KEYS = {"digest", "digest_partial", "digest_samples"} + ignore_mtime = False def __init__(self): @@ -150,6 +153,8 @@ def clear(self) -> None: conn.execute(self.create_table_query) def get(self, path: Path, key: str) -> Union[bytes, None]: + if key not in self.VALID_KEYS: + raise ValueError(f"Invalid cache key: {key}") stat = path.stat() size = stat.st_size mtime_ns = stat.st_mtime_ns @@ -175,6 +180,8 @@ def get(self, path: Path, key: str) -> Union[bytes, None]: return None def put(self, path: Path, key: str, value: Any) -> None: + if key not in self.VALID_KEYS: + raise ValueError(f"Invalid cache key: {key}") stat = path.stat() size = stat.st_size mtime_ns = stat.st_mtime_ns diff --git a/core/pe/cache_sqlite.py b/core/pe/cache_sqlite.py index 9cbe6072..63aa974f 100644 --- a/core/pe/cache_sqlite.py +++ b/core/pe/cache_sqlite.py @@ -156,12 +156,12 @@ def get_id(self, path): raise ValueError(path) def get_multiple(self, rowids): - ids = ",".join(map(str, rowids)) + placeholders = ",".join("?" * len(rowids)) sql = ( "select rowid, blocks, blocks2, blocks3, blocks4, blocks5, blocks6, blocks7, blocks8 " - f"from pictures where rowid in ({ids})" + f"from pictures where rowid in ({placeholders})" ) - cur = self.con.execute(sql) + cur = self.con.execute(sql, list(rowids)) return ( ( rowid, @@ -196,5 +196,6 @@ def purge_outdated(self): continue todelete.append(rowid) if todelete: - sql = "delete from pictures where rowid in (%s)" % ",".join(map(str, todelete)) - self.con.execute(sql) + placeholders = ",".join("?" * len(todelete)) + sql = f"delete from pictures where rowid in ({placeholders})" + self.con.execute(sql, todelete) From 0edf4ccecd6c9b5861c3471a2f80501d680d1ca6 Mon Sep 17 00:00:00 2001 From: romain Date: Sat, 14 Feb 2026 13:26:59 +0100 Subject: [PATCH 13/26] fix: prevent XXE attacks in XML parsing Replace xml.etree.ElementTree.parse() with defusedxml safe parser in all XML loading operations. This prevents XML External Entity (XXE) attacks when loading user-provided XML files. - Add defusedxml dependency to requirements.txt - Use safe_parse() from defusedxml in directories.py - Use safe_parse() from defusedxml in results.py - Use safe_parse() from defusedxml in ignore.py - Use safe_parse() from defusedxml in exclude.py - Maintain xml.etree for XML writing (safe operations) Co-Authored-By: Claude Opus 4.6 --- core/directories.py | 3 ++- core/exclude.py | 3 ++- core/ignore.py | 3 ++- core/results.py | 3 ++- requirements.txt | 1 + 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/directories.py b/core/directories.py index a30e263a..4f219fa7 100644 --- a/core/directories.py +++ b/core/directories.py @@ -6,6 +6,7 @@ import os from xml.etree import ElementTree as ET +from defusedxml.ElementTree import parse as safe_parse import logging from pathlib import Path @@ -246,7 +247,7 @@ def load_from_file(self, infile): :param file infile: path or file pointer to XML generated through :meth:`save_to_file` """ try: - root = ET.parse(infile).getroot() + root = safe_parse(infile).getroot() except Exception: return for rdn in root.iter("root_directory"): diff --git a/core/exclude.py b/core/exclude.py index e304ef12..0e1a7578 100644 --- a/core/exclude.py +++ b/core/exclude.py @@ -4,6 +4,7 @@ from core.markable import Markable from xml.etree import ElementTree as ET +from defusedxml.ElementTree import parse as safe_parse # TODO: perhaps use regex module for better Unicode support? https://pypi.org/project/regex/ # also https://pypi.org/project/re2/ @@ -321,7 +322,7 @@ def load_from_xml(self, infile): infile can be a file object or a filename. """ try: - root = ET.parse(infile).getroot() + root = safe_parse(infile).getroot() except Exception as e: logging.warning(f"Error while loading {infile}: {e}") self.restore_defaults() diff --git a/core/ignore.py b/core/ignore.py index 095da4fe..a9aed03d 100644 --- a/core/ignore.py +++ b/core/ignore.py @@ -7,6 +7,7 @@ # http://www.gnu.org/licenses/gpl-3.0.html from xml.etree import ElementTree as ET +from defusedxml.ElementTree import parse as safe_parse from hscommon.util import FileOrPath @@ -96,7 +97,7 @@ def load_from_xml(self, infile): infile can be a file object or a filename. """ try: - root = ET.parse(infile).getroot() + root = safe_parse(infile).getroot() except Exception: return file_elems = (e for e in root if e.tag == "file") diff --git a/core/results.py b/core/results.py index f9c9356e..19c2db13 100644 --- a/core/results.py +++ b/core/results.py @@ -12,6 +12,7 @@ import os.path as op from errno import EISDIR, EACCES from xml.etree import ElementTree as ET +from defusedxml.ElementTree import parse as safe_parse from hscommon.jobprogress.job import nulljob from hscommon.conflict import get_conflicted_name @@ -228,7 +229,7 @@ def do_match(ref_file, other_files, group): do_match(other_files[0], other_files[1:], group) self.apply_filter(None) - root = ET.parse(infile).getroot() + root = safe_parse(infile).getroot() group_elems = list(root.iter("group")) groups = [] marked = set() diff --git a/requirements.txt b/requirements.txt index ad1a01cd..fc3c4e03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +defusedxml>=0.7.1,<1.0.0 distro>=1.8.0,<2.0.0 mutagen>=1.46.0,<2.0.0 polib>=1.1.0,<2.0.0 From f4488bc42240048ed4d643f1dd5803632253ed3a Mon Sep 17 00:00:00 2001 From: romain Date: Sat, 14 Feb 2026 13:25:53 +0100 Subject: [PATCH 14/26] fix: prevent command injection in custom command feature Replace shell=True subprocess calls with properly tokenized arguments using shlex.split() and shell=False. This prevents command injection when file paths contain shell metacharacters. - Use shlex.split() to tokenize custom command string - Replace %d/%r placeholders in tokenized arguments - Execute with shell=False for security - Add error handling with user feedback - Remove Windows quoted-path workaround (unnecessary with shell=False) Co-Authored-By: Claude Opus 4.6 --- core/app.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/core/app.py b/core/app.py index 97682550..8455bd7d 100644 --- a/core/app.py +++ b/core/app.py @@ -9,8 +9,8 @@ import os import os.path as op import logging +import shlex import subprocess -import re import shutil from pathlib import Path @@ -547,24 +547,22 @@ def invoke_custom_command(self): dupes = self.selected_dupes refs = [self.results.get_group_of_duplicate(dupe).ref for dupe in dupes] for dupe, ref in zip(dupes, refs): - dupe_cmd = cmd.replace("%d", str(dupe.path)) - dupe_cmd = dupe_cmd.replace("%r", str(ref.path)) - match = re.match(r'"([^"]+)"(.*)', dupe_cmd) - if match is not None: - # This code here is because subprocess. Popen doesn't seem to accept, under Windows, - # executable paths with spaces in it, *even* when they're enclosed in "". So this is - # a workaround to make the damn thing work. - exepath, args = match.groups() - path, exename = op.split(exepath) - p = subprocess.Popen( - exename + args, shell=True, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) - output = p.stdout.read() - logging.info("Custom command %s %s: %s", exename, args, output) - else: - p = subprocess.Popen(dupe_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # Tokenize the command template safely before substituting paths + try: + cmd_parts = shlex.split(cmd) + except ValueError as e: + logging.error("Invalid custom command syntax: %s", e) + self.view.show_message(tr("Invalid custom command syntax: {}").format(str(e))) + return + # Replace placeholders in each token individually to prevent injection + cmd_parts = [part.replace("%d", str(dupe.path)).replace("%r", str(ref.path)) for part in cmd_parts] + try: + p = subprocess.Popen(cmd_parts, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = p.stdout.read() - logging.info("Custom command %s: %s", dupe_cmd, output) + logging.info("Custom command %s: %s", cmd_parts, output) + except OSError as e: + logging.error("Failed to execute custom command %s: %s", cmd_parts, e) + self.view.show_message(tr("Failed to execute custom command: {}").format(str(e))) def load(self): """Load directory selection and ignore list from files in appdata. From abc0287036346cd4d5f4351e6735ae679a347cd5 Mon Sep 17 00:00:00 2001 From: ojczeo Date: Thu, 4 Jul 2024 20:23:42 +0200 Subject: [PATCH 15/26] fix: Update columns.po Localization PL: better word --- locale/pl_PL/LC_MESSAGES/columns.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/pl_PL/LC_MESSAGES/columns.po b/locale/pl_PL/LC_MESSAGES/columns.po index 3d6e4234..84a0a46d 100644 --- a/locale/pl_PL/LC_MESSAGES/columns.po +++ b/locale/pl_PL/LC_MESSAGES/columns.po @@ -94,7 +94,7 @@ msgstr "Komentarz" #: core\me\result_table.py:34 core\pe\result_table.py:26 #: core\se\result_table.py:24 msgid "Match %" -msgstr "Mecz %" +msgstr "Dopasowanie %" #: core\me\result_table.py:35 core\se\result_table.py:25 msgid "Words Used" From 0b3b8869d1a894a6fc4770cb2cb1325f47346d37 Mon Sep 17 00:00:00 2001 From: ojczeo Date: Thu, 4 Jul 2024 20:24:20 +0200 Subject: [PATCH 16/26] fix: Update core.po Localization PL: better word --- locale/pl_PL/LC_MESSAGES/core.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/pl_PL/LC_MESSAGES/core.po b/locale/pl_PL/LC_MESSAGES/core.po index e1b36765..3a159c5c 100644 --- a/locale/pl_PL/LC_MESSAGES/core.po +++ b/locale/pl_PL/LC_MESSAGES/core.po @@ -196,7 +196,7 @@ msgstr "Przygotowanie do dopasowania" #: core\pe\matchblock.py:240 msgid "Verified %d/%d matches" -msgstr "Zweryfikowane %d/%d meczów" +msgstr "Zweryfikowane %d/%d dopasowań" #: core\pe\matchexif.py:19 msgid "Read EXIF of %d/%d pictures" From c54d1af7fa1feffcd969f15c31e5a01f2e597ff3 Mon Sep 17 00:00:00 2001 From: Nicolas G Date: Fri, 1 Mar 2024 09:25:50 +0100 Subject: [PATCH 17/26] Update core.po Ajouts et typo --- locale/fr/LC_MESSAGES/core.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/locale/fr/LC_MESSAGES/core.po b/locale/fr/LC_MESSAGES/core.po index 1ff37ae0..7e59101b 100644 --- a/locale/fr/LC_MESSAGES/core.po +++ b/locale/fr/LC_MESSAGES/core.po @@ -52,8 +52,8 @@ msgid "" "A previous action is still hanging in there. You can't start a new one yet. " "Wait a few seconds, then try again." msgstr "" -"Une action précédente est encore en cours. Attendez quelques secondes avant " -"d'en repartir une nouvelle." +"Une action précédente est encore en cours. Vous ne pouvez pas encore en démarrer une nouvelle. " +"Attendez quelques secondes et essayer à nouveau." #: core\app.py:300 msgid "No duplicates found." @@ -69,7 +69,7 @@ msgstr "Tous les fichiers marqués ont été déplacés correctement." #: core\app.py:319 msgid "All marked files were deleted successfully." -msgstr "" +msgstr "Tous les fichiers marqués ont bien été supprimés." #: core\app.py:321 msgid "All marked files were successfully sent to Trash." @@ -137,11 +137,11 @@ msgstr "%s (%d hors-groupe)" #: core\directories.py:191 msgid "Collected {} files to scan" -msgstr "" +msgstr "{} fichiers collectés pour scanner" #: core\directories.py:207 msgid "Collected {} folders to scan" -msgstr "" +msgstr "{} dossiers collectés pour scanner" #: core\engine.py:27 msgid "%d matches found from %d groups" @@ -183,7 +183,7 @@ msgstr "Contenu" #: core\pe\matchblock.py:66 msgid "Analyzed %d/%d pictures" -msgstr "Analyzé %d/%d images" +msgstr "Analysé %d/%d images" #: core\pe\matchblock.py:183 msgid "Performed %d/%d chunk matches" @@ -215,7 +215,7 @@ msgstr "Chiffres à la fin" #: core\prioritize.py:103 msgid "Doesn't end with number" -msgstr "Pas de chiffres à la finr" +msgstr "Pas de chiffres à la fin" #: core\prioritize.py:104 msgid "Longest" From b25bf3c5ff6d1e60cd2dd601b3e0eec05a97abc7 Mon Sep 17 00:00:00 2001 From: Nicolas G Date: Fri, 1 Mar 2024 09:27:53 +0100 Subject: [PATCH 18/26] Update columns.po Ajouts et typo --- locale/fr/LC_MESSAGES/columns.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/locale/fr/LC_MESSAGES/columns.po b/locale/fr/LC_MESSAGES/columns.po index 58a4b7d9..d88e256d 100644 --- a/locale/fr/LC_MESSAGES/columns.po +++ b/locale/fr/LC_MESSAGES/columns.po @@ -44,7 +44,7 @@ msgstr "Dossier" #: core\me\result_table.py:21 msgid "Size (MB)" -msgstr "Taille (MB)" +msgstr "Taille (Mo)" #: core\me\result_table.py:22 msgid "Time" @@ -52,7 +52,7 @@ msgstr "Temps" #: core\me\result_table.py:24 msgid "Sample Rate" -msgstr "Sample Rate" +msgstr "Échantillonnage" #: core\me\result_table.py:25 core\pe\result_table.py:22 core\prioritize.py:65 #: core\se\result_table.py:22 @@ -86,7 +86,7 @@ msgstr "Année" #: core\me\result_table.py:32 msgid "Track Number" -msgstr "Track" +msgstr "Numéro de piste" #: core\me\result_table.py:33 msgid "Comment" @@ -95,16 +95,16 @@ msgstr "Commentaire" #: core\me\result_table.py:34 core\pe\result_table.py:26 #: core\se\result_table.py:24 msgid "Match %" -msgstr "Match %" +msgstr "Correspond à %" #: core\me\result_table.py:35 core\se\result_table.py:25 msgid "Words Used" -msgstr "Mots" +msgstr "Mots utilisés" #: core\me\result_table.py:36 core\pe\result_table.py:27 #: core\se\result_table.py:26 msgid "Dupe Count" -msgstr "Nombre de Doublons" +msgstr "Nombre de doublons" #: core\pe\prioritize.py:23 core\pe\result_table.py:23 msgid "Dimensions" @@ -112,7 +112,7 @@ msgstr "Dimensions" #: core\pe\result_table.py:21 core\se\result_table.py:21 msgid "Size (KB)" -msgstr "Taille (KB)" +msgstr "Taille (Ko)" #: core\pe\result_table.py:24 msgid "EXIF Timestamp" From e8ff386f11e2ec2ffa2e4b6362e7da9b98c65edb Mon Sep 17 00:00:00 2001 From: Nicolas G Date: Fri, 1 Mar 2024 10:08:04 +0100 Subject: [PATCH 19/26] Update ui.po Ajouts, typo et corrections --- locale/fr/LC_MESSAGES/ui.po | 151 +++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 72 deletions(-) diff --git a/locale/fr/LC_MESSAGES/ui.po b/locale/fr/LC_MESSAGES/ui.po index 3b8c231c..30a32ec6 100644 --- a/locale/fr/LC_MESSAGES/ui.po +++ b/locale/fr/LC_MESSAGES/ui.po @@ -43,7 +43,7 @@ msgstr "Ouvrir logs de déboguage" #: qt/app.py:180 cocoa/en.lproj/Localizable.strings:0 msgid "Do you really want to remove all your cached picture analysis?" -msgstr "Voulez-vous vraiment vider la cache de vos analyses précédentes?" +msgstr "Voulez-vous vraiment vider la cache de vos analyses précédentes ?" #: qt/app.py:184 msgid "Picture cache cleared." @@ -66,16 +66,16 @@ msgid "" "After having deleted a duplicate, place a link targeting the reference file " "to replace the deleted file." msgstr "" -"Après avoir effacé un fichier, remplacer celui-ci par un lien vers le " +"Après avoir effacé un fichier dupliqué, remplacer celui-ci par un lien vers le " "fichier référence." #: qt/deletion_options.py:44 msgid "Hardlink" -msgstr "Hardlink" +msgstr "Lien en dur" #: qt/deletion_options.py:44 msgid "Symlink" -msgstr "Symlink" +msgstr "Lien symbolique" #: qt/deletion_options.py:48 msgid " (unsupported)" @@ -91,7 +91,7 @@ msgid "" "usually used as a workaround when the normal deletion method doesn't work." msgstr "" "Au lieu de passer par la corbeille, supprimer directement. Cette option " -"n'est généralement utilisée qu'en cas de problème." +"n'est généralement utilisée qu'en cas d'échec avec la métode de suppression normale." #: qt/deletion_options.py:59 cocoa/en.lproj/Localizable.strings:0 msgid "Proceed" @@ -162,7 +162,7 @@ msgstr "Standard" #: qt/directories_dialog.py:128 cocoa/en.lproj/Localizable.strings:0 msgid "Scan Type:" -msgstr "Type de scan:" +msgstr "Type de scan :" #: qt/directories_dialog.py:135 msgid "More Options" @@ -186,7 +186,7 @@ msgstr "Résultats non sauvegardés" #: qt/directories_dialog.py:231 cocoa/en.lproj/Localizable.strings:0 msgid "You have unsaved results, do you really want to quit?" -msgstr "Vos résultats ne sont pas sauvegardés. Voulez-vous vraiment quitter?" +msgstr "Vos résultats ne sont pas sauvegardés. Voulez-vous vraiment quitter ?" #: qt/directories_dialog.py:239 cocoa/en.lproj/Localizable.strings:0 msgid "Select a folder to add to the scanning list" @@ -211,7 +211,7 @@ msgstr "Commencer un nouveau scan" #: qt/directories_dialog.py:279 cocoa/en.lproj/Localizable.strings:0 msgid "You have unsaved results, do you really want to continue?" msgstr "" -"Vos résultats ne sont pas sauvegardés. Voulez-vous vraiment continuer?" +"Vos résultats ne sont pas sauvegardés. Voulez-vous vraiment continuer ?" #: qt/directories_model.py:23 cocoa/en.lproj/Localizable.strings:0 msgid "Name" @@ -254,7 +254,7 @@ msgstr "Tags à scanner:" #: qt/me/preferences_dialog.py:36 cocoa/en.lproj/Localizable.strings:0 msgid "Track" -msgstr "Track" +msgstr "Piste" #: qt/me/preferences_dialog.py:38 cocoa/en.lproj/Localizable.strings:0 msgid "Artist" @@ -299,12 +299,12 @@ msgstr "Utiliser les expressions régulières pour les filtres" #: qt/me/preferences_dialog.py:58 qt/pe/preferences_dialog.py:25 #: qt/se/preferences_dialog.py:38 cocoa/en.lproj/Localizable.strings:0 msgid "Remove empty folders on delete or move" -msgstr "Effacer les dossiers vides après un déplacement" +msgstr "Effacer les dossiers vides après une suppression ou un déplacement" #: qt/me/preferences_dialog.py:60 qt/pe/preferences_dialog.py:27 #: qt/se/preferences_dialog.py:59 cocoa/en.lproj/Localizable.strings:0 msgid "Ignore duplicates hardlinking to the same file" -msgstr "Ignorer doublons avec hardlink vers le même fichier" +msgstr "Ignorer doublons avec un lien en dur vers le même fichier" #: qt/me/preferences_dialog.py:62 qt/pe/preferences_dialog.py:29 #: qt/se/preferences_dialog.py:62 cocoa/en.lproj/Localizable.strings:0 @@ -317,7 +317,7 @@ msgstr "Comparer les images de tailles différentes" #: qt/preferences_dialog.py:43 msgid "Filter Hardness:" -msgstr "Seuil du filtre:" +msgstr "Seuil du filtre :" #: qt/preferences_dialog.py:69 msgid "More Results" @@ -329,15 +329,15 @@ msgstr "Moins de doublons" #: qt/preferences_dialog.py:81 msgid "Font size:" -msgstr "Taille de police:" +msgstr "Taille de police :" #: qt/preferences_dialog.py:85 msgid "Language:" -msgstr "Langue:" +msgstr "Langue :" #: qt/preferences_dialog.py:91 cocoa/en.lproj/Localizable.strings:0 msgid "Copy and Move:" -msgstr "Déplacements de fichiers:" +msgstr "Copie et déplacements de fichiers :" #: qt/preferences_dialog.py:94 cocoa/en.lproj/Localizable.strings:0 msgid "Right in destination" @@ -353,7 +353,7 @@ msgstr "Re-créer chemins absolus" #: qt/preferences_dialog.py:99 msgid "Custom Command (arguments: %d for dupe, %r for ref):" -msgstr "Commande perso. (arguments: %d pour doublon, %r pour réf):" +msgstr "Commande perso. (arguments : %d pour doublon, %r pour réf) :" #: qt/preferences_dialog.py:174 msgid "dupeGuru has to restart for language changes to take effect." @@ -369,13 +369,13 @@ msgid "" " the best to these criteria to their respective group's reference position. " "Read the help file for more information." msgstr "" -"Ajoutez des critères dans la liste de droite pour envoyer les doublons qui " -"correspondent le plus à ces critère à la position de référence. Une lecture " +"Ajoutez des critères dans la liste de droite et cliquez sur OK pour envoyer les doublons qui " +"correspondent le plus à ces critères à la position de référence. Une lecture " "préalable du fichier d'aide est conseillée." #: qt/problem_dialog.py:33 cocoa/en.lproj/Localizable.strings:0 msgid "Problems!" -msgstr "Problèmes!" +msgstr "Problèmes !" #: qt/problem_dialog.py:37 cocoa/en.lproj/Localizable.strings:0 msgid "" @@ -383,13 +383,13 @@ msgid "" "these problems are described in the table below. Those files were not " "removed from your results." msgstr "" -"Des problèmes ont été rencontrés lors du traitement de certains fichiers. La" +"Des problèmes ont été rencontrés lors du traitement de certains fichiers (ou tous). La" " nature de ces problèmes est décrite dans la liste ci-dessous. Ces fichiers " "n'ont pas été retirés des résultats." #: qt/problem_dialog.py:56 msgid "Reveal Selected" -msgstr "Révéler Fichier" +msgstr "Révéler la selection" #: qt/result_window.py:57 qt/result_window.py:104 qt/result_window.py:167 #: qt/result_window.py:191 cocoa/en.lproj/Localizable.strings:0 @@ -406,19 +406,19 @@ msgstr "Montrer les valeurs en tant que delta" #: qt/result_window.py:60 msgid "Send Marked to Recycle Bin..." -msgstr "Envoyer marqués à la corbeille..." +msgstr "Envoyer les fichiers marqués à la corbeille..." #: qt/result_window.py:61 cocoa/en.lproj/Localizable.strings:0 msgid "Move Marked to..." -msgstr "Déplacer marqués vers..." +msgstr "Déplacer les fichiers marqués vers..." #: qt/result_window.py:62 cocoa/en.lproj/Localizable.strings:0 msgid "Copy Marked to..." -msgstr "Copier marqués vers..." +msgstr "Copier les fichiers marqués vers..." #: qt/result_window.py:63 cocoa/en.lproj/Localizable.strings:0 msgid "Remove Marked from Results" -msgstr "Retirer marqués des résultats" +msgstr "Retirer les fichiers marqués des résultats" #: qt/result_window.py:64 cocoa/en.lproj/Localizable.strings:0 msgid "Re-Prioritize Results..." @@ -426,19 +426,19 @@ msgstr "Re-prioriser les résultats" #: qt/result_window.py:67 cocoa/en.lproj/Localizable.strings:0 msgid "Remove Selected from Results" -msgstr "Retirer sélectionnés des résultats" +msgstr "Retirer les fichiers sélectionnés des résultats" #: qt/result_window.py:71 cocoa/en.lproj/Localizable.strings:0 msgid "Add Selected to Ignore List" -msgstr "Ajouter sélectionnés à la liste de fichiers ignorés" +msgstr "Ajouter les fichiers sélectionnés à la liste de fichiers ignorés" #: qt/result_window.py:75 cocoa/en.lproj/Localizable.strings:0 msgid "Make Selected into Reference" -msgstr "Transformer sélectionnés en références" +msgstr "Transformer les fichiers sélectionnés en références" #: qt/result_window.py:77 cocoa/en.lproj/Localizable.strings:0 msgid "Open Selected with Default Application" -msgstr "Ouvrir sélectionné avec l'application par défaut" +msgstr "Ouvrir les fichiers sélectionnés avec l'application par défaut" #: qt/result_window.py:80 msgid "Open Containing Folder of Selected" @@ -466,19 +466,19 @@ msgstr "Marquer sélectionnés" #: qt/result_window.py:87 msgid "Export To HTML" -msgstr "Exporter vers HTML" +msgstr "Exporter en HTML" #: qt/result_window.py:88 msgid "Export To CSV" -msgstr "Exporter vers CSV" +msgstr "Exporter en CSV" #: qt/result_window.py:89 cocoa/en.lproj/Localizable.strings:0 msgid "Save Results..." -msgstr "Sauvegarder résultats..." +msgstr "Sauvegarder les résultats..." #: qt/result_window.py:90 cocoa/en.lproj/Localizable.strings:0 msgid "Invoke Custom Command" -msgstr "Invoquer commande personnalisée" +msgstr "Invoquer une commande personnalisée" #: qt/result_window.py:102 msgid "Mark" @@ -498,7 +498,7 @@ msgstr "{} résultats" #: qt/result_window.py:193 cocoa/en.lproj/Localizable.strings:0 msgid "Dupes Only" -msgstr "Sans réf." +msgstr "Dupliqués seulement" #: qt/result_window.py:194 msgid "Delta Values" @@ -514,7 +514,7 @@ msgstr "Ignorer les fichiers plus petits que" #: qt/se/preferences_dialog.py:52 cocoa/en.lproj/Localizable.strings:0 msgid "KB" -msgstr "KB" +msgstr "Ko" #: cocoa/en.lproj/Localizable.strings:0 msgid "%@ Results" @@ -526,7 +526,7 @@ msgstr "Action" #: cocoa/en.lproj/Localizable.strings:0 msgid "Add New Folder..." -msgstr "Ajouter dossier..." +msgstr "Ajouter un nouveau dossier..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Advanced" @@ -546,7 +546,7 @@ msgstr "Tout ramener au premier plan" #: cocoa/en.lproj/Localizable.strings:0 msgid "Check for update..." -msgstr "Mise à jour..." +msgstr "Recherche de mise à jour..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Close Window" @@ -558,7 +558,7 @@ msgstr "Copier" #: cocoa/en.lproj/Localizable.strings:0 msgid "Custom command (arguments: %d for dupe, %r for ref):" -msgstr "Commande perso. (arguments: %d pour doublon, %r pour réf):" +msgstr "Commande perso. (arguments : %d pour doublon, %r pour réf) :" #: cocoa/en.lproj/Localizable.strings:0 msgid "Cut" @@ -602,11 +602,11 @@ msgstr "Édition" #: cocoa/en.lproj/Localizable.strings:0 msgid "Export Results to CSV" -msgstr "Exporter les résultats vers CSV" +msgstr "Exporter les résultats en CSV" #: cocoa/en.lproj/Localizable.strings:0 msgid "Export Results to XHTML" -msgstr "Exporter les résultats vers HTML" +msgstr "Exporter les résultats en XHTML" #: cocoa/en.lproj/Localizable.strings:0 msgid "Fewer results" @@ -618,7 +618,7 @@ msgstr "Filtre" #: cocoa/en.lproj/Localizable.strings:0 msgid "Filter hardness:" -msgstr "Seuil du filtre:" +msgstr "Seuil du filtre :" #: cocoa/en.lproj/Localizable.strings:0 msgid "Filter Results..." @@ -630,7 +630,7 @@ msgstr "Fenêtre de sélection de dossiers" #: cocoa/en.lproj/Localizable.strings:0 msgid "Font Size:" -msgstr "Taille de police:" +msgstr "Taille de police :" #: cocoa/en.lproj/Localizable.strings:0 msgid "Hide dupeGuru" @@ -642,7 +642,7 @@ msgstr "Masquer les autres" #: cocoa/en.lproj/Localizable.strings:0 msgid "Ignore files smaller than:" -msgstr "Ignorer les fichiers plus petits que:" +msgstr "Ignorer les fichiers plus petits que :" #: cocoa/en.lproj/Localizable.strings:0 msgid "Load from file..." @@ -658,7 +658,7 @@ msgstr "Mode" #: cocoa/en.lproj/Localizable.strings:0 msgid "More results" -msgstr "plus de doublons" +msgstr "Plus de doublons" #: cocoa/en.lproj/Localizable.strings:0 msgid "Ok" @@ -674,7 +674,7 @@ msgstr "Préférences..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Quick Look" -msgstr "Quick Look" +msgstr "Regard rapide" #: cocoa/en.lproj/Localizable.strings:0 msgid "Quit dupeGuru" @@ -798,11 +798,11 @@ msgstr "Erreur de compilation:" #: qt\pe\image_viewer.py:56 msgid "Increase zoom" -msgstr "Accroître zoom" +msgstr "Zoomer" #: qt\pe\image_viewer.py:66 msgid "Decrease zoom" -msgstr "Décroître zoom" +msgstr "Dézoomer" #: qt\pe\image_viewer.py:71 msgid "Ctrl+/" @@ -822,7 +822,7 @@ msgstr "Meilleur ajustement" #: qt\pe\preferences_dialog.py:49 msgid "Picture cache mode:" -msgstr "Mode de cache d'images:" +msgstr "Mode de cache d'images :" #: qt\pe\preferences_dialog.py:56 msgid "Override theme icons in viewer toolbar" @@ -945,59 +945,61 @@ msgstr "Affichage" #: qt\se\preferences_dialog.py:70 msgid "Partially hash files bigger than" -msgstr "" +msgstr "Hacher partiellement les fichiers plus gros que" #: qt\se\preferences_dialog.py:80 msgid "MB" -msgstr "" +msgstr "Mo" #: qt\preferences_dialog.py:163 msgid "Use native OS dialogs" -msgstr "" +msgstr "Utiliser les dialogues natifs de l'OS" #: qt\preferences_dialog.py:166 msgid "" "For actions such as file/folder selection use the OS native dialogs.\n" "Some native dialogs have limited functionality." -msgstr "" +msgstr "Pour les actions telles que la sélection de fichiers/dossiers, utilisez les boîtes de dialogue natives du système d'exploitation.\n" +"Des boites de dialogues ont des fonctionnalités limitées." #: qt\se\preferences_dialog.py:68 msgid "Ignore files larger than" -msgstr "" +msgstr "Ignorer les fichiers plus gros que" #: qt\app.py:135 qt\app.py:293 msgid "Clear Cache" -msgstr "" +msgstr "Vider le cache" #: qt\app.py:294 msgid "" "Do you really want to clear the cache? This will remove all cached file " "hashes and picture analysis." -msgstr "" +msgstr "Voulez-vous réellement vider le cacher ? Cela supprimera tous les hash de fichiers dans le cache" +" et les analyses d'image." #: qt\app.py:299 msgid "Cache cleared." -msgstr "" +msgstr "Cache vidé." #: qt\preferences_dialog.py:173 msgid "Use dark style" -msgstr "" +msgstr "Utliliser le style sombre" #: qt\preferences_dialog.py:241 msgid "Profile scan operation" -msgstr "" +msgstr "Opération de balayage de profil" #: qt\preferences_dialog.py:242 msgid "Profile the scan operation and save logs for optimization." -msgstr "" +msgstr "Profilage de l'opération de balayage et enregistrement des journaux pour optimisation" #: qt\preferences_dialog.py:246 msgid "Logs located in: {}" -msgstr "" +msgstr "Les logs se trouvent dans : {}" #: qt\preferences_dialog.py:291 msgid "Debug" -msgstr "" +msgstr "Débogage" #: qt\about_box.py:31 msgid "About {}" @@ -1009,7 +1011,7 @@ msgstr "Version {}" #: qt\about_box.py:49 qt\about_box.py:75 msgid "Checking for updates..." -msgstr "" +msgstr "Recherche des mises à jour..." #: qt\about_box.py:54 msgid "Licensed under GPLv3" @@ -1017,11 +1019,11 @@ msgstr "Sous licence GPLv3" #: qt\about_box.py:68 msgid "No update available." -msgstr "" +msgstr "Pas de mise à jour disponible." #: qt\about_box.py:71 msgid "New version {} available, download here." -msgstr "" +msgstr "Une nouvelle version ({}) est disponible, téléchagez-là ici." #: qt\error_report_dialog.py:50 msgid "Error Report" @@ -1029,7 +1031,7 @@ msgstr "Rapport d'erreur" #: qt\error_report_dialog.py:54 msgid "Something went wrong. How about reporting the error?" -msgstr "Un problème est survenu. Rapporter l'erreur?" +msgstr "Un problème est survenu. Rapporter l'erreur ?" #: qt\error_report_dialog.py:60 msgid "" @@ -1141,31 +1143,36 @@ msgstr "Recherche..." msgid "" "These options are for advanced users or for very specific situations, most " "users should not have to modify these." -msgstr "" +msgstr "Ces options sont destinées aux utilisateurs avancés ou pour des situation bien spécifiques, " +"la plupart des utilisateurs ne devraient pas avoir à les modifier." #: qt\preferences_dialog.py:225 msgid "Include existence check after scan completion" -msgstr "" +msgstr "Inclure un contrôle d'existence à la fin de l'analyse" #: qt\preferences_dialog.py:227 msgid "Ignore difference in mtime when loading cached digests" -msgstr "" +msgstr "Ignorer la différence dans le mtime en chargent les résumés en cache" #: qt\progress_window.py:64 msgid "Cancel?" -msgstr "" +msgstr "Annuler ?" #: qt\progress_window.py:65 msgid "Are you sure you want to cancel? All progress will be lost." -msgstr "" +msgstr "Êtes-vous sûr de vouloir annuler ? Tout ce qui a déjà été fait va être perdu." #: qt\exclude_list_dialog.py:161 msgid "" "These (case sensitive) python regular expressions will filter out files during scans.
Directores will also have their default state set to Excluded in the Directories tab if their name happens to match one of the selected regular expressions.
For each file collected, two tests are performed to determine whether or not to completely ignore it:
  • 1. Regular expressions with no path separator in them will be compared to the file name only.
  • \n" "
  • 2. Regular expressions with at least one path separator in them will be compared to the full path to the file.

  • Example: if you want to filter out .PNG files from the \"My Pictures\" directory only:
    .*My\\sPictures\\\\.*\\.png

    You can test the regular expression with the \"test string\" button after pasting a fake path in the test field:
    C:\\\\User\\My Pictures\\test.png

    \n" "Matching regular expressions will be highlighted.
    If there is at least one highlight, the path or filename tested will be ignored during scans.

    Directories and files starting with a period '.' are filtered out by default.

    " -msgstr "" +msgstr "Cette expression régulière python (sensible à la casse) va filtrer les fichiers pendant les scans.
    " +"Ces expressions régulières python (sensible aux majuscules) peuvent ignorer les fichiers pendant les scans. Les dossiers auront également leur état par défaut mis sur Exclus dans l'onglet Dossiers si leur nom correspond à une des expressions régulières sélectionnées
    Pour chaque fichier collecté, deux tests sont faits pour déterminer s'il doit être totalement ignoré:
  • 1. Les expressions régulières sans séparateur de chemin sont comparées au nom de fichier seul.
  • \n" +"
  • 2. Les expressions régulières avec au moins un séparateur de chemin sont comparées au chemin complet vers le fichier.

  • \n" +"Exemple: si vous voulez uniquement ignorer les fichiers .PNG du dossier \"Mes Images\":
    .*Mes\\sImages\\\\.*\\.png

    Vous pouvez tester l'expression régulière via le bouton \"Tester la chaîne de caractères\" après avoir tapé un faux chemin de fichier dans le champs correspondant:
    C:\\\\Utilisateur\\Mes Images\\test.png

    \n" +"Les expressions régulières qui fonctionnent seront surlignées.
    S'il y a au moins un surlignage, le chemin ou nom de fichier testé sera ignoré durant les scans.

    Les dossiers et fichiers commençant par un point '.' sont ignorés par défaut.

    " #: qt\pe\preferences_dialog.py:24 msgid "Match pictures of different rotations" -msgstr "" +msgstr "Associer des images de différentes rotations" From 4f1326af0d490b1de41995baad2ff1fc34601a38 Mon Sep 17 00:00:00 2001 From: Stanislav <42420548+PushKK@users.noreply.github.com> Date: Sat, 24 Jun 2023 12:04:47 +0300 Subject: [PATCH 20/26] Update Russian language --- locale/ru/LC_MESSAGES/columns.po | 2 +- locale/ru/LC_MESSAGES/core.po | 14 +++---- locale/ru/LC_MESSAGES/ui.po | 65 ++++++++++++++++---------------- 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/locale/ru/LC_MESSAGES/columns.po b/locale/ru/LC_MESSAGES/columns.po index 0040ff6b..4223790d 100644 --- a/locale/ru/LC_MESSAGES/columns.po +++ b/locale/ru/LC_MESSAGES/columns.po @@ -112,7 +112,7 @@ msgstr "Размеры" #: core\pe\result_table.py:21 core\se\result_table.py:21 msgid "Size (KB)" -msgstr "Размер (КБ)" +msgstr "Размер (кБ)" #: core\pe\result_table.py:24 msgid "EXIF Timestamp" diff --git a/locale/ru/LC_MESSAGES/core.po b/locale/ru/LC_MESSAGES/core.po index ea3b9415..b00592a8 100644 --- a/locale/ru/LC_MESSAGES/core.po +++ b/locale/ru/LC_MESSAGES/core.po @@ -100,15 +100,15 @@ msgstr "" #: core\app.py:469 msgid "Select a directory to copy marked files to" -msgstr "Выберите каталог, в который вы хотите скопировать отмеченные файлы" +msgstr "Выберите каталог, в который Вы хотите скопировать отмеченные файлы" #: core\app.py:471 msgid "Select a directory to move marked files to" -msgstr "Выберите каталог, в который вы хотите переместить отмеченные файлы" +msgstr "Выберите каталог для перемещения отмеченных файлов" #: core\app.py:510 msgid "Select a destination for your exported CSV" -msgstr "Выберите назначение для экспортируемого " +msgstr "Выберите назначение для Вашего экспортируемого CSV" #: core\app.py:516 core\app.py:777 core\app.py:787 msgid "Couldn't write to file: {}" @@ -124,7 +124,7 @@ msgstr "Вы собираетесь удалить %d файлов из резу #: core\app.py:749 msgid "{} duplicate groups were changed by the re-prioritization." -msgstr "{} групп дубликатов было изменено при реприоритезации." +msgstr "{} групп дубликатов было изменено при обновлении приоритета." #: core\app.py:797 msgid "The selected directories contain no scannable file." @@ -136,7 +136,7 @@ msgstr "Сбор файлов для сканирования" #: core\app.py:863 msgid "%s (%d discarded)" -msgstr "%s. (%d отменено)" +msgstr "%s (%d отменено)" #: core\directories.py:191 msgid "Collected {} files to scan" @@ -148,7 +148,7 @@ msgstr "Собрано {} каталогов для сканирования" #: core\engine.py:27 msgid "%d matches found from %d groups" -msgstr "Найдено %d совпадений из %d групп" +msgstr "%d совпадений найдено из %d групп" #: core\gui\deletion_options.py:71 msgid "You are sending {} file(s) to the Trash." @@ -249,7 +249,7 @@ msgstr "%d / %d (%s / %s) дубликатов отмечено." #: core\results.py:142 msgid " filter: %s" -msgstr "фильтр: %s" +msgstr " фильтр: %s" #: core\scanner.py:114 msgid "Read metadata of %d/%d files" diff --git a/locale/ru/LC_MESSAGES/ui.po b/locale/ru/LC_MESSAGES/ui.po index c61b25ea..062bf782 100644 --- a/locale/ru/LC_MESSAGES/ui.po +++ b/locale/ru/LC_MESSAGES/ui.po @@ -93,8 +93,8 @@ msgid "" "Instead of sending files to trash, delete them directly. This option is " "usually used as a workaround when the normal deletion method doesn't work." msgstr "" -"Удалить файлы с диска вместо отправки в Корзину. Используйте если нормальный" -" метод удаления не работает." +"Удалить файлы с диска вместо отправки в Корзину. Используйте, если нормальный " +"метод удаления не работает." #: qt/deletion_options.py:59 cocoa/en.lproj/Localizable.strings:0 msgid "Proceed" @@ -189,7 +189,7 @@ msgstr "Несохранённые результаты" #: qt/directories_dialog.py:231 cocoa/en.lproj/Localizable.strings:0 msgid "You have unsaved results, do you really want to quit?" -msgstr "Имеются несохранённые результаты, вы действительно хотите выйти?" +msgstr "Имеются несохранённые результаты, Вы действительно хотите выйти?" #: qt/directories_dialog.py:239 cocoa/en.lproj/Localizable.strings:0 msgid "Select a folder to add to the scanning list" @@ -205,7 +205,7 @@ msgstr "Все файлы (*.*)" #: qt/directories_dialog.py:267 qt/result_window.py:311 msgid "dupeGuru Results (*.dupeguru)" -msgstr "Результаты dupeGuru (*. dupeguru)" +msgstr "Результаты dupeGuru (*.dupeguru)" #: qt/directories_dialog.py:278 msgid "Start a new scan" @@ -213,7 +213,7 @@ msgstr "Начать новую проверку" #: qt/directories_dialog.py:279 cocoa/en.lproj/Localizable.strings:0 msgid "You have unsaved results, do you really want to continue?" -msgstr "Имеются несохранённые результаты, вы действительно хотите продолжить?" +msgstr "Имеются несохранённые результаты, Вы действительно хотите продолжить?" #: qt/directories_model.py:23 cocoa/en.lproj/Localizable.strings:0 msgid "Name" @@ -256,7 +256,7 @@ msgstr "Теги для проверки:" #: qt/me/preferences_dialog.py:36 cocoa/en.lproj/Localizable.strings:0 msgid "Track" -msgstr "Трек" +msgstr "Дорожка" #: qt/me/preferences_dialog.py:38 cocoa/en.lproj/Localizable.strings:0 msgid "Artist" @@ -323,7 +323,7 @@ msgstr "Уровень фильтрации:" #: qt/preferences_dialog.py:69 msgid "More Results" -msgstr "Дополнительные результаты" +msgstr "Больше результатов" #: qt/preferences_dialog.py:74 msgid "Fewer Results" @@ -379,7 +379,7 @@ msgstr "" #: qt/problem_dialog.py:33 cocoa/en.lproj/Localizable.strings:0 msgid "Problems!" -msgstr "Проблемка!" +msgstr "Проблема!" #: qt/problem_dialog.py:37 cocoa/en.lproj/Localizable.strings:0 msgid "" @@ -409,15 +409,15 @@ msgstr "Показать значения разницы" #: qt/result_window.py:60 msgid "Send Marked to Recycle Bin..." -msgstr "Переместить отмеченные в Корзину…" +msgstr "Переместить отмеченные в Корзину..." #: qt/result_window.py:61 cocoa/en.lproj/Localizable.strings:0 msgid "Move Marked to..." -msgstr "Переместить отмеченные в…" +msgstr "Переместить отмеченные в..." #: qt/result_window.py:62 cocoa/en.lproj/Localizable.strings:0 msgid "Copy Marked to..." -msgstr "Скопировать отмеченные в…" +msgstr "Скопировать отмеченные в..." #: qt/result_window.py:63 cocoa/en.lproj/Localizable.strings:0 msgid "Remove Marked from Results" @@ -477,7 +477,7 @@ msgstr "Экспорт в CSV" #: qt/result_window.py:89 cocoa/en.lproj/Localizable.strings:0 msgid "Save Results..." -msgstr "Сохранить результаты…" +msgstr "Сохранить результаты..." #: qt/result_window.py:90 cocoa/en.lproj/Localizable.strings:0 msgid "Invoke Custom Command" @@ -509,15 +509,15 @@ msgstr "Значения разницы" #: qt/result_window.py:310 cocoa/en.lproj/Localizable.strings:0 msgid "Select a file to save your results to" -msgstr "Выберите файл, чтобы сохранить ваши результаты" +msgstr "Выберите файл для сохранения Ваших результатов" #: qt/se/preferences_dialog.py:41 msgid "Ignore files smaller than" -msgstr "Игнорировать файлы меньше чем" +msgstr "Игнорировать файлы меньше, чем" #: qt/se/preferences_dialog.py:52 cocoa/en.lproj/Localizable.strings:0 msgid "KB" -msgstr "КБ" +msgstr "кБ" #: cocoa/en.lproj/Localizable.strings:0 msgid "%@ Results" @@ -549,7 +549,7 @@ msgstr "Все на передний план" #: cocoa/en.lproj/Localizable.strings:0 msgid "Check for update..." -msgstr "Проверка обновлений…" +msgstr "Проверка обновлений..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Close Window" @@ -594,7 +594,7 @@ msgstr "Настройки dupeGuru" #: cocoa/en.lproj/Localizable.strings:0 msgid "dupeGuru Results" -msgstr "Результаты dupeGuru " +msgstr "Результаты dupeGuru" #: cocoa/en.lproj/Localizable.strings:0 msgid "dupeGuru Website" @@ -626,7 +626,7 @@ msgstr "Уровень фильтрации:" #: cocoa/en.lproj/Localizable.strings:0 msgid "Filter Results..." -msgstr "Отфильтровать результаты…" +msgstr "Отфильтровать результаты..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Folder Selection Window" @@ -646,11 +646,11 @@ msgstr "Скрыть остальные" #: cocoa/en.lproj/Localizable.strings:0 msgid "Ignore files smaller than:" -msgstr "Пропускать файлы меньше чем:" +msgstr "Пропускать файлы меньше, чем:" #: cocoa/en.lproj/Localizable.strings:0 msgid "Load from file..." -msgstr "Загрузить из файла…" +msgstr "Загрузить из файла..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Minimize" @@ -674,7 +674,7 @@ msgstr "Вставить" #: cocoa/en.lproj/Localizable.strings:0 msgid "Preferences..." -msgstr "Настройки…" +msgstr "Настройки..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Quick Look" @@ -686,7 +686,7 @@ msgstr "Выйти из dupeGuru" #: cocoa/en.lproj/Localizable.strings:0 msgid "Reset to Default" -msgstr "Восстановить значения по умолчанию" +msgstr "Восстановить значение по умолчанию" #: cocoa/en.lproj/Localizable.strings:0 msgid "Reset To Defaults" @@ -706,7 +706,7 @@ msgstr "Выбрать все" #: cocoa/en.lproj/Localizable.strings:0 msgid "Send Marked to Trash..." -msgstr "Переместить отмеченные в Корзину…" +msgstr "Переместить отмеченные в Корзину..." #: cocoa/en.lproj/Localizable.strings:0 msgid "Services" @@ -754,11 +754,11 @@ msgstr "Выберите файл каталогов для загрузки" #: qt\directories_dialog.py:338 msgid "dupeGuru Results (*.dupegurudirs)" -msgstr "Каталоги dupeGuru (*.dupegurudirs)" +msgstr "Результаты dupeGuru (*.dupegurudirs)" #: qt\directories_dialog.py:347 msgid "Select a file to save your directories to" -msgstr "Выберите файл для сохранения каталогов" +msgstr "Выберите файл для сохранения Ваших каталогов" #: qt\directories_dialog.py:348 msgid "dupeGuru Directories (*.dupegurudirs)" @@ -798,7 +798,7 @@ msgstr "" #: qt\exclude_list_table.py:36 msgid "Compilation error: " -msgstr "Ошибка компиляции:" +msgstr "Ошибка компиляции: " #: qt\pe\image_viewer.py:56 msgid "Increase zoom" @@ -835,7 +835,7 @@ msgstr "Переопределить значки темы на панели и #: qt\pe\preferences_dialog.py:58 msgid "" "Use our own internal icons instead of those provided by the theme engine" -msgstr "Используйте внутренние значки вместо значков, встроенных в тему" +msgstr "Использовать наши внутренние значки вместо значков, встроенных в тему" #: qt\pe\preferences_dialog.py:66 msgid "Show scrollbars in image viewers" @@ -965,7 +965,7 @@ msgstr "" #: qt\se\preferences_dialog.py:68 msgid "Ignore files larger than" -msgstr "Игнорировать файлы больше чем" +msgstr "Игнорировать файлы больше, чем" #: qt\app.py:135 qt\app.py:293 msgid "Clear Cache" @@ -981,7 +981,7 @@ msgstr "" #: qt\app.py:299 msgid "Cache cleared." -msgstr "Кэш очищен " +msgstr "Кэш очищен." #: qt\preferences_dialog.py:173 msgid "Use dark style" @@ -994,8 +994,7 @@ msgstr "Сохранить профиль сканирования" #: qt\preferences_dialog.py:242 msgid "Profile the scan operation and save logs for optimization." msgstr "" -"В папке установленной или портативной программы, есть папка Data в которую " -"сохраняется логи и файл с раширением *.profile для оптимизации." +"Настройте операцию сканирования и сохраните журналы для оптимизации." #: qt\preferences_dialog.py:246 msgid "Logs located in: {}" @@ -1023,11 +1022,11 @@ msgstr "Под лицензией GPLv3" #: qt\about_box.py:68 msgid "No update available." -msgstr "У вас самая свежая версия" +msgstr "Обновления недоступны." #: qt\about_box.py:71 msgid "New version {} available, download here." -msgstr "Обнаружена новая {} версия, загружать тут." +msgstr "Обнаружена новая {} версия, загрузить тут." #: qt\error_report_dialog.py:50 msgid "Error Report" From a6d6048c84ff59345cd43bb8e5892d7a89039e43 Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Sun, 2 Aug 2026 13:24:02 -0600 Subject: [PATCH 21/26] add dupeguru namespace to avoid naming conflicts --- Makefile | 14 +++++++------- help/conf.tmpl | 1 + setup.cfg | 29 ++++++++++++++++++++--------- setup.py | 20 ++++++++++---------- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 04e09407..e4f5fb24 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ endif # Our build scripts are not very "make like" yet and perform their task in a bundle. For now, we # use one of each file to act as a representative, a target, of these groups. -packages = hscommon core qt +packages = dupeguru/hscommon dupeguru/core dupeguru/qt localedirs = $(wildcard locale/*/LC_MESSAGES) pofiles = $(wildcard locale/*/LC_MESSAGES/*.po) mofiles = $(patsubst %.po,%.mo,$(pofiles)) @@ -43,7 +43,7 @@ mofiles = $(patsubst %.po,%.mo,$(pofiles)) vpath %.po $(localedirs) vpath %.mo $(localedirs) -all: | env i18n modules qt/dg_rc.py +all: | env i18n modules dupeguru/qt/dg_rc.py @echo "Build complete! You can run dupeGuru with 'make run'" run: @@ -76,10 +76,10 @@ endif build/help: | env $(VENV_PYTHON) build.py --doc -qt/dg_rc.py: qt/dg.qrc - $(RCC) -g python qt/dg.qrc -o qt/dg_rc.py - sed -i 's/from PySide2/from PyQt6/' qt/dg_rc.py - sed -i 's/from PySide6/from PyQt6/' qt/dg_rc.py +dupeguru/qt/dg_rc.py: dupeguru/qt/dg.qrc + $(RCC) -g python dupeguru/qt/dg.qrc -o dupeguru/qt/dg_rc.py + sed -i 's/from PySide2/from PyQt6/' dupeguru/qt/dg_rc.py + sed -i 's/from PySide6/from PyQt6/' dupeguru/qt/dg_rc.py i18n: $(mofiles) @@ -120,6 +120,6 @@ uninstall: clean: -rm -rf build -rm locale/*/LC_MESSAGES/*.mo - -rm core/pe/*.$(SO) qt/pe/*.$(SO) + -rm dupeguru/core/pe/*.$(SO) dupeguru/qt/pe/*.$(SO) .PHONY: clean normpo mergepot modules i18n reqs run pyc install uninstall all diff --git a/help/conf.tmpl b/help/conf.tmpl index 5878cbdc..b4c0e807 100644 --- a/help/conf.tmpl +++ b/help/conf.tmpl @@ -32,6 +32,7 @@ def setup(app): app.connect('autodoc-process-signature', fix_nulljob_in_sig) autodoc_member_order = 'groupwise' +autodoc_mock_imports = ["dupeguru.core.pe._block", "dupeguru.core.pe._cache", "dupeguru.qt.pe._block"] # -- General configuration ----------------------------------------------------- diff --git a/setup.cfg b/setup.cfg index 4c400731..c001b210 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,20 +1,19 @@ [metadata] name = dupeGuru -version = attr: core.__version__ +version = attr: dupeguru.core.__version__ url = https://github.com/arsenetar/dupeguru project_urls = Bug Reports = https://github.com/arsenetar/dupeguru/issues author = Andrew Senetar author_email = arsenetar@voltaicideas.net -license = GPLv3 -license_files = license +license = GPL-3.0-only +license_files = LICENSE description = dupeGuru is a tool to find duplicate files on your computer. long_description = file:README.md long_description_content_type = text/markdown classifiers = Development Status :: 5 - Production/Stable Intended Audience :: End Users/Desktop - License :: OSI Approved :: GNU General Public License v3 (GPLv3) Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Operating System :: POSIX @@ -26,7 +25,23 @@ classifiers = Topic :: Desktop Environment :: File Managers [options] -packages = find: +packages = + dupeguru.core + dupeguru.core.gui + dupeguru.core.me + dupeguru.core.pe + dupeguru.core.pe.modules + dupeguru.core.se + dupeguru.core.tests + dupeguru.hscommon + dupeguru.hscommon.gui + dupeguru.hscommon.jobprogress + dupeguru.hscommon.tests + dupeguru.qt + dupeguru.qt.me + dupeguru.qt.pe + dupeguru.qt.pe.modules + dupeguru.qt.se python_requires = >=3.7 install_requires = Send2Trash>=1.8.2,<2.0.0 @@ -42,7 +57,3 @@ setup_requires = tests_require = pytest >=6,<7 include_package_data = true - -[options.entry_points] -console_scripts = - dupeguru = run.py diff --git a/setup.py b/setup.py index 97cb61e3..e34dd746 100644 --- a/setup.py +++ b/setup.py @@ -3,24 +3,24 @@ exts = [ Extension( - "core.pe._block", + "dupeguru.core.pe._block", [ - str(Path("core", "pe", "modules", "block.c")), - str(Path("core", "pe", "modules", "common.c")), + str(Path("dupeguru", "core", "pe", "modules", "block.c")), + str(Path("dupeguru", "core", "pe", "modules", "common.c")), ], - include_dirs=[str(Path("core", "pe", "modules"))], + include_dirs=[str(Path("dupeguru", "core", "pe", "modules"))], ), Extension( - "core.pe._cache", + "dupeguru.core.pe._cache", [ - str(Path("core", "pe", "modules", "cache.c")), - str(Path("core", "pe", "modules", "common.c")), + str(Path("dupeguru", "core", "pe", "modules", "cache.c")), + str(Path("dupeguru", "core", "pe", "modules", "common.c")), ], - include_dirs=[str(Path("core", "pe", "modules"))], + include_dirs=[str(Path("dupeguru", "core", "pe", "modules"))], ), - Extension("qt.pe._block_qt", [str(Path("qt", "pe", "modules", "block.c"))]), + Extension("dupeguru.qt.pe._block_qt", [str(Path("dupeguru", "qt", "pe", "modules", "block.c"))]), ] -headers = [str(Path("core", "pe", "modules", "common.h"))] +headers = [str(Path("dupeguru", "core", "pe", "modules", "common.h"))] setup(ext_modules=exts, headers=headers) From 7a07a3d74a011e85b6a2ce744314b7ceb62588e6 Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Sun, 2 Aug 2026 13:24:02 -0600 Subject: [PATCH 22/26] force Qt6 to use the X11 interfaces since Fedora now runs wayland and under wayland, Qt6 application windows do not use the proper styling --- run.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/run.py b/run.py index 5fd48d95..07de6bea 100644 --- a/run.py +++ b/run.py @@ -7,6 +7,7 @@ import sys import os.path as op +import os import gc from PyQt6.QtCore import QDir @@ -31,6 +32,10 @@ global dgapp dgapp = None +# force Qt6 to use the X11 interfaces since Fedora now runs wayland +# and under wayland, Qt6 application window does not use the proper +# styling. +os.environ["QT_QPA_PLATFORM"] = "xcb" def signal_handler(sig, frame): global dgapp From 2a031c8fec258b3c4d89e3f29ccecf3132eb18fc Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Sun, 2 Aug 2026 13:24:02 -0600 Subject: [PATCH 23/26] allow send2trash 2.1.0 which is in f45 --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index fc3c4e03..291f68b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,6 @@ polib>=1.1.0,<2.0.0 PyQt6 >=6.3,<7.0 pywin32>=304; sys_platform == 'win32' semantic-version>=2.0.0,<3.0.0 -Send2Trash>=1.8.2,<2.0.0 +Send2Trash>=1.8.2,<3.0.0 sphinx>=5.3.0,<8.0.0 xxhash>=3.0.0,<4.0.0 diff --git a/setup.cfg b/setup.cfg index c001b210..2bef299e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,7 +44,7 @@ packages = dupeguru.qt.se python_requires = >=3.7 install_requires = - Send2Trash>=1.8.2,<2.0.0 + Send2Trash>=1.8.2,<3.0.0 mutagen>=1.46.0,<2.0.0 distro>=1.8.0,<2.0.0 PyQt6 >=6.3.0,<7.0; sys_platform != 'linux' From cf4d0b4eb850abf4aa4746ea90c7bbbdfdebbe0d Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Sun, 2 Aug 2026 13:52:28 -0600 Subject: [PATCH 24/26] move core,hscommon,qt down one level --- {core => dupeguru/core}/__init__.py | 0 {core => dupeguru/core}/app.py | 0 {core => dupeguru/core}/directories.py | 0 {core => dupeguru/core}/engine.py | 0 {core => dupeguru/core}/exclude.py | 0 {core => dupeguru/core}/export.py | 0 {core => dupeguru/core}/fs.py | 0 {core => dupeguru/core}/gui/__init__.py | 0 {core => dupeguru/core}/gui/base.py | 0 {core => dupeguru/core}/gui/deletion_options.py | 0 {core => dupeguru/core}/gui/details_panel.py | 0 {core => dupeguru/core}/gui/directory_tree.py | 0 {core => dupeguru/core}/gui/exclude_list_dialog.py | 0 {core => dupeguru/core}/gui/exclude_list_table.py | 0 {core => dupeguru/core}/gui/ignore_list_dialog.py | 0 {core => dupeguru/core}/gui/ignore_list_table.py | 0 {core => dupeguru/core}/gui/prioritize_dialog.py | 0 {core => dupeguru/core}/gui/problem_dialog.py | 0 {core => dupeguru/core}/gui/problem_table.py | 0 {core => dupeguru/core}/gui/result_table.py | 0 {core => dupeguru/core}/gui/stats_label.py | 0 {core => dupeguru/core}/ignore.py | 0 {core => dupeguru/core}/markable.py | 0 {core => dupeguru/core}/me/__init__.py | 0 {core => dupeguru/core}/me/fs.py | 0 {core => dupeguru/core}/me/prioritize.py | 0 {core => dupeguru/core}/me/result_table.py | 0 {core => dupeguru/core}/me/scanner.py | 0 {core => dupeguru/core}/pe/__init__.py | 0 {core => dupeguru/core}/pe/block.py | 0 {core => dupeguru/core}/pe/block.pyi | 0 {core => dupeguru/core}/pe/cache.py | 0 {core => dupeguru/core}/pe/cache.pyi | 0 {core => dupeguru/core}/pe/cache_sqlite.py | 0 {core => dupeguru/core}/pe/exif.py | 0 {core => dupeguru/core}/pe/matchblock.py | 0 {core => dupeguru/core}/pe/matchexif.py | 0 {core => dupeguru/core}/pe/modules/block.c | 0 {core => dupeguru/core}/pe/modules/block_osx.m | 0 {core => dupeguru/core}/pe/modules/cache.c | 0 {core => dupeguru/core}/pe/modules/common.c | 0 {core => dupeguru/core}/pe/modules/common.h | 0 {core => dupeguru/core}/pe/photo.py | 0 {core => dupeguru/core}/pe/prioritize.py | 0 {core => dupeguru/core}/pe/result_table.py | 0 {core => dupeguru/core}/pe/scanner.py | 0 {core => dupeguru/core}/prioritize.py | 0 {core => dupeguru/core}/results.py | 0 {core => dupeguru/core}/scanner.py | 0 {core => dupeguru/core}/se/__init__.py | 0 {core => dupeguru/core}/se/fs.py | 0 {core => dupeguru/core}/se/result_table.py | 0 {core => dupeguru/core}/se/scanner.py | 0 {core => dupeguru/core}/tests/__init__.py | 0 {core => dupeguru/core}/tests/app_test.py | 0 {core => dupeguru/core}/tests/base.py | 0 {core => dupeguru/core}/tests/block_test.py | 0 {core => dupeguru/core}/tests/cache_test.py | 0 {core => dupeguru/core}/tests/conftest.py | 0 {core => dupeguru/core}/tests/directories_test.py | 0 {core => dupeguru/core}/tests/engine_test.py | 0 {core => dupeguru/core}/tests/exclude_test.py | 0 {core => dupeguru/core}/tests/fs_test.py | 0 {core => dupeguru/core}/tests/ignore_test.py | 0 {core => dupeguru/core}/tests/markable_test.py | 0 {core => dupeguru/core}/tests/prioritize_test.py | 0 {core => dupeguru/core}/tests/result_table_test.py | 0 {core => dupeguru/core}/tests/results_test.py | 0 {core => dupeguru/core}/tests/scanner_test.py | 0 {core => dupeguru/core}/util.py | 0 {hscommon => dupeguru/hscommon}/LICENSE | 0 {hscommon => dupeguru/hscommon}/README | 0 {hscommon => dupeguru/hscommon}/__init__.py | 0 {hscommon => dupeguru/hscommon}/build.py | 0 {hscommon => dupeguru/hscommon}/conflict.py | 0 {hscommon => dupeguru/hscommon}/desktop.py | 0 {hscommon => dupeguru/hscommon}/gui/__init__.py | 0 {hscommon => dupeguru/hscommon}/gui/base.py | 0 {hscommon => dupeguru/hscommon}/gui/column.py | 0 {hscommon => dupeguru/hscommon}/gui/progress_window.py | 0 {hscommon => dupeguru/hscommon}/gui/selectable_list.py | 0 {hscommon => dupeguru/hscommon}/gui/table.py | 0 {hscommon => dupeguru/hscommon}/gui/text_field.py | 0 {hscommon => dupeguru/hscommon}/gui/tree.py | 0 {hscommon => dupeguru/hscommon}/jobprogress/__init__.py | 0 {hscommon => dupeguru/hscommon}/jobprogress/job.py | 0 {hscommon => dupeguru/hscommon}/jobprogress/performer.py | 0 {hscommon => dupeguru/hscommon}/loc.py | 0 {hscommon => dupeguru/hscommon}/notify.py | 0 {hscommon => dupeguru/hscommon}/path.py | 0 {hscommon => dupeguru/hscommon}/plat.py | 0 {hscommon => dupeguru/hscommon}/pygettext.py | 0 {hscommon => dupeguru/hscommon}/sphinxgen.py | 0 {hscommon => dupeguru/hscommon}/tests/__init__.py | 0 {hscommon => dupeguru/hscommon}/tests/conflict_test.py | 0 {hscommon => dupeguru/hscommon}/tests/notify_test.py | 0 {hscommon => dupeguru/hscommon}/tests/path_test.py | 0 {hscommon => dupeguru/hscommon}/tests/selectable_list_test.py | 0 {hscommon => dupeguru/hscommon}/tests/table_test.py | 0 {hscommon => dupeguru/hscommon}/tests/tree_test.py | 0 {hscommon => dupeguru/hscommon}/tests/util_test.py | 0 {hscommon => dupeguru/hscommon}/testutil.py | 0 {hscommon => dupeguru/hscommon}/trans.py | 0 {hscommon => dupeguru/hscommon}/util.py | 0 {qt => dupeguru/qt}/__init__.py | 0 {qt => dupeguru/qt}/about_box.py | 0 {qt => dupeguru/qt}/app.py | 0 {qt => dupeguru/qt}/column.py | 0 {qt => dupeguru/qt}/deletion_options.py | 0 {qt => dupeguru/qt}/details_dialog.py | 0 {qt => dupeguru/qt}/details_table.py | 0 {qt => dupeguru/qt}/dg.qrc | 0 {qt => dupeguru/qt}/directories_dialog.py | 0 {qt => dupeguru/qt}/directories_model.py | 0 {qt => dupeguru/qt}/error_report_dialog.py | 0 {qt => dupeguru/qt}/exclude_list_dialog.py | 0 {qt => dupeguru/qt}/exclude_list_table.py | 0 {qt => dupeguru/qt}/ignore_list_dialog.py | 0 {qt => dupeguru/qt}/ignore_list_table.py | 0 {qt => dupeguru/qt}/me/__init__.py | 0 {qt => dupeguru/qt}/me/details_dialog.py | 0 {qt => dupeguru/qt}/me/preferences_dialog.py | 0 {qt => dupeguru/qt}/me/results_model.py | 0 {qt => dupeguru/qt}/pe/__init__.py | 0 {qt => dupeguru/qt}/pe/block.py | 0 {qt => dupeguru/qt}/pe/block.pyi | 0 {qt => dupeguru/qt}/pe/details_dialog.py | 0 {qt => dupeguru/qt}/pe/image_viewer.py | 0 {qt => dupeguru/qt}/pe/modules/block.c | 0 {qt => dupeguru/qt}/pe/photo.py | 0 {qt => dupeguru/qt}/pe/preferences_dialog.py | 0 {qt => dupeguru/qt}/pe/results_model.py | 0 {qt => dupeguru/qt}/platform.py | 0 {qt => dupeguru/qt}/preferences.py | 0 {qt => dupeguru/qt}/preferences_dialog.py | 0 {qt => dupeguru/qt}/prioritize_dialog.py | 0 {qt => dupeguru/qt}/problem_dialog.py | 0 {qt => dupeguru/qt}/problem_table.py | 0 {qt => dupeguru/qt}/progress_window.py | 0 {qt => dupeguru/qt}/radio_box.py | 0 {qt => dupeguru/qt}/recent.py | 0 {qt => dupeguru/qt}/result_window.py | 0 {qt => dupeguru/qt}/results_model.py | 0 {qt => dupeguru/qt}/se/__init__.py | 0 {qt => dupeguru/qt}/se/details_dialog.py | 0 {qt => dupeguru/qt}/se/preferences_dialog.py | 0 {qt => dupeguru/qt}/se/results_model.py | 0 {qt => dupeguru/qt}/search_edit.py | 0 {qt => dupeguru/qt}/selectable_list.py | 0 {qt => dupeguru/qt}/stats_label.py | 0 {qt => dupeguru/qt}/tabbed_window.py | 0 {qt => dupeguru/qt}/table.py | 0 {qt => dupeguru/qt}/tree_model.py | 0 {qt => dupeguru/qt}/util.py | 0 154 files changed, 0 insertions(+), 0 deletions(-) rename {core => dupeguru/core}/__init__.py (100%) rename {core => dupeguru/core}/app.py (100%) rename {core => dupeguru/core}/directories.py (100%) rename {core => dupeguru/core}/engine.py (100%) rename {core => dupeguru/core}/exclude.py (100%) rename {core => dupeguru/core}/export.py (100%) rename {core => dupeguru/core}/fs.py (100%) rename {core => dupeguru/core}/gui/__init__.py (100%) rename {core => dupeguru/core}/gui/base.py (100%) rename {core => dupeguru/core}/gui/deletion_options.py (100%) rename {core => dupeguru/core}/gui/details_panel.py (100%) rename {core => dupeguru/core}/gui/directory_tree.py (100%) rename {core => dupeguru/core}/gui/exclude_list_dialog.py (100%) rename {core => dupeguru/core}/gui/exclude_list_table.py (100%) rename {core => dupeguru/core}/gui/ignore_list_dialog.py (100%) rename {core => dupeguru/core}/gui/ignore_list_table.py (100%) rename {core => dupeguru/core}/gui/prioritize_dialog.py (100%) rename {core => dupeguru/core}/gui/problem_dialog.py (100%) rename {core => dupeguru/core}/gui/problem_table.py (100%) rename {core => dupeguru/core}/gui/result_table.py (100%) rename {core => dupeguru/core}/gui/stats_label.py (100%) rename {core => dupeguru/core}/ignore.py (100%) rename {core => dupeguru/core}/markable.py (100%) rename {core => dupeguru/core}/me/__init__.py (100%) rename {core => dupeguru/core}/me/fs.py (100%) rename {core => dupeguru/core}/me/prioritize.py (100%) rename {core => dupeguru/core}/me/result_table.py (100%) rename {core => dupeguru/core}/me/scanner.py (100%) rename {core => dupeguru/core}/pe/__init__.py (100%) rename {core => dupeguru/core}/pe/block.py (100%) rename {core => dupeguru/core}/pe/block.pyi (100%) rename {core => dupeguru/core}/pe/cache.py (100%) rename {core => dupeguru/core}/pe/cache.pyi (100%) rename {core => dupeguru/core}/pe/cache_sqlite.py (100%) rename {core => dupeguru/core}/pe/exif.py (100%) rename {core => dupeguru/core}/pe/matchblock.py (100%) rename {core => dupeguru/core}/pe/matchexif.py (100%) rename {core => dupeguru/core}/pe/modules/block.c (100%) rename {core => dupeguru/core}/pe/modules/block_osx.m (100%) rename {core => dupeguru/core}/pe/modules/cache.c (100%) rename {core => dupeguru/core}/pe/modules/common.c (100%) rename {core => dupeguru/core}/pe/modules/common.h (100%) rename {core => dupeguru/core}/pe/photo.py (100%) rename {core => dupeguru/core}/pe/prioritize.py (100%) rename {core => dupeguru/core}/pe/result_table.py (100%) rename {core => dupeguru/core}/pe/scanner.py (100%) rename {core => dupeguru/core}/prioritize.py (100%) rename {core => dupeguru/core}/results.py (100%) rename {core => dupeguru/core}/scanner.py (100%) rename {core => dupeguru/core}/se/__init__.py (100%) rename {core => dupeguru/core}/se/fs.py (100%) rename {core => dupeguru/core}/se/result_table.py (100%) rename {core => dupeguru/core}/se/scanner.py (100%) rename {core => dupeguru/core}/tests/__init__.py (100%) rename {core => dupeguru/core}/tests/app_test.py (100%) rename {core => dupeguru/core}/tests/base.py (100%) rename {core => dupeguru/core}/tests/block_test.py (100%) rename {core => dupeguru/core}/tests/cache_test.py (100%) rename {core => dupeguru/core}/tests/conftest.py (100%) rename {core => dupeguru/core}/tests/directories_test.py (100%) rename {core => dupeguru/core}/tests/engine_test.py (100%) rename {core => dupeguru/core}/tests/exclude_test.py (100%) rename {core => dupeguru/core}/tests/fs_test.py (100%) rename {core => dupeguru/core}/tests/ignore_test.py (100%) rename {core => dupeguru/core}/tests/markable_test.py (100%) rename {core => dupeguru/core}/tests/prioritize_test.py (100%) rename {core => dupeguru/core}/tests/result_table_test.py (100%) rename {core => dupeguru/core}/tests/results_test.py (100%) rename {core => dupeguru/core}/tests/scanner_test.py (100%) rename {core => dupeguru/core}/util.py (100%) rename {hscommon => dupeguru/hscommon}/LICENSE (100%) rename {hscommon => dupeguru/hscommon}/README (100%) rename {hscommon => dupeguru/hscommon}/__init__.py (100%) rename {hscommon => dupeguru/hscommon}/build.py (100%) rename {hscommon => dupeguru/hscommon}/conflict.py (100%) rename {hscommon => dupeguru/hscommon}/desktop.py (100%) rename {hscommon => dupeguru/hscommon}/gui/__init__.py (100%) rename {hscommon => dupeguru/hscommon}/gui/base.py (100%) rename {hscommon => dupeguru/hscommon}/gui/column.py (100%) rename {hscommon => dupeguru/hscommon}/gui/progress_window.py (100%) rename {hscommon => dupeguru/hscommon}/gui/selectable_list.py (100%) rename {hscommon => dupeguru/hscommon}/gui/table.py (100%) rename {hscommon => dupeguru/hscommon}/gui/text_field.py (100%) rename {hscommon => dupeguru/hscommon}/gui/tree.py (100%) rename {hscommon => dupeguru/hscommon}/jobprogress/__init__.py (100%) rename {hscommon => dupeguru/hscommon}/jobprogress/job.py (100%) rename {hscommon => dupeguru/hscommon}/jobprogress/performer.py (100%) rename {hscommon => dupeguru/hscommon}/loc.py (100%) rename {hscommon => dupeguru/hscommon}/notify.py (100%) rename {hscommon => dupeguru/hscommon}/path.py (100%) rename {hscommon => dupeguru/hscommon}/plat.py (100%) rename {hscommon => dupeguru/hscommon}/pygettext.py (100%) rename {hscommon => dupeguru/hscommon}/sphinxgen.py (100%) rename {hscommon => dupeguru/hscommon}/tests/__init__.py (100%) rename {hscommon => dupeguru/hscommon}/tests/conflict_test.py (100%) rename {hscommon => dupeguru/hscommon}/tests/notify_test.py (100%) rename {hscommon => dupeguru/hscommon}/tests/path_test.py (100%) rename {hscommon => dupeguru/hscommon}/tests/selectable_list_test.py (100%) rename {hscommon => dupeguru/hscommon}/tests/table_test.py (100%) rename {hscommon => dupeguru/hscommon}/tests/tree_test.py (100%) rename {hscommon => dupeguru/hscommon}/tests/util_test.py (100%) rename {hscommon => dupeguru/hscommon}/testutil.py (100%) rename {hscommon => dupeguru/hscommon}/trans.py (100%) rename {hscommon => dupeguru/hscommon}/util.py (100%) rename {qt => dupeguru/qt}/__init__.py (100%) rename {qt => dupeguru/qt}/about_box.py (100%) rename {qt => dupeguru/qt}/app.py (100%) rename {qt => dupeguru/qt}/column.py (100%) rename {qt => dupeguru/qt}/deletion_options.py (100%) rename {qt => dupeguru/qt}/details_dialog.py (100%) rename {qt => dupeguru/qt}/details_table.py (100%) rename {qt => dupeguru/qt}/dg.qrc (100%) rename {qt => dupeguru/qt}/directories_dialog.py (100%) rename {qt => dupeguru/qt}/directories_model.py (100%) rename {qt => dupeguru/qt}/error_report_dialog.py (100%) rename {qt => dupeguru/qt}/exclude_list_dialog.py (100%) rename {qt => dupeguru/qt}/exclude_list_table.py (100%) rename {qt => dupeguru/qt}/ignore_list_dialog.py (100%) rename {qt => dupeguru/qt}/ignore_list_table.py (100%) rename {qt => dupeguru/qt}/me/__init__.py (100%) rename {qt => dupeguru/qt}/me/details_dialog.py (100%) rename {qt => dupeguru/qt}/me/preferences_dialog.py (100%) rename {qt => dupeguru/qt}/me/results_model.py (100%) rename {qt => dupeguru/qt}/pe/__init__.py (100%) rename {qt => dupeguru/qt}/pe/block.py (100%) rename {qt => dupeguru/qt}/pe/block.pyi (100%) rename {qt => dupeguru/qt}/pe/details_dialog.py (100%) rename {qt => dupeguru/qt}/pe/image_viewer.py (100%) rename {qt => dupeguru/qt}/pe/modules/block.c (100%) rename {qt => dupeguru/qt}/pe/photo.py (100%) rename {qt => dupeguru/qt}/pe/preferences_dialog.py (100%) rename {qt => dupeguru/qt}/pe/results_model.py (100%) rename {qt => dupeguru/qt}/platform.py (100%) rename {qt => dupeguru/qt}/preferences.py (100%) rename {qt => dupeguru/qt}/preferences_dialog.py (100%) rename {qt => dupeguru/qt}/prioritize_dialog.py (100%) rename {qt => dupeguru/qt}/problem_dialog.py (100%) rename {qt => dupeguru/qt}/problem_table.py (100%) rename {qt => dupeguru/qt}/progress_window.py (100%) rename {qt => dupeguru/qt}/radio_box.py (100%) rename {qt => dupeguru/qt}/recent.py (100%) rename {qt => dupeguru/qt}/result_window.py (100%) rename {qt => dupeguru/qt}/results_model.py (100%) rename {qt => dupeguru/qt}/se/__init__.py (100%) rename {qt => dupeguru/qt}/se/details_dialog.py (100%) rename {qt => dupeguru/qt}/se/preferences_dialog.py (100%) rename {qt => dupeguru/qt}/se/results_model.py (100%) rename {qt => dupeguru/qt}/search_edit.py (100%) rename {qt => dupeguru/qt}/selectable_list.py (100%) rename {qt => dupeguru/qt}/stats_label.py (100%) rename {qt => dupeguru/qt}/tabbed_window.py (100%) rename {qt => dupeguru/qt}/table.py (100%) rename {qt => dupeguru/qt}/tree_model.py (100%) rename {qt => dupeguru/qt}/util.py (100%) diff --git a/core/__init__.py b/dupeguru/core/__init__.py similarity index 100% rename from core/__init__.py rename to dupeguru/core/__init__.py diff --git a/core/app.py b/dupeguru/core/app.py similarity index 100% rename from core/app.py rename to dupeguru/core/app.py diff --git a/core/directories.py b/dupeguru/core/directories.py similarity index 100% rename from core/directories.py rename to dupeguru/core/directories.py diff --git a/core/engine.py b/dupeguru/core/engine.py similarity index 100% rename from core/engine.py rename to dupeguru/core/engine.py diff --git a/core/exclude.py b/dupeguru/core/exclude.py similarity index 100% rename from core/exclude.py rename to dupeguru/core/exclude.py diff --git a/core/export.py b/dupeguru/core/export.py similarity index 100% rename from core/export.py rename to dupeguru/core/export.py diff --git a/core/fs.py b/dupeguru/core/fs.py similarity index 100% rename from core/fs.py rename to dupeguru/core/fs.py diff --git a/core/gui/__init__.py b/dupeguru/core/gui/__init__.py similarity index 100% rename from core/gui/__init__.py rename to dupeguru/core/gui/__init__.py diff --git a/core/gui/base.py b/dupeguru/core/gui/base.py similarity index 100% rename from core/gui/base.py rename to dupeguru/core/gui/base.py diff --git a/core/gui/deletion_options.py b/dupeguru/core/gui/deletion_options.py similarity index 100% rename from core/gui/deletion_options.py rename to dupeguru/core/gui/deletion_options.py diff --git a/core/gui/details_panel.py b/dupeguru/core/gui/details_panel.py similarity index 100% rename from core/gui/details_panel.py rename to dupeguru/core/gui/details_panel.py diff --git a/core/gui/directory_tree.py b/dupeguru/core/gui/directory_tree.py similarity index 100% rename from core/gui/directory_tree.py rename to dupeguru/core/gui/directory_tree.py diff --git a/core/gui/exclude_list_dialog.py b/dupeguru/core/gui/exclude_list_dialog.py similarity index 100% rename from core/gui/exclude_list_dialog.py rename to dupeguru/core/gui/exclude_list_dialog.py diff --git a/core/gui/exclude_list_table.py b/dupeguru/core/gui/exclude_list_table.py similarity index 100% rename from core/gui/exclude_list_table.py rename to dupeguru/core/gui/exclude_list_table.py diff --git a/core/gui/ignore_list_dialog.py b/dupeguru/core/gui/ignore_list_dialog.py similarity index 100% rename from core/gui/ignore_list_dialog.py rename to dupeguru/core/gui/ignore_list_dialog.py diff --git a/core/gui/ignore_list_table.py b/dupeguru/core/gui/ignore_list_table.py similarity index 100% rename from core/gui/ignore_list_table.py rename to dupeguru/core/gui/ignore_list_table.py diff --git a/core/gui/prioritize_dialog.py b/dupeguru/core/gui/prioritize_dialog.py similarity index 100% rename from core/gui/prioritize_dialog.py rename to dupeguru/core/gui/prioritize_dialog.py diff --git a/core/gui/problem_dialog.py b/dupeguru/core/gui/problem_dialog.py similarity index 100% rename from core/gui/problem_dialog.py rename to dupeguru/core/gui/problem_dialog.py diff --git a/core/gui/problem_table.py b/dupeguru/core/gui/problem_table.py similarity index 100% rename from core/gui/problem_table.py rename to dupeguru/core/gui/problem_table.py diff --git a/core/gui/result_table.py b/dupeguru/core/gui/result_table.py similarity index 100% rename from core/gui/result_table.py rename to dupeguru/core/gui/result_table.py diff --git a/core/gui/stats_label.py b/dupeguru/core/gui/stats_label.py similarity index 100% rename from core/gui/stats_label.py rename to dupeguru/core/gui/stats_label.py diff --git a/core/ignore.py b/dupeguru/core/ignore.py similarity index 100% rename from core/ignore.py rename to dupeguru/core/ignore.py diff --git a/core/markable.py b/dupeguru/core/markable.py similarity index 100% rename from core/markable.py rename to dupeguru/core/markable.py diff --git a/core/me/__init__.py b/dupeguru/core/me/__init__.py similarity index 100% rename from core/me/__init__.py rename to dupeguru/core/me/__init__.py diff --git a/core/me/fs.py b/dupeguru/core/me/fs.py similarity index 100% rename from core/me/fs.py rename to dupeguru/core/me/fs.py diff --git a/core/me/prioritize.py b/dupeguru/core/me/prioritize.py similarity index 100% rename from core/me/prioritize.py rename to dupeguru/core/me/prioritize.py diff --git a/core/me/result_table.py b/dupeguru/core/me/result_table.py similarity index 100% rename from core/me/result_table.py rename to dupeguru/core/me/result_table.py diff --git a/core/me/scanner.py b/dupeguru/core/me/scanner.py similarity index 100% rename from core/me/scanner.py rename to dupeguru/core/me/scanner.py diff --git a/core/pe/__init__.py b/dupeguru/core/pe/__init__.py similarity index 100% rename from core/pe/__init__.py rename to dupeguru/core/pe/__init__.py diff --git a/core/pe/block.py b/dupeguru/core/pe/block.py similarity index 100% rename from core/pe/block.py rename to dupeguru/core/pe/block.py diff --git a/core/pe/block.pyi b/dupeguru/core/pe/block.pyi similarity index 100% rename from core/pe/block.pyi rename to dupeguru/core/pe/block.pyi diff --git a/core/pe/cache.py b/dupeguru/core/pe/cache.py similarity index 100% rename from core/pe/cache.py rename to dupeguru/core/pe/cache.py diff --git a/core/pe/cache.pyi b/dupeguru/core/pe/cache.pyi similarity index 100% rename from core/pe/cache.pyi rename to dupeguru/core/pe/cache.pyi diff --git a/core/pe/cache_sqlite.py b/dupeguru/core/pe/cache_sqlite.py similarity index 100% rename from core/pe/cache_sqlite.py rename to dupeguru/core/pe/cache_sqlite.py diff --git a/core/pe/exif.py b/dupeguru/core/pe/exif.py similarity index 100% rename from core/pe/exif.py rename to dupeguru/core/pe/exif.py diff --git a/core/pe/matchblock.py b/dupeguru/core/pe/matchblock.py similarity index 100% rename from core/pe/matchblock.py rename to dupeguru/core/pe/matchblock.py diff --git a/core/pe/matchexif.py b/dupeguru/core/pe/matchexif.py similarity index 100% rename from core/pe/matchexif.py rename to dupeguru/core/pe/matchexif.py diff --git a/core/pe/modules/block.c b/dupeguru/core/pe/modules/block.c similarity index 100% rename from core/pe/modules/block.c rename to dupeguru/core/pe/modules/block.c diff --git a/core/pe/modules/block_osx.m b/dupeguru/core/pe/modules/block_osx.m similarity index 100% rename from core/pe/modules/block_osx.m rename to dupeguru/core/pe/modules/block_osx.m diff --git a/core/pe/modules/cache.c b/dupeguru/core/pe/modules/cache.c similarity index 100% rename from core/pe/modules/cache.c rename to dupeguru/core/pe/modules/cache.c diff --git a/core/pe/modules/common.c b/dupeguru/core/pe/modules/common.c similarity index 100% rename from core/pe/modules/common.c rename to dupeguru/core/pe/modules/common.c diff --git a/core/pe/modules/common.h b/dupeguru/core/pe/modules/common.h similarity index 100% rename from core/pe/modules/common.h rename to dupeguru/core/pe/modules/common.h diff --git a/core/pe/photo.py b/dupeguru/core/pe/photo.py similarity index 100% rename from core/pe/photo.py rename to dupeguru/core/pe/photo.py diff --git a/core/pe/prioritize.py b/dupeguru/core/pe/prioritize.py similarity index 100% rename from core/pe/prioritize.py rename to dupeguru/core/pe/prioritize.py diff --git a/core/pe/result_table.py b/dupeguru/core/pe/result_table.py similarity index 100% rename from core/pe/result_table.py rename to dupeguru/core/pe/result_table.py diff --git a/core/pe/scanner.py b/dupeguru/core/pe/scanner.py similarity index 100% rename from core/pe/scanner.py rename to dupeguru/core/pe/scanner.py diff --git a/core/prioritize.py b/dupeguru/core/prioritize.py similarity index 100% rename from core/prioritize.py rename to dupeguru/core/prioritize.py diff --git a/core/results.py b/dupeguru/core/results.py similarity index 100% rename from core/results.py rename to dupeguru/core/results.py diff --git a/core/scanner.py b/dupeguru/core/scanner.py similarity index 100% rename from core/scanner.py rename to dupeguru/core/scanner.py diff --git a/core/se/__init__.py b/dupeguru/core/se/__init__.py similarity index 100% rename from core/se/__init__.py rename to dupeguru/core/se/__init__.py diff --git a/core/se/fs.py b/dupeguru/core/se/fs.py similarity index 100% rename from core/se/fs.py rename to dupeguru/core/se/fs.py diff --git a/core/se/result_table.py b/dupeguru/core/se/result_table.py similarity index 100% rename from core/se/result_table.py rename to dupeguru/core/se/result_table.py diff --git a/core/se/scanner.py b/dupeguru/core/se/scanner.py similarity index 100% rename from core/se/scanner.py rename to dupeguru/core/se/scanner.py diff --git a/core/tests/__init__.py b/dupeguru/core/tests/__init__.py similarity index 100% rename from core/tests/__init__.py rename to dupeguru/core/tests/__init__.py diff --git a/core/tests/app_test.py b/dupeguru/core/tests/app_test.py similarity index 100% rename from core/tests/app_test.py rename to dupeguru/core/tests/app_test.py diff --git a/core/tests/base.py b/dupeguru/core/tests/base.py similarity index 100% rename from core/tests/base.py rename to dupeguru/core/tests/base.py diff --git a/core/tests/block_test.py b/dupeguru/core/tests/block_test.py similarity index 100% rename from core/tests/block_test.py rename to dupeguru/core/tests/block_test.py diff --git a/core/tests/cache_test.py b/dupeguru/core/tests/cache_test.py similarity index 100% rename from core/tests/cache_test.py rename to dupeguru/core/tests/cache_test.py diff --git a/core/tests/conftest.py b/dupeguru/core/tests/conftest.py similarity index 100% rename from core/tests/conftest.py rename to dupeguru/core/tests/conftest.py diff --git a/core/tests/directories_test.py b/dupeguru/core/tests/directories_test.py similarity index 100% rename from core/tests/directories_test.py rename to dupeguru/core/tests/directories_test.py diff --git a/core/tests/engine_test.py b/dupeguru/core/tests/engine_test.py similarity index 100% rename from core/tests/engine_test.py rename to dupeguru/core/tests/engine_test.py diff --git a/core/tests/exclude_test.py b/dupeguru/core/tests/exclude_test.py similarity index 100% rename from core/tests/exclude_test.py rename to dupeguru/core/tests/exclude_test.py diff --git a/core/tests/fs_test.py b/dupeguru/core/tests/fs_test.py similarity index 100% rename from core/tests/fs_test.py rename to dupeguru/core/tests/fs_test.py diff --git a/core/tests/ignore_test.py b/dupeguru/core/tests/ignore_test.py similarity index 100% rename from core/tests/ignore_test.py rename to dupeguru/core/tests/ignore_test.py diff --git a/core/tests/markable_test.py b/dupeguru/core/tests/markable_test.py similarity index 100% rename from core/tests/markable_test.py rename to dupeguru/core/tests/markable_test.py diff --git a/core/tests/prioritize_test.py b/dupeguru/core/tests/prioritize_test.py similarity index 100% rename from core/tests/prioritize_test.py rename to dupeguru/core/tests/prioritize_test.py diff --git a/core/tests/result_table_test.py b/dupeguru/core/tests/result_table_test.py similarity index 100% rename from core/tests/result_table_test.py rename to dupeguru/core/tests/result_table_test.py diff --git a/core/tests/results_test.py b/dupeguru/core/tests/results_test.py similarity index 100% rename from core/tests/results_test.py rename to dupeguru/core/tests/results_test.py diff --git a/core/tests/scanner_test.py b/dupeguru/core/tests/scanner_test.py similarity index 100% rename from core/tests/scanner_test.py rename to dupeguru/core/tests/scanner_test.py diff --git a/core/util.py b/dupeguru/core/util.py similarity index 100% rename from core/util.py rename to dupeguru/core/util.py diff --git a/hscommon/LICENSE b/dupeguru/hscommon/LICENSE similarity index 100% rename from hscommon/LICENSE rename to dupeguru/hscommon/LICENSE diff --git a/hscommon/README b/dupeguru/hscommon/README similarity index 100% rename from hscommon/README rename to dupeguru/hscommon/README diff --git a/hscommon/__init__.py b/dupeguru/hscommon/__init__.py similarity index 100% rename from hscommon/__init__.py rename to dupeguru/hscommon/__init__.py diff --git a/hscommon/build.py b/dupeguru/hscommon/build.py similarity index 100% rename from hscommon/build.py rename to dupeguru/hscommon/build.py diff --git a/hscommon/conflict.py b/dupeguru/hscommon/conflict.py similarity index 100% rename from hscommon/conflict.py rename to dupeguru/hscommon/conflict.py diff --git a/hscommon/desktop.py b/dupeguru/hscommon/desktop.py similarity index 100% rename from hscommon/desktop.py rename to dupeguru/hscommon/desktop.py diff --git a/hscommon/gui/__init__.py b/dupeguru/hscommon/gui/__init__.py similarity index 100% rename from hscommon/gui/__init__.py rename to dupeguru/hscommon/gui/__init__.py diff --git a/hscommon/gui/base.py b/dupeguru/hscommon/gui/base.py similarity index 100% rename from hscommon/gui/base.py rename to dupeguru/hscommon/gui/base.py diff --git a/hscommon/gui/column.py b/dupeguru/hscommon/gui/column.py similarity index 100% rename from hscommon/gui/column.py rename to dupeguru/hscommon/gui/column.py diff --git a/hscommon/gui/progress_window.py b/dupeguru/hscommon/gui/progress_window.py similarity index 100% rename from hscommon/gui/progress_window.py rename to dupeguru/hscommon/gui/progress_window.py diff --git a/hscommon/gui/selectable_list.py b/dupeguru/hscommon/gui/selectable_list.py similarity index 100% rename from hscommon/gui/selectable_list.py rename to dupeguru/hscommon/gui/selectable_list.py diff --git a/hscommon/gui/table.py b/dupeguru/hscommon/gui/table.py similarity index 100% rename from hscommon/gui/table.py rename to dupeguru/hscommon/gui/table.py diff --git a/hscommon/gui/text_field.py b/dupeguru/hscommon/gui/text_field.py similarity index 100% rename from hscommon/gui/text_field.py rename to dupeguru/hscommon/gui/text_field.py diff --git a/hscommon/gui/tree.py b/dupeguru/hscommon/gui/tree.py similarity index 100% rename from hscommon/gui/tree.py rename to dupeguru/hscommon/gui/tree.py diff --git a/hscommon/jobprogress/__init__.py b/dupeguru/hscommon/jobprogress/__init__.py similarity index 100% rename from hscommon/jobprogress/__init__.py rename to dupeguru/hscommon/jobprogress/__init__.py diff --git a/hscommon/jobprogress/job.py b/dupeguru/hscommon/jobprogress/job.py similarity index 100% rename from hscommon/jobprogress/job.py rename to dupeguru/hscommon/jobprogress/job.py diff --git a/hscommon/jobprogress/performer.py b/dupeguru/hscommon/jobprogress/performer.py similarity index 100% rename from hscommon/jobprogress/performer.py rename to dupeguru/hscommon/jobprogress/performer.py diff --git a/hscommon/loc.py b/dupeguru/hscommon/loc.py similarity index 100% rename from hscommon/loc.py rename to dupeguru/hscommon/loc.py diff --git a/hscommon/notify.py b/dupeguru/hscommon/notify.py similarity index 100% rename from hscommon/notify.py rename to dupeguru/hscommon/notify.py diff --git a/hscommon/path.py b/dupeguru/hscommon/path.py similarity index 100% rename from hscommon/path.py rename to dupeguru/hscommon/path.py diff --git a/hscommon/plat.py b/dupeguru/hscommon/plat.py similarity index 100% rename from hscommon/plat.py rename to dupeguru/hscommon/plat.py diff --git a/hscommon/pygettext.py b/dupeguru/hscommon/pygettext.py similarity index 100% rename from hscommon/pygettext.py rename to dupeguru/hscommon/pygettext.py diff --git a/hscommon/sphinxgen.py b/dupeguru/hscommon/sphinxgen.py similarity index 100% rename from hscommon/sphinxgen.py rename to dupeguru/hscommon/sphinxgen.py diff --git a/hscommon/tests/__init__.py b/dupeguru/hscommon/tests/__init__.py similarity index 100% rename from hscommon/tests/__init__.py rename to dupeguru/hscommon/tests/__init__.py diff --git a/hscommon/tests/conflict_test.py b/dupeguru/hscommon/tests/conflict_test.py similarity index 100% rename from hscommon/tests/conflict_test.py rename to dupeguru/hscommon/tests/conflict_test.py diff --git a/hscommon/tests/notify_test.py b/dupeguru/hscommon/tests/notify_test.py similarity index 100% rename from hscommon/tests/notify_test.py rename to dupeguru/hscommon/tests/notify_test.py diff --git a/hscommon/tests/path_test.py b/dupeguru/hscommon/tests/path_test.py similarity index 100% rename from hscommon/tests/path_test.py rename to dupeguru/hscommon/tests/path_test.py diff --git a/hscommon/tests/selectable_list_test.py b/dupeguru/hscommon/tests/selectable_list_test.py similarity index 100% rename from hscommon/tests/selectable_list_test.py rename to dupeguru/hscommon/tests/selectable_list_test.py diff --git a/hscommon/tests/table_test.py b/dupeguru/hscommon/tests/table_test.py similarity index 100% rename from hscommon/tests/table_test.py rename to dupeguru/hscommon/tests/table_test.py diff --git a/hscommon/tests/tree_test.py b/dupeguru/hscommon/tests/tree_test.py similarity index 100% rename from hscommon/tests/tree_test.py rename to dupeguru/hscommon/tests/tree_test.py diff --git a/hscommon/tests/util_test.py b/dupeguru/hscommon/tests/util_test.py similarity index 100% rename from hscommon/tests/util_test.py rename to dupeguru/hscommon/tests/util_test.py diff --git a/hscommon/testutil.py b/dupeguru/hscommon/testutil.py similarity index 100% rename from hscommon/testutil.py rename to dupeguru/hscommon/testutil.py diff --git a/hscommon/trans.py b/dupeguru/hscommon/trans.py similarity index 100% rename from hscommon/trans.py rename to dupeguru/hscommon/trans.py diff --git a/hscommon/util.py b/dupeguru/hscommon/util.py similarity index 100% rename from hscommon/util.py rename to dupeguru/hscommon/util.py diff --git a/qt/__init__.py b/dupeguru/qt/__init__.py similarity index 100% rename from qt/__init__.py rename to dupeguru/qt/__init__.py diff --git a/qt/about_box.py b/dupeguru/qt/about_box.py similarity index 100% rename from qt/about_box.py rename to dupeguru/qt/about_box.py diff --git a/qt/app.py b/dupeguru/qt/app.py similarity index 100% rename from qt/app.py rename to dupeguru/qt/app.py diff --git a/qt/column.py b/dupeguru/qt/column.py similarity index 100% rename from qt/column.py rename to dupeguru/qt/column.py diff --git a/qt/deletion_options.py b/dupeguru/qt/deletion_options.py similarity index 100% rename from qt/deletion_options.py rename to dupeguru/qt/deletion_options.py diff --git a/qt/details_dialog.py b/dupeguru/qt/details_dialog.py similarity index 100% rename from qt/details_dialog.py rename to dupeguru/qt/details_dialog.py diff --git a/qt/details_table.py b/dupeguru/qt/details_table.py similarity index 100% rename from qt/details_table.py rename to dupeguru/qt/details_table.py diff --git a/qt/dg.qrc b/dupeguru/qt/dg.qrc similarity index 100% rename from qt/dg.qrc rename to dupeguru/qt/dg.qrc diff --git a/qt/directories_dialog.py b/dupeguru/qt/directories_dialog.py similarity index 100% rename from qt/directories_dialog.py rename to dupeguru/qt/directories_dialog.py diff --git a/qt/directories_model.py b/dupeguru/qt/directories_model.py similarity index 100% rename from qt/directories_model.py rename to dupeguru/qt/directories_model.py diff --git a/qt/error_report_dialog.py b/dupeguru/qt/error_report_dialog.py similarity index 100% rename from qt/error_report_dialog.py rename to dupeguru/qt/error_report_dialog.py diff --git a/qt/exclude_list_dialog.py b/dupeguru/qt/exclude_list_dialog.py similarity index 100% rename from qt/exclude_list_dialog.py rename to dupeguru/qt/exclude_list_dialog.py diff --git a/qt/exclude_list_table.py b/dupeguru/qt/exclude_list_table.py similarity index 100% rename from qt/exclude_list_table.py rename to dupeguru/qt/exclude_list_table.py diff --git a/qt/ignore_list_dialog.py b/dupeguru/qt/ignore_list_dialog.py similarity index 100% rename from qt/ignore_list_dialog.py rename to dupeguru/qt/ignore_list_dialog.py diff --git a/qt/ignore_list_table.py b/dupeguru/qt/ignore_list_table.py similarity index 100% rename from qt/ignore_list_table.py rename to dupeguru/qt/ignore_list_table.py diff --git a/qt/me/__init__.py b/dupeguru/qt/me/__init__.py similarity index 100% rename from qt/me/__init__.py rename to dupeguru/qt/me/__init__.py diff --git a/qt/me/details_dialog.py b/dupeguru/qt/me/details_dialog.py similarity index 100% rename from qt/me/details_dialog.py rename to dupeguru/qt/me/details_dialog.py diff --git a/qt/me/preferences_dialog.py b/dupeguru/qt/me/preferences_dialog.py similarity index 100% rename from qt/me/preferences_dialog.py rename to dupeguru/qt/me/preferences_dialog.py diff --git a/qt/me/results_model.py b/dupeguru/qt/me/results_model.py similarity index 100% rename from qt/me/results_model.py rename to dupeguru/qt/me/results_model.py diff --git a/qt/pe/__init__.py b/dupeguru/qt/pe/__init__.py similarity index 100% rename from qt/pe/__init__.py rename to dupeguru/qt/pe/__init__.py diff --git a/qt/pe/block.py b/dupeguru/qt/pe/block.py similarity index 100% rename from qt/pe/block.py rename to dupeguru/qt/pe/block.py diff --git a/qt/pe/block.pyi b/dupeguru/qt/pe/block.pyi similarity index 100% rename from qt/pe/block.pyi rename to dupeguru/qt/pe/block.pyi diff --git a/qt/pe/details_dialog.py b/dupeguru/qt/pe/details_dialog.py similarity index 100% rename from qt/pe/details_dialog.py rename to dupeguru/qt/pe/details_dialog.py diff --git a/qt/pe/image_viewer.py b/dupeguru/qt/pe/image_viewer.py similarity index 100% rename from qt/pe/image_viewer.py rename to dupeguru/qt/pe/image_viewer.py diff --git a/qt/pe/modules/block.c b/dupeguru/qt/pe/modules/block.c similarity index 100% rename from qt/pe/modules/block.c rename to dupeguru/qt/pe/modules/block.c diff --git a/qt/pe/photo.py b/dupeguru/qt/pe/photo.py similarity index 100% rename from qt/pe/photo.py rename to dupeguru/qt/pe/photo.py diff --git a/qt/pe/preferences_dialog.py b/dupeguru/qt/pe/preferences_dialog.py similarity index 100% rename from qt/pe/preferences_dialog.py rename to dupeguru/qt/pe/preferences_dialog.py diff --git a/qt/pe/results_model.py b/dupeguru/qt/pe/results_model.py similarity index 100% rename from qt/pe/results_model.py rename to dupeguru/qt/pe/results_model.py diff --git a/qt/platform.py b/dupeguru/qt/platform.py similarity index 100% rename from qt/platform.py rename to dupeguru/qt/platform.py diff --git a/qt/preferences.py b/dupeguru/qt/preferences.py similarity index 100% rename from qt/preferences.py rename to dupeguru/qt/preferences.py diff --git a/qt/preferences_dialog.py b/dupeguru/qt/preferences_dialog.py similarity index 100% rename from qt/preferences_dialog.py rename to dupeguru/qt/preferences_dialog.py diff --git a/qt/prioritize_dialog.py b/dupeguru/qt/prioritize_dialog.py similarity index 100% rename from qt/prioritize_dialog.py rename to dupeguru/qt/prioritize_dialog.py diff --git a/qt/problem_dialog.py b/dupeguru/qt/problem_dialog.py similarity index 100% rename from qt/problem_dialog.py rename to dupeguru/qt/problem_dialog.py diff --git a/qt/problem_table.py b/dupeguru/qt/problem_table.py similarity index 100% rename from qt/problem_table.py rename to dupeguru/qt/problem_table.py diff --git a/qt/progress_window.py b/dupeguru/qt/progress_window.py similarity index 100% rename from qt/progress_window.py rename to dupeguru/qt/progress_window.py diff --git a/qt/radio_box.py b/dupeguru/qt/radio_box.py similarity index 100% rename from qt/radio_box.py rename to dupeguru/qt/radio_box.py diff --git a/qt/recent.py b/dupeguru/qt/recent.py similarity index 100% rename from qt/recent.py rename to dupeguru/qt/recent.py diff --git a/qt/result_window.py b/dupeguru/qt/result_window.py similarity index 100% rename from qt/result_window.py rename to dupeguru/qt/result_window.py diff --git a/qt/results_model.py b/dupeguru/qt/results_model.py similarity index 100% rename from qt/results_model.py rename to dupeguru/qt/results_model.py diff --git a/qt/se/__init__.py b/dupeguru/qt/se/__init__.py similarity index 100% rename from qt/se/__init__.py rename to dupeguru/qt/se/__init__.py diff --git a/qt/se/details_dialog.py b/dupeguru/qt/se/details_dialog.py similarity index 100% rename from qt/se/details_dialog.py rename to dupeguru/qt/se/details_dialog.py diff --git a/qt/se/preferences_dialog.py b/dupeguru/qt/se/preferences_dialog.py similarity index 100% rename from qt/se/preferences_dialog.py rename to dupeguru/qt/se/preferences_dialog.py diff --git a/qt/se/results_model.py b/dupeguru/qt/se/results_model.py similarity index 100% rename from qt/se/results_model.py rename to dupeguru/qt/se/results_model.py diff --git a/qt/search_edit.py b/dupeguru/qt/search_edit.py similarity index 100% rename from qt/search_edit.py rename to dupeguru/qt/search_edit.py diff --git a/qt/selectable_list.py b/dupeguru/qt/selectable_list.py similarity index 100% rename from qt/selectable_list.py rename to dupeguru/qt/selectable_list.py diff --git a/qt/stats_label.py b/dupeguru/qt/stats_label.py similarity index 100% rename from qt/stats_label.py rename to dupeguru/qt/stats_label.py diff --git a/qt/tabbed_window.py b/dupeguru/qt/tabbed_window.py similarity index 100% rename from qt/tabbed_window.py rename to dupeguru/qt/tabbed_window.py diff --git a/qt/table.py b/dupeguru/qt/table.py similarity index 100% rename from qt/table.py rename to dupeguru/qt/table.py diff --git a/qt/tree_model.py b/dupeguru/qt/tree_model.py similarity index 100% rename from qt/tree_model.py rename to dupeguru/qt/tree_model.py diff --git a/qt/util.py b/dupeguru/qt/util.py similarity index 100% rename from qt/util.py rename to dupeguru/qt/util.py From e8f50a9947e5aef4eabe856e05a8f279aa077686 Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Sun, 2 Aug 2026 13:53:17 -0600 Subject: [PATCH 25/26] add dupeguru namespace in all imports --- build.py | 6 +- dupeguru/core/app.py | 44 +++++++------- dupeguru/core/directories.py | 8 +-- dupeguru/core/engine.py | 6 +- dupeguru/core/exclude.py | 6 +- dupeguru/core/fs.py | 2 +- dupeguru/core/gui/base.py | 2 +- dupeguru/core/gui/deletion_options.py | 4 +- dupeguru/core/gui/details_panel.py | 4 +- dupeguru/core/gui/directory_tree.py | 6 +- dupeguru/core/gui/exclude_list_dialog.py | 4 +- dupeguru/core/gui/exclude_list_table.py | 8 +-- dupeguru/core/gui/ignore_list_dialog.py | 4 +- dupeguru/core/gui/ignore_list_table.py | 6 +- dupeguru/core/gui/prioritize_dialog.py | 4 +- dupeguru/core/gui/problem_dialog.py | 4 +- dupeguru/core/gui/problem_table.py | 6 +- dupeguru/core/gui/result_table.py | 6 +- dupeguru/core/gui/stats_label.py | 2 +- dupeguru/core/ignore.py | 2 +- dupeguru/core/me/__init__.py | 2 +- dupeguru/core/me/fs.py | 6 +- dupeguru/core/me/prioritize.py | 4 +- dupeguru/core/me/result_table.py | 6 +- dupeguru/core/me/scanner.py | 4 +- dupeguru/core/pe/__init__.py | 2 +- dupeguru/core/pe/block.py | 2 +- dupeguru/core/pe/cache.py | 2 +- dupeguru/core/pe/cache_sqlite.py | 2 +- dupeguru/core/pe/matchblock.py | 12 ++-- dupeguru/core/pe/matchexif.py | 4 +- dupeguru/core/pe/photo.py | 8 +-- dupeguru/core/pe/prioritize.py | 4 +- dupeguru/core/pe/result_table.py | 6 +- dupeguru/core/pe/scanner.py | 6 +- dupeguru/core/prioritize.py | 4 +- dupeguru/core/results.py | 12 ++-- dupeguru/core/scanner.py | 8 +-- dupeguru/core/se/__init__.py | 2 +- dupeguru/core/se/fs.py | 6 +- dupeguru/core/se/result_table.py | 6 +- dupeguru/core/se/scanner.py | 4 +- dupeguru/core/tests/app_test.py | 42 +++++++------- dupeguru/core/tests/base.py | 20 +++---- dupeguru/core/tests/block_test.py | 4 +- dupeguru/core/tests/cache_test.py | 6 +- dupeguru/core/tests/conftest.py | 2 +- dupeguru/core/tests/directories_test.py | 10 ++-- dupeguru/core/tests/engine_test.py | 12 ++-- dupeguru/core/tests/exclude_test.py | 8 +-- dupeguru/core/tests/fs_test.py | 6 +- dupeguru/core/tests/ignore_test.py | 4 +- dupeguru/core/tests/markable_test.py | 4 +- dupeguru/core/tests/prioritize_test.py | 4 +- dupeguru/core/tests/result_table_test.py | 2 +- dupeguru/core/tests/results_test.py | 10 ++-- dupeguru/core/tests/scanner_test.py | 14 ++--- dupeguru/core/util.py | 2 +- dupeguru/hscommon/build.py | 2 +- dupeguru/hscommon/desktop.py | 6 +- dupeguru/hscommon/gui/column.py | 4 +- dupeguru/hscommon/gui/progress_window.py | 6 +- dupeguru/hscommon/gui/selectable_list.py | 2 +- dupeguru/hscommon/gui/table.py | 4 +- dupeguru/hscommon/gui/text_field.py | 4 +- dupeguru/hscommon/gui/tree.py | 2 +- dupeguru/hscommon/jobprogress/performer.py | 2 +- dupeguru/hscommon/loc.py | 2 +- dupeguru/hscommon/sphinxgen.py | 2 +- dupeguru/hscommon/tests/conflict_test.py | 4 +- dupeguru/hscommon/tests/notify_test.py | 4 +- dupeguru/hscommon/tests/path_test.py | 2 +- .../hscommon/tests/selectable_list_test.py | 4 +- dupeguru/hscommon/tests/table_test.py | 4 +- dupeguru/hscommon/tests/tree_test.py | 4 +- dupeguru/hscommon/tests/util_test.py | 4 +- dupeguru/hscommon/trans.py | 2 +- dupeguru/hscommon/util.py | 2 +- dupeguru/qt/about_box.py | 8 +-- dupeguru/qt/app.py | 58 +++++++++---------- dupeguru/qt/deletion_options.py | 4 +- dupeguru/qt/details_dialog.py | 6 +- dupeguru/qt/details_table.py | 2 +- dupeguru/qt/dg.qrc | 22 +++---- dupeguru/qt/directories_dialog.py | 16 ++--- dupeguru/qt/directories_model.py | 4 +- dupeguru/qt/error_report_dialog.py | 6 +- dupeguru/qt/exclude_list_dialog.py | 6 +- dupeguru/qt/exclude_list_table.py | 6 +- dupeguru/qt/ignore_list_dialog.py | 6 +- dupeguru/qt/ignore_list_table.py | 4 +- dupeguru/qt/me/details_dialog.py | 6 +- dupeguru/qt/me/preferences_dialog.py | 10 ++-- dupeguru/qt/me/results_model.py | 4 +- dupeguru/qt/pe/block.py | 2 +- dupeguru/qt/pe/details_dialog.py | 8 +-- dupeguru/qt/pe/image_viewer.py | 4 +- dupeguru/qt/pe/photo.py | 4 +- dupeguru/qt/pe/preferences_dialog.py | 12 ++-- dupeguru/qt/pe/results_model.py | 4 +- dupeguru/qt/platform.py | 2 +- dupeguru/qt/preferences.py | 12 ++-- dupeguru/qt/preferences_dialog.py | 14 ++--- dupeguru/qt/prioritize_dialog.py | 8 +-- dupeguru/qt/problem_dialog.py | 8 +-- dupeguru/qt/problem_table.py | 4 +- dupeguru/qt/progress_window.py | 2 +- dupeguru/qt/radio_box.py | 2 +- dupeguru/qt/recent.py | 4 +- dupeguru/qt/result_window.py | 22 +++---- dupeguru/qt/results_model.py | 2 +- dupeguru/qt/se/details_dialog.py | 6 +- dupeguru/qt/se/preferences_dialog.py | 10 ++-- dupeguru/qt/se/results_model.py | 4 +- dupeguru/qt/search_edit.py | 2 +- dupeguru/qt/tabbed_window.py | 12 ++-- dupeguru/qt/table.py | 2 +- dupeguru/qt/util.py | 6 +- help/en/developer/core/app.rst | 2 +- help/en/developer/core/directories.rst | 2 +- help/en/developer/core/engine.rst | 2 +- help/en/developer/core/fs.rst | 2 +- .../developer/core/gui/deletion_options.rst | 2 +- help/en/developer/core/gui/index.rst | 2 +- help/en/developer/core/results.rst | 2 +- help/en/developer/hscommon/build.rst | 2 +- help/en/developer/hscommon/conflict.rst | 2 +- help/en/developer/hscommon/desktop.rst | 2 +- help/en/developer/hscommon/gui/base.rst | 2 +- help/en/developer/hscommon/gui/column.rst | 2 +- .../hscommon/gui/progress_window.rst | 2 +- .../hscommon/gui/selectable_list.rst | 2 +- help/en/developer/hscommon/gui/table.rst | 2 +- help/en/developer/hscommon/gui/text_field.rst | 2 +- help/en/developer/hscommon/gui/tree.rst | 2 +- .../en/developer/hscommon/jobprogress/job.rst | 2 +- .../hscommon/jobprogress/performer.rst | 2 +- help/en/developer/hscommon/notify.rst | 2 +- help/en/developer/hscommon/path.rst | 2 +- help/en/developer/hscommon/util.rst | 2 +- package.py | 2 +- run.py | 14 ++--- 142 files changed, 423 insertions(+), 423 deletions(-) diff --git a/build.py b/build.py index fda8d0c4..5e78c715 100644 --- a/build.py +++ b/build.py @@ -10,13 +10,13 @@ import shutil from multiprocessing import Pool -from hscommon import sphinxgen -from hscommon.build import ( +from dupeguru.hscommon import sphinxgen +from dupeguru.hscommon.build import ( add_to_pythonpath, print_and_do, fix_qt_resource_file, ) -from hscommon import loc +from dupeguru.hscommon import loc import subprocess diff --git a/dupeguru/core/app.py b/dupeguru/core/app.py index 8455bd7d..e6f9107e 100644 --- a/dupeguru/core/app.py +++ b/dupeguru/core/app.py @@ -15,28 +15,28 @@ from pathlib import Path from send2trash import send2trash -from hscommon.jobprogress import job -from hscommon.notify import Broadcaster -from hscommon.conflict import smart_move, smart_copy -from hscommon.gui.progress_window import ProgressWindow -from hscommon.util import delete_if_empty, first, escape, nonone, allsame -from hscommon.trans import tr -from hscommon import desktop - -from core import se, me, pe -from core.pe.photo import get_delta_dimensions -from core.util import cmp_value, fix_surrogate_encoding -from core import directories, results, export, fs, prioritize -from core.ignore import IgnoreList -from core.exclude import ExcludeDict as ExcludeList -from core.scanner import ScanType -from core.gui.deletion_options import DeletionOptions -from core.gui.details_panel import DetailsPanel -from core.gui.directory_tree import DirectoryTree -from core.gui.ignore_list_dialog import IgnoreListDialog -from core.gui.exclude_list_dialog import ExcludeListDialogCore -from core.gui.problem_dialog import ProblemDialog -from core.gui.stats_label import StatsLabel +from dupeguru.hscommon.jobprogress import job +from dupeguru.hscommon.notify import Broadcaster +from dupeguru.hscommon.conflict import smart_move, smart_copy +from dupeguru.hscommon.gui.progress_window import ProgressWindow +from dupeguru.hscommon.util import delete_if_empty, first, escape, nonone, allsame +from dupeguru.hscommon.trans import tr +from dupeguru.hscommon import desktop + +from dupeguru.core import se, me, pe +from dupeguru.core.pe.photo import get_delta_dimensions +from dupeguru.core.util import cmp_value, fix_surrogate_encoding +from dupeguru.core import directories, results, export, fs, prioritize +from dupeguru.core.ignore import IgnoreList +from dupeguru.core.exclude import ExcludeDict as ExcludeList +from dupeguru.core.scanner import ScanType +from dupeguru.core.gui.deletion_options import DeletionOptions +from dupeguru.core.gui.details_panel import DetailsPanel +from dupeguru.core.gui.directory_tree import DirectoryTree +from dupeguru.core.gui.ignore_list_dialog import IgnoreListDialog +from dupeguru.core.gui.exclude_list_dialog import ExcludeListDialogCore +from dupeguru.core.gui.problem_dialog import ProblemDialog +from dupeguru.core.gui.stats_label import StatsLabel HAD_FIRST_LAUNCH_PREFERENCE = "HadFirstLaunch" DEBUG_MODE_PREFERENCE = "DebugMode" diff --git a/dupeguru/core/directories.py b/dupeguru/core/directories.py index 4f219fa7..27479486 100644 --- a/dupeguru/core/directories.py +++ b/dupeguru/core/directories.py @@ -10,11 +10,11 @@ import logging from pathlib import Path -from hscommon.jobprogress import job -from hscommon.util import FileOrPath -from hscommon.trans import tr +from dupeguru.hscommon.jobprogress import job +from dupeguru.hscommon.util import FileOrPath +from dupeguru.hscommon.trans import tr -from core import fs +from dupeguru.core import fs __all__ = [ "Directories", diff --git a/dupeguru/core/engine.py b/dupeguru/core/engine.py index 934152fb..08a0c902 100644 --- a/dupeguru/core/engine.py +++ b/dupeguru/core/engine.py @@ -13,9 +13,9 @@ from collections import defaultdict, namedtuple from unicodedata import normalize -from hscommon.util import flatten, multi_replace -from hscommon.trans import tr -from hscommon.jobprogress import job +from dupeguru.hscommon.util import flatten, multi_replace +from dupeguru.hscommon.trans import tr +from dupeguru.hscommon.jobprogress import job ( WEIGHT_WORDS, diff --git a/dupeguru/core/exclude.py b/dupeguru/core/exclude.py index 0e1a7578..d83fb0bb 100644 --- a/dupeguru/core/exclude.py +++ b/dupeguru/core/exclude.py @@ -2,7 +2,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.markable import Markable +from dupeguru.core.markable import Markable from xml.etree import ElementTree as ET from defusedxml.ElementTree import parse as safe_parse @@ -13,8 +13,8 @@ from os import sep import logging import functools -from hscommon.util import FileOrPath -from hscommon.plat import ISWINDOWS +from dupeguru.hscommon.util import FileOrPath +from dupeguru.hscommon.plat import ISWINDOWS import time default_regexes = [ diff --git a/dupeguru/core/fs.py b/dupeguru/core/fs.py index 01512da6..b2e7d58d 100644 --- a/dupeguru/core/fs.py +++ b/dupeguru/core/fs.py @@ -21,7 +21,7 @@ from typing import Any, AnyStr, Union, Callable from pathlib import Path -from hscommon.util import nonone, get_file_ext +from dupeguru.hscommon.util import nonone, get_file_ext hasher: Callable try: diff --git a/dupeguru/core/gui/base.py b/dupeguru/core/gui/base.py index 72095b81..08f24364 100644 --- a/dupeguru/core/gui/base.py +++ b/dupeguru/core/gui/base.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.notify import Listener +from dupeguru.hscommon.notify import Listener class DupeGuruGUIObject(Listener): diff --git a/dupeguru/core/gui/deletion_options.py b/dupeguru/core/gui/deletion_options.py index ec6093b9..c2f101da 100644 --- a/dupeguru/core/gui/deletion_options.py +++ b/dupeguru/core/gui/deletion_options.py @@ -7,8 +7,8 @@ import os -from hscommon.gui.base import GUIObject -from hscommon.trans import tr +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.hscommon.trans import tr class DeletionOptionsView: diff --git a/dupeguru/core/gui/details_panel.py b/dupeguru/core/gui/details_panel.py index f6a4e820..7535c16d 100644 --- a/dupeguru/core/gui/details_panel.py +++ b/dupeguru/core/gui/details_panel.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.base import GUIObject -from core.gui.base import DupeGuruGUIObject +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.core.gui.base import DupeGuruGUIObject class DetailsPanel(GUIObject, DupeGuruGUIObject): diff --git a/dupeguru/core/gui/directory_tree.py b/dupeguru/core/gui/directory_tree.py index e44da9f0..3b5e5fe7 100644 --- a/dupeguru/core/gui/directory_tree.py +++ b/dupeguru/core/gui/directory_tree.py @@ -6,10 +6,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.tree import Tree, Node +from dupeguru.hscommon.gui.tree import Tree, Node -from core.directories import DirectoryState -from core.gui.base import DupeGuruGUIObject +from dupeguru.core.directories import DirectoryState +from dupeguru.core.gui.base import DupeGuruGUIObject STATE_ORDER = [DirectoryState.NORMAL, DirectoryState.REFERENCE, DirectoryState.EXCLUDED] diff --git a/dupeguru/core/gui/exclude_list_dialog.py b/dupeguru/core/gui/exclude_list_dialog.py index 2769bc67..0932680f 100644 --- a/dupeguru/core/gui/exclude_list_dialog.py +++ b/dupeguru/core/gui/exclude_list_dialog.py @@ -5,8 +5,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.gui.exclude_list_table import ExcludeListTable -from core.exclude import has_sep +from dupeguru.core.gui.exclude_list_table import ExcludeListTable +from dupeguru.core.exclude import has_sep from os import sep import logging diff --git a/dupeguru/core/gui/exclude_list_table.py b/dupeguru/core/gui/exclude_list_table.py index 30b36505..2b24b237 100644 --- a/dupeguru/core/gui/exclude_list_table.py +++ b/dupeguru/core/gui/exclude_list_table.py @@ -2,10 +2,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.gui.base import DupeGuruGUIObject -from hscommon.gui.table import GUITable, Row -from hscommon.gui.column import Column, Columns -from hscommon.trans import trget +from dupeguru.core.gui.base import DupeGuruGUIObject +from dupeguru.hscommon.gui.table import GUITable, Row +from dupeguru.hscommon.gui.column import Column, Columns +from dupeguru.hscommon.trans import trget tr = trget("ui") diff --git a/dupeguru/core/gui/ignore_list_dialog.py b/dupeguru/core/gui/ignore_list_dialog.py index 9ea67ea8..9866bc7e 100644 --- a/dupeguru/core/gui/ignore_list_dialog.py +++ b/dupeguru/core/gui/ignore_list_dialog.py @@ -5,8 +5,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.trans import tr -from core.gui.ignore_list_table import IgnoreListTable +from dupeguru.hscommon.trans import tr +from dupeguru.core.gui.ignore_list_table import IgnoreListTable class IgnoreListDialog: diff --git a/dupeguru/core/gui/ignore_list_table.py b/dupeguru/core/gui/ignore_list_table.py index 912d8e0f..4477dbdf 100644 --- a/dupeguru/core/gui/ignore_list_table.py +++ b/dupeguru/core/gui/ignore_list_table.py @@ -6,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.table import GUITable, Row -from hscommon.gui.column import Column, Columns -from hscommon.trans import trget +from dupeguru.hscommon.gui.table import GUITable, Row +from dupeguru.hscommon.gui.column import Column, Columns +from dupeguru.hscommon.trans import trget coltr = trget("columns") diff --git a/dupeguru/core/gui/prioritize_dialog.py b/dupeguru/core/gui/prioritize_dialog.py index adcd8515..4e5b0fc2 100644 --- a/dupeguru/core/gui/prioritize_dialog.py +++ b/dupeguru/core/gui/prioritize_dialog.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.base import GUIObject -from hscommon.gui.selectable_list import GUISelectableList +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.hscommon.gui.selectable_list import GUISelectableList class CriterionCategoryList(GUISelectableList): diff --git a/dupeguru/core/gui/problem_dialog.py b/dupeguru/core/gui/problem_dialog.py index 35f9ad0e..8724cdee 100644 --- a/dupeguru/core/gui/problem_dialog.py +++ b/dupeguru/core/gui/problem_dialog.py @@ -6,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon import desktop +from dupeguru.hscommon import desktop -from core.gui.problem_table import ProblemTable +from dupeguru.core.gui.problem_table import ProblemTable class ProblemDialog: diff --git a/dupeguru/core/gui/problem_table.py b/dupeguru/core/gui/problem_table.py index 1be8b85f..26034424 100644 --- a/dupeguru/core/gui/problem_table.py +++ b/dupeguru/core/gui/problem_table.py @@ -6,9 +6,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.table import GUITable, Row -from hscommon.gui.column import Column, Columns -from hscommon.trans import trget +from dupeguru.hscommon.gui.table import GUITable, Row +from dupeguru.hscommon.gui.column import Column, Columns +from dupeguru.hscommon.trans import trget coltr = trget("columns") diff --git a/dupeguru/core/gui/result_table.py b/dupeguru/core/gui/result_table.py index 5582e467..63d261d2 100644 --- a/dupeguru/core/gui/result_table.py +++ b/dupeguru/core/gui/result_table.py @@ -8,10 +8,10 @@ from operator import attrgetter -from hscommon.gui.table import GUITable, Row -from hscommon.gui.column import Columns +from dupeguru.hscommon.gui.table import GUITable, Row +from dupeguru.hscommon.gui.column import Columns -from core.gui.base import DupeGuruGUIObject +from dupeguru.core.gui.base import DupeGuruGUIObject class DupeRow(Row): diff --git a/dupeguru/core/gui/stats_label.py b/dupeguru/core/gui/stats_label.py index fa9fe005..6c198cf8 100644 --- a/dupeguru/core/gui/stats_label.py +++ b/dupeguru/core/gui/stats_label.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.gui.base import DupeGuruGUIObject +from dupeguru.core.gui.base import DupeGuruGUIObject class StatsLabel(DupeGuruGUIObject): diff --git a/dupeguru/core/ignore.py b/dupeguru/core/ignore.py index a9aed03d..26c6b8e8 100644 --- a/dupeguru/core/ignore.py +++ b/dupeguru/core/ignore.py @@ -9,7 +9,7 @@ from xml.etree import ElementTree as ET from defusedxml.ElementTree import parse as safe_parse -from hscommon.util import FileOrPath +from dupeguru.hscommon.util import FileOrPath class IgnoreList: diff --git a/dupeguru/core/me/__init__.py b/dupeguru/core/me/__init__.py index d5476626..a22192af 100644 --- a/dupeguru/core/me/__init__.py +++ b/dupeguru/core/me/__init__.py @@ -1 +1 @@ -from core.me import fs, prioritize, result_table, scanner # noqa +from dupeguru.core.me import fs, prioritize, result_table, scanner # noqa diff --git a/dupeguru/core/me/fs.py b/dupeguru/core/me/fs.py index fbfdfa09..c45dd7a2 100644 --- a/dupeguru/core/me/fs.py +++ b/dupeguru/core/me/fs.py @@ -7,10 +7,10 @@ # http://www.gnu.org/licenses/gpl-3.0.html import mutagen -from hscommon.util import get_file_ext, format_size, format_time +from dupeguru.hscommon.util import get_file_ext, format_size, format_time -from core.util import format_timestamp, format_perc, format_words, format_dupe_count -from core import fs +from dupeguru.core.util import format_timestamp, format_perc, format_words, format_dupe_count +from dupeguru.core import fs TAG_FIELDS = { "audiosize", diff --git a/dupeguru/core/me/prioritize.py b/dupeguru/core/me/prioritize.py index 8d468f3a..8056fc10 100644 --- a/dupeguru/core/me/prioritize.py +++ b/dupeguru/core/me/prioritize.py @@ -5,9 +5,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.trans import trget +from dupeguru.hscommon.trans import trget -from core.prioritize import ( +from dupeguru.core.prioritize import ( KindCategory, FolderCategory, FilenameCategory, diff --git a/dupeguru/core/me/result_table.py b/dupeguru/core/me/result_table.py index ea7c45f5..2a61761b 100644 --- a/dupeguru/core/me/result_table.py +++ b/dupeguru/core/me/result_table.py @@ -5,10 +5,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.column import Column -from hscommon.trans import trget +from dupeguru.hscommon.gui.column import Column +from dupeguru.hscommon.trans import trget -from core.gui.result_table import ResultTable as ResultTableBase +from dupeguru.core.gui.result_table import ResultTable as ResultTableBase coltr = trget("columns") diff --git a/dupeguru/core/me/scanner.py b/dupeguru/core/me/scanner.py index 491df718..70a56b63 100644 --- a/dupeguru/core/me/scanner.py +++ b/dupeguru/core/me/scanner.py @@ -4,9 +4,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.trans import tr +from dupeguru.hscommon.trans import tr -from core.scanner import Scanner as ScannerBase, ScanOption, ScanType +from dupeguru.core.scanner import Scanner as ScannerBase, ScanOption, ScanType class ScannerME(ScannerBase): diff --git a/dupeguru/core/pe/__init__.py b/dupeguru/core/pe/__init__.py index 56d45b7b..0bb82f35 100644 --- a/dupeguru/core/pe/__init__.py +++ b/dupeguru/core/pe/__init__.py @@ -1,4 +1,4 @@ -from core.pe import ( # noqa +from dupeguru.core.pe import ( # noqa block, cache, exif, diff --git a/dupeguru/core/pe/block.py b/dupeguru/core/pe/block.py index 84084e6f..964836d8 100644 --- a/dupeguru/core/pe/block.py +++ b/dupeguru/core/pe/block.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.pe._block import NoBlocksError, DifferentBlockCountError, avgdiff, getblocks2 # NOQA +from dupeguru.core.pe._block import NoBlocksError, DifferentBlockCountError, avgdiff, getblocks2 # NOQA # Converted to C # def getblock(image): diff --git a/dupeguru/core/pe/cache.py b/dupeguru/core/pe/cache.py index 738037f7..2335d231 100644 --- a/dupeguru/core/pe/cache.py +++ b/dupeguru/core/pe/cache.py @@ -4,7 +4,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.pe._cache import bytes_to_colors # noqa +from dupeguru.core.pe._cache import bytes_to_colors # noqa def colors_to_bytes(colors): diff --git a/dupeguru/core/pe/cache_sqlite.py b/dupeguru/core/pe/cache_sqlite.py index 63aa974f..fb0057ee 100644 --- a/dupeguru/core/pe/cache_sqlite.py +++ b/dupeguru/core/pe/cache_sqlite.py @@ -9,7 +9,7 @@ import logging import sqlite3 as sqlite -from core.pe.cache import bytes_to_colors, colors_to_bytes +from dupeguru.core.pe.cache import bytes_to_colors, colors_to_bytes class SqliteCache: diff --git a/dupeguru/core/pe/matchblock.py b/dupeguru/core/pe/matchblock.py index 0ed09a74..cfae04f6 100644 --- a/dupeguru/core/pe/matchblock.py +++ b/dupeguru/core/pe/matchblock.py @@ -12,13 +12,13 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from itertools import combinations -from hscommon.util import extract, iterconsume -from hscommon.trans import tr -from hscommon.jobprogress import job +from dupeguru.hscommon.util import extract, iterconsume +from dupeguru.hscommon.trans import tr +from dupeguru.hscommon.jobprogress import job -from core.engine import Match -from core.pe.block import avgdiff, DifferentBlockCountError, NoBlocksError -from core.pe.cache_sqlite import SqliteCache +from dupeguru.core.engine import Match +from dupeguru.core.pe.block import avgdiff, DifferentBlockCountError, NoBlocksError +from dupeguru.core.pe.cache_sqlite import SqliteCache # OPTIMIZATION NOTES: # The bottleneck of the matching phase is CPU, which is why we use multiprocessing. However, another diff --git a/dupeguru/core/pe/matchexif.py b/dupeguru/core/pe/matchexif.py index 29d4283a..bb30d809 100644 --- a/dupeguru/core/pe/matchexif.py +++ b/dupeguru/core/pe/matchexif.py @@ -9,9 +9,9 @@ from collections import defaultdict from itertools import combinations -from hscommon.trans import tr +from dupeguru.hscommon.trans import tr -from core.engine import Match +from dupeguru.core.engine import Match def getmatches(files, match_scaled, j): diff --git a/dupeguru/core/pe/photo.py b/dupeguru/core/pe/photo.py index 06c6c245..c574bdc5 100644 --- a/dupeguru/core/pe/photo.py +++ b/dupeguru/core/pe/photo.py @@ -5,11 +5,11 @@ # http://www.gnu.org/licenses/gpl-3.0.html import logging -from hscommon.util import get_file_ext, format_size +from dupeguru.hscommon.util import get_file_ext, format_size -from core.util import format_timestamp, format_perc, format_dupe_count -from core import fs -from core.pe import exif +from dupeguru.core.util import format_timestamp, format_perc, format_dupe_count +from dupeguru.core import fs +from dupeguru.core.pe import exif # This global value is set by the platform-specific subclasser of the Photo base class PLAT_SPECIFIC_PHOTO_CLASS = None diff --git a/dupeguru/core/pe/prioritize.py b/dupeguru/core/pe/prioritize.py index 40784822..6e54cafa 100644 --- a/dupeguru/core/pe/prioritize.py +++ b/dupeguru/core/pe/prioritize.py @@ -5,9 +5,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.trans import trget +from dupeguru.hscommon.trans import trget -from core.prioritize import ( +from dupeguru.core.prioritize import ( KindCategory, FolderCategory, FilenameCategory, diff --git a/dupeguru/core/pe/result_table.py b/dupeguru/core/pe/result_table.py index 3216a411..55e13741 100644 --- a/dupeguru/core/pe/result_table.py +++ b/dupeguru/core/pe/result_table.py @@ -5,10 +5,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.column import Column -from hscommon.trans import trget +from dupeguru.hscommon.gui.column import Column +from dupeguru.hscommon.trans import trget -from core.gui.result_table import ResultTable as ResultTableBase +from dupeguru.core.gui.result_table import ResultTable as ResultTableBase coltr = trget("columns") diff --git a/dupeguru/core/pe/scanner.py b/dupeguru/core/pe/scanner.py index fa8eb578..b3653bce 100644 --- a/dupeguru/core/pe/scanner.py +++ b/dupeguru/core/pe/scanner.py @@ -4,11 +4,11 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.trans import tr +from dupeguru.hscommon.trans import tr -from core.scanner import Scanner, ScanType, ScanOption +from dupeguru.core.scanner import Scanner, ScanType, ScanOption -from core.pe import matchblock, matchexif +from dupeguru.core.pe import matchblock, matchexif class ScannerPE(Scanner): diff --git a/dupeguru/core/prioritize.py b/dupeguru/core/prioritize.py index 065969a6..fd17a9d1 100644 --- a/dupeguru/core/prioritize.py +++ b/dupeguru/core/prioritize.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.util import dedupe, flatten, rem_file_ext -from hscommon.trans import trget, tr +from dupeguru.hscommon.util import dedupe, flatten, rem_file_ext +from dupeguru.hscommon.trans import trget, tr coltr = trget("columns") diff --git a/dupeguru/core/results.py b/dupeguru/core/results.py index 19c2db13..e6c03309 100644 --- a/dupeguru/core/results.py +++ b/dupeguru/core/results.py @@ -14,13 +14,13 @@ from xml.etree import ElementTree as ET from defusedxml.ElementTree import parse as safe_parse -from hscommon.jobprogress.job import nulljob -from hscommon.conflict import get_conflicted_name -from hscommon.util import flatten, nonone, FileOrPath, format_size -from hscommon.trans import tr +from dupeguru.hscommon.jobprogress.job import nulljob +from dupeguru.hscommon.conflict import get_conflicted_name +from dupeguru.hscommon.util import flatten, nonone, FileOrPath, format_size +from dupeguru.hscommon.trans import tr -from core import engine -from core.markable import Markable +from dupeguru.core import engine +from dupeguru.core.markable import Markable class Results(Markable): diff --git a/dupeguru/core/scanner.py b/dupeguru/core/scanner.py index cd2c0f02..9d1aee7d 100644 --- a/dupeguru/core/scanner.py +++ b/dupeguru/core/scanner.py @@ -9,11 +9,11 @@ import os.path as op from collections import namedtuple -from hscommon.jobprogress import job -from hscommon.util import dedupe, rem_file_ext, get_file_ext -from hscommon.trans import tr +from dupeguru.hscommon.jobprogress import job +from dupeguru.hscommon.util import dedupe, rem_file_ext, get_file_ext +from dupeguru.hscommon.trans import tr -from core import engine +from dupeguru.core import engine # It's quite ugly to have scan types from all editions all put in the same class, but because there's # there will be some nasty bugs popping up (ScanType is used in core when in should exclusively be diff --git a/dupeguru/core/se/__init__.py b/dupeguru/core/se/__init__.py index 6d5f677d..15c26b9b 100644 --- a/dupeguru/core/se/__init__.py +++ b/dupeguru/core/se/__init__.py @@ -1 +1 @@ -from core.se import fs, result_table, scanner # noqa +from dupeguru.core.se import fs, result_table, scanner # noqa diff --git a/dupeguru/core/se/fs.py b/dupeguru/core/se/fs.py index aa8edbe8..f33e519e 100644 --- a/dupeguru/core/se/fs.py +++ b/dupeguru/core/se/fs.py @@ -6,10 +6,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.util import format_size +from dupeguru.hscommon.util import format_size -from core import fs -from core.util import format_timestamp, format_perc, format_words, format_dupe_count +from dupeguru.core import fs +from dupeguru.core.util import format_timestamp, format_perc, format_words, format_dupe_count def get_display_info(dupe, group, delta): diff --git a/dupeguru/core/se/result_table.py b/dupeguru/core/se/result_table.py index a97a8f50..137f7b6a 100644 --- a/dupeguru/core/se/result_table.py +++ b/dupeguru/core/se/result_table.py @@ -5,10 +5,10 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.column import Column -from hscommon.trans import trget +from dupeguru.hscommon.gui.column import Column +from dupeguru.hscommon.trans import trget -from core.gui.result_table import ResultTable as ResultTableBase +from dupeguru.core.gui.result_table import ResultTable as ResultTableBase coltr = trget("columns") diff --git a/dupeguru/core/se/scanner.py b/dupeguru/core/se/scanner.py index 711df554..542beaee 100644 --- a/dupeguru/core/se/scanner.py +++ b/dupeguru/core/se/scanner.py @@ -4,9 +4,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.trans import tr +from dupeguru.hscommon.trans import tr -from core.scanner import Scanner as ScannerBase, ScanOption, ScanType +from dupeguru.core.scanner import Scanner as ScannerBase, ScanOption, ScanType class ScannerSE(ScannerBase): diff --git a/dupeguru/core/tests/app_test.py b/dupeguru/core/tests/app_test.py index 114d9fda..7fcf316e 100644 --- a/dupeguru/core/tests/app_test.py +++ b/dupeguru/core/tests/app_test.py @@ -11,15 +11,15 @@ import pytest from pathlib import Path -import hscommon.conflict -import hscommon.util -from hscommon.testutil import eq_, log_calls -from hscommon.jobprogress.job import Job +import dupeguru.hscommon.conflict +import dupeguru.hscommon.util +from dupeguru.hscommon.testutil import eq_, log_calls +from dupeguru.hscommon.jobprogress.job import Job -from core.tests.base import TestApp -from core.tests.results_test import GetTestGroups -from core import app, fs, engine -from core.scanner import ScanType +from dupeguru.core.tests.base import TestApp +from dupeguru.core.tests.results_test import GetTestGroups +from dupeguru.core import app, fs, engine +from dupeguru.core.scanner import ScanType def add_fake_files_to_directories(directories, files): @@ -59,20 +59,20 @@ def test_copy_or_move(self, tmpdir, monkeypatch): p = Path(str(tmpdir)) p.joinpath("foo").touch() monkeypatch.setattr( - hscommon.conflict, + dupeguru.hscommon.conflict, "smart_copy", log_calls(lambda source_path, dest_path: None), ) # XXX This monkeypatch is temporary. will be fixed in a better monkeypatcher. - monkeypatch.setattr(app, "smart_copy", hscommon.conflict.smart_copy) + monkeypatch.setattr(app, "smart_copy", dupeguru.hscommon.conflict.smart_copy) monkeypatch.setattr(os, "makedirs", lambda path: None) # We don't want the test to create that fake directory dgapp = TestApp().app dgapp.directories.add_path(p) [f] = dgapp.directories.get_files() with tempfile.TemporaryDirectory() as tmp_dir: dgapp.copy_or_move(f, True, tmp_dir, 0) - eq_(1, len(hscommon.conflict.smart_copy.calls)) - call = hscommon.conflict.smart_copy.calls[0] + eq_(1, len(dupeguru.hscommon.conflict.smart_copy.calls)) + call = dupeguru.hscommon.conflict.smart_copy.calls[0] eq_(call["dest_path"], Path(tmp_dir, "foo")) eq_(call["source_path"], f.path) @@ -96,11 +96,11 @@ def test_copy_or_move_no_location_path(self, tmpdir, monkeypatch): p = Path(str(tmpdir)) p.joinpath("foo").touch() monkeypatch.setattr( - hscommon.conflict, + dupeguru.hscommon.conflict, "smart_copy", log_calls(lambda source_path, dest_path: None), ) - monkeypatch.setattr(app, "smart_copy", hscommon.conflict.smart_copy) + monkeypatch.setattr(app, "smart_copy", dupeguru.hscommon.conflict.smart_copy) monkeypatch.setattr(os, "makedirs", lambda path: None) dgapp = TestApp().app dgapp.directories.add_path(p) @@ -108,7 +108,7 @@ def test_copy_or_move_no_location_path(self, tmpdir, monkeypatch): dgapp.directories._dirs.clear() with tempfile.TemporaryDirectory() as tmp_dir: dgapp.copy_or_move(f, True, tmp_dir, 1) - eq_(1, len(hscommon.conflict.smart_copy.calls)) + eq_(1, len(dupeguru.hscommon.conflict.smart_copy.calls)) def test_scan_with_objects_evaluating_to_false(self): class FakeFile(fs.File): @@ -151,22 +151,22 @@ class TestCaseDupeGuruCleanEmptyDirs: def do_setup(self, request): monkeypatch = request.getfixturevalue("monkeypatch") monkeypatch.setattr( - hscommon.util, + dupeguru.hscommon.util, "delete_if_empty", log_calls(lambda path, files_to_delete=[]: None), ) # XXX This monkeypatch is temporary. will be fixed in a better monkeypatcher. - monkeypatch.setattr(app, "delete_if_empty", hscommon.util.delete_if_empty) + monkeypatch.setattr(app, "delete_if_empty", dupeguru.hscommon.util.delete_if_empty) self.app = TestApp().app def test_option_off(self, do_setup): self.app.clean_empty_dirs(Path("/foo/bar")) - eq_(0, len(hscommon.util.delete_if_empty.calls)) + eq_(0, len(dupeguru.hscommon.util.delete_if_empty.calls)) def test_option_on(self, do_setup): self.app.options["clean_empty_dirs"] = True self.app.clean_empty_dirs(Path("/foo/bar")) - calls = hscommon.util.delete_if_empty.calls + calls = dupeguru.hscommon.util.delete_if_empty.calls eq_(1, len(calls)) eq_(Path("/foo/bar"), calls[0]["path"]) eq_([".DS_Store"], calls[0]["files_to_delete"]) @@ -177,12 +177,12 @@ def test_recurse_up(self, do_setup, monkeypatch): def mock_delete_if_empty(path, files_to_delete=[]): return len(path.parts) > 1 - monkeypatch.setattr(hscommon.util, "delete_if_empty", mock_delete_if_empty) + monkeypatch.setattr(dupeguru.hscommon.util, "delete_if_empty", mock_delete_if_empty) # XXX This monkeypatch is temporary. will be fixed in a better monkeypatcher. monkeypatch.setattr(app, "delete_if_empty", mock_delete_if_empty) self.app.options["clean_empty_dirs"] = True self.app.clean_empty_dirs(Path("not-empty/empty/empty")) - calls = hscommon.util.delete_if_empty.calls + calls = dupeguru.hscommon.util.delete_if_empty.calls eq_(3, len(calls)) eq_(Path("not-empty/empty/empty"), calls[0]["path"]) eq_(Path("not-empty/empty"), calls[1]["path"]) diff --git a/dupeguru/core/tests/base.py b/dupeguru/core/tests/base.py index eeea7d6a..b84b87fe 100644 --- a/dupeguru/core/tests/base.py +++ b/dupeguru/core/tests/base.py @@ -4,17 +4,17 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import TestApp as TestAppBase, CallLogger, eq_, with_app # noqa +from dupeguru.hscommon.testutil import TestApp as TestAppBase, CallLogger, eq_, with_app # noqa from pathlib import Path -from hscommon.util import get_file_ext, format_size -from hscommon.gui.column import Column -from hscommon.jobprogress.job import nulljob, JobCancelled - -from core import engine, prioritize -from core.engine import getwords -from core.app import DupeGuru as DupeGuruBase -from core.gui.result_table import ResultTable as ResultTableBase -from core.gui.prioritize_dialog import PrioritizeDialog +from dupeguru.hscommon.util import get_file_ext, format_size +from dupeguru.hscommon.gui.column import Column +from dupeguru.hscommon.jobprogress.job import nulljob, JobCancelled + +from dupeguru.core import engine, prioritize +from dupeguru.core.engine import getwords +from dupeguru.core.app import DupeGuru as DupeGuruBase +from dupeguru.core.gui.result_table import ResultTable as ResultTableBase +from dupeguru.core.gui.prioritize_dialog import PrioritizeDialog class DupeGuruView: diff --git a/dupeguru/core/tests/block_test.py b/dupeguru/core/tests/block_test.py index caec436c..a0a3fdd9 100644 --- a/dupeguru/core/tests/block_test.py +++ b/dupeguru/core/tests/block_test.py @@ -6,10 +6,10 @@ # The commented out tests are tests for function that have been converted to pure C for speed from pytest import raises, skip -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ try: - from core.pe.block import avgdiff, getblocks2, NoBlocksError, DifferentBlockCountError + from dupeguru.core.pe.block import avgdiff, getblocks2, NoBlocksError, DifferentBlockCountError except ImportError: skip("Can't import the block module, probably hasn't been compiled.") diff --git a/dupeguru/core/tests/cache_test.py b/dupeguru/core/tests/cache_test.py index 6e9e0e88..17822c81 100644 --- a/dupeguru/core/tests/cache_test.py +++ b/dupeguru/core/tests/cache_test.py @@ -7,11 +7,11 @@ import logging from pytest import raises, skip -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ try: - from core.pe.cache import colors_to_bytes, bytes_to_colors - from core.pe.cache_sqlite import SqliteCache + from dupeguru.core.pe.cache import colors_to_bytes, bytes_to_colors + from dupeguru.core.pe.cache_sqlite import SqliteCache except ImportError: skip("Can't import the cache module, probably hasn't been compiled.") diff --git a/dupeguru/core/tests/conftest.py b/dupeguru/core/tests/conftest.py index 2a9cfd61..1a71dd8c 100644 --- a/dupeguru/core/tests/conftest.py +++ b/dupeguru/core/tests/conftest.py @@ -1 +1 @@ -from hscommon.testutil import app # noqa +from dupeguru.hscommon.testutil import app # noqa diff --git a/dupeguru/core/tests/directories_test.py b/dupeguru/core/tests/directories_test.py index 8e0fe7bf..22b7d208 100644 --- a/dupeguru/core/tests/directories_test.py +++ b/dupeguru/core/tests/directories_test.py @@ -11,17 +11,17 @@ from pytest import raises from pathlib import Path -from hscommon.testutil import eq_ -from hscommon.plat import ISWINDOWS +from dupeguru.hscommon.testutil import eq_ +from dupeguru.hscommon.plat import ISWINDOWS -from core.fs import File -from core.directories import ( +from dupeguru.core.fs import File +from dupeguru.core.directories import ( Directories, DirectoryState, AlreadyThereError, InvalidPathError, ) -from core.exclude import ExcludeList, ExcludeDict +from dupeguru.core.exclude import ExcludeList, ExcludeDict def create_fake_fs(rootpath): diff --git a/dupeguru/core/tests/engine_test.py b/dupeguru/core/tests/engine_test.py index 2e5170b9..bd77a7b7 100644 --- a/dupeguru/core/tests/engine_test.py +++ b/dupeguru/core/tests/engine_test.py @@ -6,13 +6,13 @@ import sys -from hscommon.jobprogress import job -from hscommon.util import first -from hscommon.testutil import eq_, log_calls +from dupeguru.hscommon.jobprogress import job +from dupeguru.hscommon.util import first +from dupeguru.hscommon.testutil import eq_, log_calls -from core.tests.base import NamedObject -from core import engine -from core.engine import ( +from dupeguru.core.tests.base import NamedObject +from dupeguru.core import engine +from dupeguru.core.engine import ( get_match, getwords, Group, diff --git a/dupeguru/core/tests/exclude_test.py b/dupeguru/core/tests/exclude_test.py index 64314b97..676d56f1 100644 --- a/dupeguru/core/tests/exclude_test.py +++ b/dupeguru/core/tests/exclude_test.py @@ -7,11 +7,11 @@ import io from xml.etree import ElementTree as ET -from hscommon.testutil import eq_ -from hscommon.plat import ISWINDOWS +from dupeguru.hscommon.testutil import eq_ +from dupeguru.hscommon.plat import ISWINDOWS -from core.tests.base import DupeGuru -from core.exclude import ExcludeList, ExcludeDict, default_regexes, AlreadyThereException +from dupeguru.core.tests.base import DupeGuru +from dupeguru.core.exclude import ExcludeList, ExcludeDict, default_regexes, AlreadyThereException from re import error diff --git a/dupeguru/core/tests/fs_test.py b/dupeguru/core/tests/fs_test.py index d0f2634b..0e0cd773 100644 --- a/dupeguru/core/tests/fs_test.py +++ b/dupeguru/core/tests/fs_test.py @@ -10,10 +10,10 @@ from os import urandom from pathlib import Path -from hscommon.testutil import eq_ -from core.tests.directories_test import create_fake_fs +from dupeguru.hscommon.testutil import eq_ +from dupeguru.core.tests.directories_test import create_fake_fs -from core import fs +from dupeguru.core import fs hasher: typing.Callable try: diff --git a/dupeguru/core/tests/ignore_test.py b/dupeguru/core/tests/ignore_test.py index 7e859ceb..fcb42e76 100644 --- a/dupeguru/core/tests/ignore_test.py +++ b/dupeguru/core/tests/ignore_test.py @@ -8,9 +8,9 @@ from xml.etree import ElementTree as ET from pytest import raises -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ -from core.ignore import IgnoreList +from dupeguru.core.ignore import IgnoreList def test_empty(): diff --git a/dupeguru/core/tests/markable_test.py b/dupeguru/core/tests/markable_test.py index 27ca80d1..c61fa53e 100644 --- a/dupeguru/core/tests/markable_test.py +++ b/dupeguru/core/tests/markable_test.py @@ -4,9 +4,9 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ -from core.markable import MarkableList, Markable +from dupeguru.core.markable import MarkableList, Markable def gen(): diff --git a/dupeguru/core/tests/prioritize_test.py b/dupeguru/core/tests/prioritize_test.py index 87909a2f..1a0041eb 100644 --- a/dupeguru/core/tests/prioritize_test.py +++ b/dupeguru/core/tests/prioritize_test.py @@ -9,8 +9,8 @@ import os.path as op from itertools import combinations -from core.tests.base import TestApp, NamedObject, with_app, eq_ -from core.engine import Group, Match +from dupeguru.core.tests.base import TestApp, NamedObject, with_app, eq_ +from dupeguru.core.engine import Group, Match no = NamedObject diff --git a/dupeguru/core/tests/result_table_test.py b/dupeguru/core/tests/result_table_test.py index 3f5e4c65..22c533c0 100644 --- a/dupeguru/core/tests/result_table_test.py +++ b/dupeguru/core/tests/result_table_test.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.tests.base import TestApp, GetTestGroups +from dupeguru.core.tests.base import TestApp, GetTestGroups def app_with_results(): diff --git a/dupeguru/core/tests/results_test.py b/dupeguru/core/tests/results_test.py index c83141c7..beb2ab0a 100644 --- a/dupeguru/core/tests/results_test.py +++ b/dupeguru/core/tests/results_test.py @@ -10,11 +10,11 @@ from xml.etree import ElementTree as ET from pytest import raises -from hscommon.testutil import eq_ -from hscommon.util import first -from core import engine -from core.tests.base import NamedObject, GetTestGroups, DupeGuru -from core.results import Results +from dupeguru.hscommon.testutil import eq_ +from dupeguru.hscommon.util import first +from dupeguru.core import engine +from dupeguru.core.tests.base import NamedObject, GetTestGroups, DupeGuru +from dupeguru.core.results import Results class TestCaseResultsEmpty: diff --git a/dupeguru/core/tests/scanner_test.py b/dupeguru/core/tests/scanner_test.py index 6dc315fe..ac114779 100644 --- a/dupeguru/core/tests/scanner_test.py +++ b/dupeguru/core/tests/scanner_test.py @@ -6,15 +6,15 @@ import pytest -from hscommon.jobprogress import job +from dupeguru.hscommon.jobprogress import job from pathlib import Path -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ -from core import fs -from core.engine import getwords, Match -from core.ignore import IgnoreList -from core.scanner import Scanner, ScanType -from core.me.scanner import ScannerME +from dupeguru.core import fs +from dupeguru.core.engine import getwords, Match +from dupeguru.core.ignore import IgnoreList +from dupeguru.core.scanner import Scanner, ScanType +from dupeguru.core.me.scanner import ScannerME # TODO update this to be able to inherit from fs.File diff --git a/dupeguru/core/util.py b/dupeguru/core/util.py index 4c3feef3..1ca677c1 100644 --- a/dupeguru/core/util.py +++ b/dupeguru/core/util.py @@ -14,7 +14,7 @@ import logging from typing import Union -from hscommon.util import format_time_decimal +from dupeguru.hscommon.util import format_time_decimal def format_timestamp(t, delta): diff --git a/dupeguru/hscommon/build.py b/dupeguru/hscommon/build.py index 22640f74..83352f4e 100644 --- a/dupeguru/hscommon/build.py +++ b/dupeguru/hscommon/build.py @@ -23,7 +23,7 @@ import glob from typing import Any, AnyStr, Callable, Dict, List, Union -from hscommon.plat import ISWINDOWS +from dupeguru.hscommon.plat import ISWINDOWS def print_and_do(cmd: str) -> int: diff --git a/dupeguru/hscommon/desktop.py b/dupeguru/hscommon/desktop.py index d96a4704..ff89cbb7 100644 --- a/dupeguru/hscommon/desktop.py +++ b/dupeguru/hscommon/desktop.py @@ -46,9 +46,9 @@ def special_folder_path(special_folder: SpecialFolder, portable: bool = False) - try: from PyQt6.QtCore import QUrl, QStandardPaths from PyQt6.QtGui import QDesktopServices - from qt.util import get_appdata - from core.util import executable_folder - from hscommon.plat import ISWINDOWS, ISOSX + from dupeguru.qt.util import get_appdata + from dupeguru.core.util import executable_folder + from dupeguru.hscommon.plat import ISWINDOWS, ISOSX import subprocess def _open_url(url: str) -> None: diff --git a/dupeguru/hscommon/gui/column.py b/dupeguru/hscommon/gui/column.py index 942546ac..23f8d010 100644 --- a/dupeguru/hscommon/gui/column.py +++ b/dupeguru/hscommon/gui/column.py @@ -9,8 +9,8 @@ import copy from typing import Any, List, Tuple, Union -from hscommon.gui.base import GUIObject -from hscommon.gui.table import GUITable +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.hscommon.gui.table import GUITable class Column: diff --git a/dupeguru/hscommon/gui/progress_window.py b/dupeguru/hscommon/gui/progress_window.py index 7adafb1d..60fa7604 100644 --- a/dupeguru/hscommon/gui/progress_window.py +++ b/dupeguru/hscommon/gui/progress_window.py @@ -5,9 +5,9 @@ # http://www.gnu.org/licenses/gpl-3.0.html from typing import Callable, Tuple, Union -from hscommon.jobprogress.performer import ThreadedJobPerformer -from hscommon.gui.base import GUIObject -from hscommon.gui.text_field import TextField +from dupeguru.hscommon.jobprogress.performer import ThreadedJobPerformer +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.hscommon.gui.text_field import TextField class ProgressWindowView: diff --git a/dupeguru/hscommon/gui/selectable_list.py b/dupeguru/hscommon/gui/selectable_list.py index 352c4f5b..33496bec 100644 --- a/dupeguru/hscommon/gui/selectable_list.py +++ b/dupeguru/hscommon/gui/selectable_list.py @@ -8,7 +8,7 @@ from collections.abc import Sequence, MutableSequence -from hscommon.gui.base import GUIObject +from dupeguru.hscommon.gui.base import GUIObject class Selectable(Sequence): diff --git a/dupeguru/hscommon/gui/table.py b/dupeguru/hscommon/gui/table.py index 8be9d2d7..bfdfd332 100644 --- a/dupeguru/hscommon/gui/table.py +++ b/dupeguru/hscommon/gui/table.py @@ -10,8 +10,8 @@ from collections import namedtuple from typing import Any, List, Tuple, Union -from hscommon.gui.base import GUIObject -from hscommon.gui.selectable_list import Selectable +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.hscommon.gui.selectable_list import Selectable # We used to directly subclass list, but it caused problems at some point with deepcopy diff --git a/dupeguru/hscommon/gui/text_field.py b/dupeguru/hscommon/gui/text_field.py index 31a87a25..26644558 100644 --- a/dupeguru/hscommon/gui/text_field.py +++ b/dupeguru/hscommon/gui/text_field.py @@ -5,8 +5,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.gui.base import GUIObject -from hscommon.util import nonone +from dupeguru.hscommon.gui.base import GUIObject +from dupeguru.hscommon.util import nonone class TextFieldView: diff --git a/dupeguru/hscommon/gui/tree.py b/dupeguru/hscommon/gui/tree.py index c9fbb048..3c7405cf 100644 --- a/dupeguru/hscommon/gui/tree.py +++ b/dupeguru/hscommon/gui/tree.py @@ -6,7 +6,7 @@ from collections.abc import MutableSequence -from hscommon.gui.base import GUIObject +from dupeguru.hscommon.gui.base import GUIObject class Node(MutableSequence): diff --git a/dupeguru/hscommon/jobprogress/performer.py b/dupeguru/hscommon/jobprogress/performer.py index 8e64e540..19efd82d 100644 --- a/dupeguru/hscommon/jobprogress/performer.py +++ b/dupeguru/hscommon/jobprogress/performer.py @@ -10,7 +10,7 @@ import sys from typing import Callable, Tuple, Union -from hscommon.jobprogress.job import Job, JobInProgressError, JobCancelled +from dupeguru.hscommon.jobprogress.job import Job, JobInProgressError, JobCancelled class ThreadedJobPerformer: diff --git a/dupeguru/hscommon/loc.py b/dupeguru/hscommon/loc.py index c185aa6e..c096d9fe 100644 --- a/dupeguru/hscommon/loc.py +++ b/dupeguru/hscommon/loc.py @@ -6,7 +6,7 @@ import polib -from hscommon import pygettext +from dupeguru.hscommon import pygettext LC_MESSAGES = "LC_MESSAGES" diff --git a/dupeguru/hscommon/sphinxgen.py b/dupeguru/hscommon/sphinxgen.py index c4e4958b..2acf1a1e 100644 --- a/dupeguru/hscommon/sphinxgen.py +++ b/dupeguru/hscommon/sphinxgen.py @@ -8,7 +8,7 @@ import re from typing import Callable, Dict, Union -from hscommon.build import read_changelog_file, filereplace +from dupeguru.hscommon.build import read_changelog_file, filereplace from sphinx.cmd.build import build_main as sphinx_build CHANGELOG_FORMAT = """ diff --git a/dupeguru/hscommon/tests/conflict_test.py b/dupeguru/hscommon/tests/conflict_test.py index 4f2dca85..530e6f6f 100644 --- a/dupeguru/hscommon/tests/conflict_test.py +++ b/dupeguru/hscommon/tests/conflict_test.py @@ -8,7 +8,7 @@ import pytest -from hscommon.conflict import ( +from dupeguru.hscommon.conflict import ( get_conflicted_name, get_unconflicted_name, is_conflicted, @@ -16,7 +16,7 @@ smart_move, ) from pathlib import Path -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ class TestCaseGetConflictedName: diff --git a/dupeguru/hscommon/tests/notify_test.py b/dupeguru/hscommon/tests/notify_test.py index 062ed5a9..74f59618 100644 --- a/dupeguru/hscommon/tests/notify_test.py +++ b/dupeguru/hscommon/tests/notify_test.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import eq_ -from hscommon.notify import Broadcaster, Listener, Repeater +from dupeguru.hscommon.testutil import eq_ +from dupeguru.hscommon.notify import Broadcaster, Listener, Repeater class HelloListener(Listener): diff --git a/dupeguru/hscommon/tests/path_test.py b/dupeguru/hscommon/tests/path_test.py index d42b33d9..68669a74 100644 --- a/dupeguru/hscommon/tests/path_test.py +++ b/dupeguru/hscommon/tests/path_test.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.path import pathify +from dupeguru.hscommon.path import pathify from pathlib import Path diff --git a/dupeguru/hscommon/tests/selectable_list_test.py b/dupeguru/hscommon/tests/selectable_list_test.py index 5b4c0c95..b3820ccd 100644 --- a/dupeguru/hscommon/tests/selectable_list_test.py +++ b/dupeguru/hscommon/tests/selectable_list_test.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import eq_, callcounter, CallLogger -from hscommon.gui.selectable_list import SelectableList, GUISelectableList +from dupeguru.hscommon.testutil import eq_, callcounter, CallLogger +from dupeguru.hscommon.gui.selectable_list import SelectableList, GUISelectableList def test_in(): diff --git a/dupeguru/hscommon/tests/table_test.py b/dupeguru/hscommon/tests/table_test.py index 30b8d3ed..c9e0475f 100644 --- a/dupeguru/hscommon/tests/table_test.py +++ b/dupeguru/hscommon/tests/table_test.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import CallLogger, eq_ -from hscommon.gui.table import Table, GUITable, Row +from dupeguru.hscommon.testutil import CallLogger, eq_ +from dupeguru.hscommon.gui.table import Table, GUITable, Row class TestRow(Row): diff --git a/dupeguru/hscommon/tests/tree_test.py b/dupeguru/hscommon/tests/tree_test.py index d9a876bf..22a1e01f 100644 --- a/dupeguru/hscommon/tests/tree_test.py +++ b/dupeguru/hscommon/tests/tree_test.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import eq_ -from hscommon.gui.tree import Tree, Node +from dupeguru.hscommon.testutil import eq_ +from dupeguru.hscommon.gui.tree import Tree, Node def tree_with_some_nodes(): diff --git a/dupeguru/hscommon/tests/util_test.py b/dupeguru/hscommon/tests/util_test.py index 601fa3bb..66c0642a 100644 --- a/dupeguru/hscommon/tests/util_test.py +++ b/dupeguru/hscommon/tests/util_test.py @@ -10,9 +10,9 @@ from pytest import raises -from hscommon.testutil import eq_ +from dupeguru.hscommon.testutil import eq_ from pathlib import Path -from hscommon.util import ( +from dupeguru.hscommon.util import ( nonone, tryint, first, diff --git a/dupeguru/hscommon/trans.py b/dupeguru/hscommon/trans.py index 52f8bc6d..f6f3b159 100644 --- a/dupeguru/hscommon/trans.py +++ b/dupeguru/hscommon/trans.py @@ -15,7 +15,7 @@ import os.path as op from typing import Callable, Union -from hscommon.plat import ISLINUX +from dupeguru.hscommon.plat import ISLINUX _trfunc = None _trget = None diff --git a/dupeguru/hscommon/util.py b/dupeguru/hscommon/util.py index 39b32a7e..364c2d2a 100644 --- a/dupeguru/hscommon/util.py +++ b/dupeguru/hscommon/util.py @@ -8,7 +8,7 @@ from math import ceil from pathlib import Path -from hscommon.path import pathify, log_io_error +from dupeguru.hscommon.path import pathify, log_io_error from typing import IO, Any, Callable, Generator, Iterable, List, Tuple, Union diff --git a/dupeguru/qt/about_box.py b/dupeguru/qt/about_box.py index 6d69e961..01b07812 100644 --- a/dupeguru/qt/about_box.py +++ b/dupeguru/qt/about_box.py @@ -10,14 +10,14 @@ from PyQt6.QtGui import QPixmap, QFont, QShowEvent from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QHBoxLayout, QVBoxLayout, QLabel, QWidget -from core.util import check_for_update -from qt.util import move_to_screen_center -from hscommon.trans import trget +from dupeguru.core.util import check_for_update +from dupeguru.qt.util import move_to_screen_center +from dupeguru.hscommon.trans import trget from typing import TYPE_CHECKING if TYPE_CHECKING: - from qt.app import DupeGuru + from dupeguru.qt.app import DupeGuru tr = trget("ui") diff --git a/dupeguru/qt/app.py b/dupeguru/qt/app.py index fe9635ff..f407a585 100644 --- a/dupeguru/qt/app.py +++ b/dupeguru/qt/app.py @@ -12,34 +12,34 @@ from PyQt6.QtGui import QColor, QDesktopServices, QPalette from PyQt6.QtWidgets import QApplication, QFileDialog, QDialog, QMessageBox, QStyleFactory, QToolTip -from hscommon.trans import trget -from hscommon import desktop, plat - -from qt.about_box import AboutBox -from qt.details_dialog import DetailsDialog -from qt.preferences_dialog import PreferencesDialogBase -from qt.recent import Recent -from qt.util import create_actions -from qt.progress_window import ProgressWindow - -from core.app import AppMode, DupeGuru as DupeGuruModel -import core.pe.photo -from qt import platform -from qt.preferences import Preferences -from qt.result_window import ResultWindow -from qt.directories_dialog import DirectoriesDialog -from qt.problem_dialog import ProblemDialog -from qt.ignore_list_dialog import IgnoreListDialog -from qt.exclude_list_dialog import ExcludeListDialog -from qt.deletion_options import DeletionOptions -from qt.se.details_dialog import DetailsDialog as DetailsDialogStandard -from qt.me.details_dialog import DetailsDialog as DetailsDialogMusic -from qt.pe.details_dialog import DetailsDialog as DetailsDialogPicture -from qt.se.preferences_dialog import PreferencesDialog as PreferencesDialogStandard -from qt.me.preferences_dialog import PreferencesDialog as PreferencesDialogMusic -from qt.pe.preferences_dialog import PreferencesDialog as PreferencesDialogPicture -from qt.pe.photo import File as PlatSpecificPhoto -from qt.tabbed_window import TabBarWindow, TabWindow +from dupeguru.hscommon.trans import trget +from dupeguru.hscommon import desktop, plat + +from dupeguru.qt.about_box import AboutBox +from dupeguru.qt.details_dialog import DetailsDialog +from dupeguru.qt.preferences_dialog import PreferencesDialogBase +from dupeguru.qt.recent import Recent +from dupeguru.qt.util import create_actions +from dupeguru.qt.progress_window import ProgressWindow + +from dupeguru.core.app import AppMode, DupeGuru as DupeGuruModel +import dupeguru.core.pe.photo +from dupeguru.qt import platform +from dupeguru.qt.preferences import Preferences +from dupeguru.qt.result_window import ResultWindow +from dupeguru.qt.directories_dialog import DirectoriesDialog +from dupeguru.qt.problem_dialog import ProblemDialog +from dupeguru.qt.ignore_list_dialog import IgnoreListDialog +from dupeguru.qt.exclude_list_dialog import ExcludeListDialog +from dupeguru.qt.deletion_options import DeletionOptions +from dupeguru.qt.se.details_dialog import DetailsDialog as DetailsDialogStandard +from dupeguru.qt.me.details_dialog import DetailsDialog as DetailsDialogMusic +from dupeguru.qt.pe.details_dialog import DetailsDialog as DetailsDialogPicture +from dupeguru.qt.se.preferences_dialog import PreferencesDialog as PreferencesDialogStandard +from dupeguru.qt.me.preferences_dialog import PreferencesDialog as PreferencesDialogMusic +from dupeguru.qt.pe.preferences_dialog import PreferencesDialog as PreferencesDialogPicture +from dupeguru.qt.pe.photo import File as PlatSpecificPhoto +from dupeguru.qt.tabbed_window import TabBarWindow, TabWindow tr = trget("ui") @@ -60,7 +60,7 @@ def __init__(self, **kwargs) -> None: # --- Private def _setup(self) -> None: - core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = PlatSpecificPhoto + dupeguru.core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = PlatSpecificPhoto self._setupActions() self.details_dialog = None self._update_options() diff --git a/dupeguru/qt/deletion_options.py b/dupeguru/qt/deletion_options.py index d12e63dc..a2d274be 100644 --- a/dupeguru/qt/deletion_options.py +++ b/dupeguru/qt/deletion_options.py @@ -9,8 +9,8 @@ from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QCheckBox, QDialogButtonBox -from hscommon.trans import trget -from qt.radio_box import RadioBox +from dupeguru.hscommon.trans import trget +from dupeguru.qt.radio_box import RadioBox tr = trget("ui") diff --git a/dupeguru/qt/details_dialog.py b/dupeguru/qt/details_dialog.py index f5773b72..a61ddca6 100644 --- a/dupeguru/qt/details_dialog.py +++ b/dupeguru/qt/details_dialog.py @@ -9,9 +9,9 @@ from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QDockWidget, QWidget -from qt.util import move_to_screen_center -from qt.details_table import DetailsModel -from hscommon.plat import ISLINUX +from dupeguru.qt.util import move_to_screen_center +from dupeguru.qt.details_table import DetailsModel +from dupeguru.hscommon.plat import ISLINUX class DetailsDialog(QDockWidget): diff --git a/dupeguru/qt/details_table.py b/dupeguru/qt/details_table.py index 5e88c453..f14aa13c 100644 --- a/dupeguru/qt/details_table.py +++ b/dupeguru/qt/details_table.py @@ -10,7 +10,7 @@ from PyQt6.QtWidgets import QHeaderView, QTableView from PyQt6.QtGui import QFont, QBrush -from hscommon.trans import trget +from dupeguru.hscommon.trans import trget tr = trget("ui") diff --git a/dupeguru/qt/dg.qrc b/dupeguru/qt/dg.qrc index d432eb44..a2214af1 100644 --- a/dupeguru/qt/dg.qrc +++ b/dupeguru/qt/dg.qrc @@ -1,15 +1,15 @@ - ../images/dgse_logo_32.png - ../images/dgse_logo_128.png - ../images/plus_8.png - ../images/minus_8.png - ../images/search_clear_13.png - ../images/exchange_purple_upscaled.png - ../images/old_zoom_in.png - ../images/old_zoom_out.png - ../images/old_zoom_original.png - ../images/old_zoom_best_fit.png - ../images/dialog-error.png + ../../images/dgse_logo_32.png + ../../images/dgse_logo_128.png + ../../images/plus_8.png + ../../images/minus_8.png + ../../images/search_clear_13.png + ../../images/exchange_purple_upscaled.png + ../../images/old_zoom_in.png + ../../images/old_zoom_out.png + ../../images/old_zoom_original.png + ../../images/old_zoom_best_fit.png + ../../images/dialog-error.png diff --git a/dupeguru/qt/directories_dialog.py b/dupeguru/qt/directories_dialog.py index b0aec8aa..65d3e896 100644 --- a/dupeguru/qt/directories_dialog.py +++ b/dupeguru/qt/directories_dialog.py @@ -25,14 +25,14 @@ ) from PyQt6.QtGui import QPixmap, QIcon -from hscommon.trans import trget -from core.app import AppMode -from qt.radio_box import RadioBox -from qt.recent import Recent -from qt.util import move_to_screen_center, create_actions - -from qt import platform -from qt.directories_model import DirectoriesModel, DirectoriesDelegate +from dupeguru.hscommon.trans import trget +from dupeguru.core.app import AppMode +from dupeguru.qt.radio_box import RadioBox +from dupeguru.qt.recent import Recent +from dupeguru.qt.util import move_to_screen_center, create_actions + +from dupeguru.qt import platform +from dupeguru.qt.directories_model import DirectoriesModel, DirectoriesDelegate tr = trget("ui") diff --git a/dupeguru/qt/directories_model.py b/dupeguru/qt/directories_model.py index e67aa9b2..76cfce73 100644 --- a/dupeguru/qt/directories_model.py +++ b/dupeguru/qt/directories_model.py @@ -17,8 +17,8 @@ ) from PyQt6.QtGui import QBrush -from hscommon.trans import trget -from qt.tree_model import RefNode, TreeModel +from dupeguru.hscommon.trans import trget +from dupeguru.qt.tree_model import RefNode, TreeModel tr = trget("ui") diff --git a/dupeguru/qt/error_report_dialog.py b/dupeguru/qt/error_report_dialog.py index c1b3e9cd..83a154b9 100644 --- a/dupeguru/qt/error_report_dialog.py +++ b/dupeguru/qt/error_report_dialog.py @@ -21,9 +21,9 @@ QPushButton, ) -from hscommon.trans import trget -from hscommon.desktop import open_url -from qt.util import horizontal_spacer +from dupeguru.hscommon.trans import trget +from dupeguru.hscommon.desktop import open_url +from dupeguru.qt.util import horizontal_spacer tr = trget("ui") diff --git a/dupeguru/qt/exclude_list_dialog.py b/dupeguru/qt/exclude_list_dialog.py index 0c0e218a..79e70ea8 100644 --- a/dupeguru/qt/exclude_list_dialog.py +++ b/dupeguru/qt/exclude_list_dialog.py @@ -16,10 +16,10 @@ QSizePolicy, QHeaderView, ) -from qt.exclude_list_table import ExcludeListTable +from dupeguru.qt.exclude_list_table import ExcludeListTable -from core.exclude import AlreadyThereException -from hscommon.trans import trget +from dupeguru.core.exclude import AlreadyThereException +from dupeguru.hscommon.trans import trget tr = trget("ui") diff --git a/dupeguru/qt/exclude_list_table.py b/dupeguru/qt/exclude_list_table.py index 8efe353c..e327d0d6 100644 --- a/dupeguru/qt/exclude_list_table.py +++ b/dupeguru/qt/exclude_list_table.py @@ -5,9 +5,9 @@ from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont, QFontMetrics, QIcon, QColor -from qt.column import Column -from qt.table import Table -from hscommon.trans import trget +from dupeguru.qt.column import Column +from dupeguru.qt.table import Table +from dupeguru.hscommon.trans import trget tr = trget("ui") diff --git a/dupeguru/qt/ignore_list_dialog.py b/dupeguru/qt/ignore_list_dialog.py index 3a65b3eb..74ca9d65 100644 --- a/dupeguru/qt/ignore_list_dialog.py +++ b/dupeguru/qt/ignore_list_dialog.py @@ -15,9 +15,9 @@ QAbstractItemView, ) -from hscommon.trans import trget -from qt.util import horizontal_wrap -from qt.ignore_list_table import IgnoreListTable +from dupeguru.hscommon.trans import trget +from dupeguru.qt.util import horizontal_wrap +from dupeguru.qt.ignore_list_table import IgnoreListTable tr = trget("ui") diff --git a/dupeguru/qt/ignore_list_table.py b/dupeguru/qt/ignore_list_table.py index 908d93a6..42291a0a 100644 --- a/dupeguru/qt/ignore_list_table.py +++ b/dupeguru/qt/ignore_list_table.py @@ -5,8 +5,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from qt.column import Column -from qt.table import Table +from dupeguru.qt.column import Column +from dupeguru.qt.table import Table class IgnoreListTable(Table): diff --git a/dupeguru/qt/me/details_dialog.py b/dupeguru/qt/me/details_dialog.py index 4b47d277..37156e4a 100644 --- a/dupeguru/qt/me/details_dialog.py +++ b/dupeguru/qt/me/details_dialog.py @@ -7,9 +7,9 @@ from PyQt6.QtCore import QSize from PyQt6.QtWidgets import QAbstractItemView -from hscommon.trans import trget -from qt.details_dialog import DetailsDialog as DetailsDialogBase -from qt.details_table import DetailsTable +from dupeguru.hscommon.trans import trget +from dupeguru.qt.details_dialog import DetailsDialog as DetailsDialogBase +from dupeguru.qt.details_table import DetailsTable tr = trget("ui") diff --git a/dupeguru/qt/me/preferences_dialog.py b/dupeguru/qt/me/preferences_dialog.py index 35e8b38b..1d5cee86 100644 --- a/dupeguru/qt/me/preferences_dialog.py +++ b/dupeguru/qt/me/preferences_dialog.py @@ -8,12 +8,12 @@ from PyQt6.QtCore import QSize from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget, QCheckBox -from hscommon.trans import trget -from core.app import AppMode -from core.scanner import ScanType -from qt.preferences import Preferences +from dupeguru.hscommon.trans import trget +from dupeguru.core.app import AppMode +from dupeguru.core.scanner import ScanType +from dupeguru.qt.preferences import Preferences -from qt.preferences_dialog import PreferencesDialogBase, Sections +from dupeguru.qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") diff --git a/dupeguru/qt/me/results_model.py b/dupeguru/qt/me/results_model.py index e4dafc6d..148bd7d2 100644 --- a/dupeguru/qt/me/results_model.py +++ b/dupeguru/qt/me/results_model.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from qt.column import Column -from qt.results_model import ResultsModel as ResultsModelBase +from dupeguru.qt.column import Column +from dupeguru.qt.results_model import ResultsModel as ResultsModelBase class ResultsModel(ResultsModelBase): diff --git a/dupeguru/qt/pe/block.py b/dupeguru/qt/pe/block.py index 5db5c61c..1d78d555 100644 --- a/dupeguru/qt/pe/block.py +++ b/dupeguru/qt/pe/block.py @@ -6,7 +6,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from qt.pe._block_qt import getblocks # NOQA +from dupeguru.qt.pe._block_qt import getblocks # NOQA # Converted to C # def getblock(image): diff --git a/dupeguru/qt/pe/details_dialog.py b/dupeguru/qt/pe/details_dialog.py index 6bcebe15..e26fcc5d 100644 --- a/dupeguru/qt/pe/details_dialog.py +++ b/dupeguru/qt/pe/details_dialog.py @@ -7,10 +7,10 @@ from PyQt6.QtCore import Qt, QSize, pyqtSignal, pyqtSlot from PyQt6.QtWidgets import QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame from PyQt6.QtGui import QResizeEvent -from hscommon.trans import trget -from qt.details_dialog import DetailsDialog as DetailsDialogBase -from qt.details_table import DetailsTable -from qt.pe.image_viewer import ViewerToolBar, ScrollAreaImageViewer, ScrollAreaController +from dupeguru.hscommon.trans import trget +from dupeguru.qt.details_dialog import DetailsDialog as DetailsDialogBase +from dupeguru.qt.details_table import DetailsTable +from dupeguru.qt.pe.image_viewer import ViewerToolBar, ScrollAreaImageViewer, ScrollAreaController tr = trget("ui") diff --git a/dupeguru/qt/pe/image_viewer.py b/dupeguru/qt/pe/image_viewer.py index 66de2521..a43d66d1 100644 --- a/dupeguru/qt/pe/image_viewer.py +++ b/dupeguru/qt/pe/image_viewer.py @@ -16,8 +16,8 @@ QAbstractScrollArea, QStyle, ) -from hscommon.trans import trget -from hscommon.plat import ISLINUX +from dupeguru.hscommon.trans import trget +from dupeguru.hscommon.plat import ISLINUX tr = trget("ui") diff --git a/dupeguru/qt/pe/photo.py b/dupeguru/qt/pe/photo.py index 2aa9704e..79b87903 100644 --- a/dupeguru/qt/pe/photo.py +++ b/dupeguru/qt/pe/photo.py @@ -9,9 +9,9 @@ from PyQt6.QtCore import Qt from PyQt6.QtGui import QImage, QImageReader, QTransform -from core.pe.photo import Photo as PhotoBase +from dupeguru.core.pe.photo import Photo as PhotoBase -from qt.pe.block import getblocks +from dupeguru.qt.pe.block import getblocks class File(PhotoBase): diff --git a/dupeguru/qt/pe/preferences_dialog.py b/dupeguru/qt/pe/preferences_dialog.py index fa657b1d..17a1f7b2 100644 --- a/dupeguru/qt/pe/preferences_dialog.py +++ b/dupeguru/qt/pe/preferences_dialog.py @@ -11,13 +11,13 @@ from PyQt6.QtWidgets import QLabel, QComboBox -from hscommon.trans import trget -from hscommon.plat import ISLINUX -from core.scanner import ScanType -from core.app import AppMode +from dupeguru.hscommon.trans import trget +from dupeguru.hscommon.plat import ISLINUX +from dupeguru.core.scanner import ScanType +from dupeguru.core.app import AppMode -from qt.preferences import Preferences -from qt.preferences_dialog import PreferencesDialogBase, Sections +from dupeguru.qt.preferences import Preferences +from dupeguru.qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") diff --git a/dupeguru/qt/pe/results_model.py b/dupeguru/qt/pe/results_model.py index 5ebfa8c4..485fe9f7 100644 --- a/dupeguru/qt/pe/results_model.py +++ b/dupeguru/qt/pe/results_model.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from qt.column import Column -from qt.results_model import ResultsModel as ResultsModelBase +from dupeguru.qt.column import Column +from dupeguru.qt.results_model import ResultsModel as ResultsModelBase class ResultsModel(ResultsModelBase): diff --git a/dupeguru/qt/platform.py b/dupeguru/qt/platform.py index 02c52e11..e1d1758d 100644 --- a/dupeguru/qt/platform.py +++ b/dupeguru/qt/platform.py @@ -5,7 +5,7 @@ # http://www.gnu.org/licenses/gpl-3.0.html import os.path as op -from hscommon.plat import ISWINDOWS, ISOSX, ISLINUX +from dupeguru.hscommon.plat import ISWINDOWS, ISOSX, ISLINUX if op.exists(__file__): # We want to get the absolute path or our root folder. We know that in that folder we're diff --git a/dupeguru/qt/preferences.py b/dupeguru/qt/preferences.py index 1b69c30d..f37e265e 100644 --- a/dupeguru/qt/preferences.py +++ b/dupeguru/qt/preferences.py @@ -9,12 +9,12 @@ from PyQt6.QtCore import Qt, QRect, QObject, pyqtSignal from PyQt6.QtGui import QColor -from hscommon import trans -from hscommon.plat import ISLINUX -from core.app import AppMode -from core.scanner import ScanType -from hscommon.util import tryint -from qt.util import create_qsettings +from dupeguru.hscommon import trans +from dupeguru.hscommon.plat import ISLINUX +from dupeguru.core.app import AppMode +from dupeguru.core.scanner import ScanType +from dupeguru.hscommon.util import tryint +from dupeguru.qt.util import create_qsettings def get_langnames(): diff --git a/dupeguru/qt/preferences_dialog.py b/dupeguru/qt/preferences_dialog.py index 2c8eaa42..ad1d4f65 100644 --- a/dupeguru/qt/preferences_dialog.py +++ b/dupeguru/qt/preferences_dialog.py @@ -30,20 +30,20 @@ QFormLayout, ) from PyQt6.QtGui import QPixmap, QIcon, QShowEvent -from hscommon import desktop, plat +from dupeguru.hscommon import desktop, plat -from hscommon.trans import trget -from hscommon.plat import ISLINUX -from qt.util import horizontal_wrap, move_to_screen_center -from qt.preferences import get_langnames +from dupeguru.hscommon.trans import trget +from dupeguru.hscommon.plat import ISLINUX +from dupeguru.qt.util import horizontal_wrap, move_to_screen_center +from dupeguru.qt.preferences import get_langnames from enum import Flag, auto -from qt.preferences import Preferences +from dupeguru.qt.preferences import Preferences from typing import TYPE_CHECKING if TYPE_CHECKING: - from qt.app import DupeGuru + from dupeguru.qt.app import DupeGuru tr = trget("ui") diff --git a/dupeguru/qt/prioritize_dialog.py b/dupeguru/qt/prioritize_dialog.py index 180c6a55..cc59b22e 100644 --- a/dupeguru/qt/prioritize_dialog.py +++ b/dupeguru/qt/prioritize_dialog.py @@ -23,10 +23,10 @@ QSizePolicy, ) -from hscommon.trans import trget -from qt.selectable_list import ComboboxModel, ListviewModel -from qt.util import vertical_spacer -from core.gui.prioritize_dialog import PrioritizeDialog as PrioritizeDialogModel +from dupeguru.hscommon.trans import trget +from dupeguru.qt.selectable_list import ComboboxModel, ListviewModel +from dupeguru.qt.util import vertical_spacer +from dupeguru.core.gui.prioritize_dialog import PrioritizeDialog as PrioritizeDialogModel tr = trget("ui") diff --git a/dupeguru/qt/problem_dialog.py b/dupeguru/qt/problem_dialog.py index 24803199..a81ca670 100644 --- a/dupeguru/qt/problem_dialog.py +++ b/dupeguru/qt/problem_dialog.py @@ -21,10 +21,10 @@ QWidget, ) -from core.gui.problem_dialog import ProblemDialog as ProblemDiaglogModel -from qt.util import move_to_screen_center -from hscommon.trans import trget -from qt.problem_table import ProblemTable +from dupeguru.core.gui.problem_dialog import ProblemDialog as ProblemDiaglogModel +from dupeguru.qt.util import move_to_screen_center +from dupeguru.hscommon.trans import trget +from dupeguru.qt.problem_table import ProblemTable tr = trget("ui") diff --git a/dupeguru/qt/problem_table.py b/dupeguru/qt/problem_table.py index 9e8a3515..ac2fb51c 100644 --- a/dupeguru/qt/problem_table.py +++ b/dupeguru/qt/problem_table.py @@ -6,8 +6,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from qt.column import Column -from qt.table import Table +from dupeguru.qt.column import Column +from dupeguru.qt.table import Table class ProblemTable(Table): diff --git a/dupeguru/qt/progress_window.py b/dupeguru/qt/progress_window.py index 083c961e..0f366eeb 100644 --- a/dupeguru/qt/progress_window.py +++ b/dupeguru/qt/progress_window.py @@ -7,7 +7,7 @@ from PyQt6.QtCore import Qt, QTimer from PyQt6.QtWidgets import QDialog, QMessageBox, QVBoxLayout, QLabel, QProgressBar, QPushButton -from hscommon.trans import tr +from dupeguru.hscommon.trans import tr class ProgressWindow: diff --git a/dupeguru/qt/radio_box.py b/dupeguru/qt/radio_box.py index 7c69fdf5..3d75ea93 100644 --- a/dupeguru/qt/radio_box.py +++ b/dupeguru/qt/radio_box.py @@ -8,7 +8,7 @@ from PyQt6.QtCore import pyqtSignal from PyQt6.QtWidgets import QWidget, QHBoxLayout, QRadioButton -from qt.util import horizontal_spacer +from dupeguru.qt.util import horizontal_spacer class RadioBox(QWidget): diff --git a/dupeguru/qt/recent.py b/dupeguru/qt/recent.py index 91acc8f0..c48885d1 100644 --- a/dupeguru/qt/recent.py +++ b/dupeguru/qt/recent.py @@ -11,8 +11,8 @@ from PyQt6.QtCore import pyqtSignal, QObject from PyQt6.QtGui import QAction -from hscommon.trans import trget -from hscommon.util import dedupe +from dupeguru.hscommon.trans import trget +from dupeguru.hscommon.util import dedupe tr = trget("ui") diff --git a/dupeguru/qt/result_window.py b/dupeguru/qt/result_window.py index d96806be..c49ff435 100644 --- a/dupeguru/qt/result_window.py +++ b/dupeguru/qt/result_window.py @@ -23,17 +23,17 @@ QApplication, ) -from hscommon.trans import trget -from qt.util import move_to_screen_center, horizontal_wrap, create_actions -from qt.search_edit import SearchEdit - -from core.app import AppMode -from qt.results_model import ResultsView -from qt.stats_label import StatsLabel -from qt.prioritize_dialog import PrioritizeDialog -from qt.se.results_model import ResultsModel as ResultsModelStandard -from qt.me.results_model import ResultsModel as ResultsModelMusic -from qt.pe.results_model import ResultsModel as ResultsModelPicture +from dupeguru.hscommon.trans import trget +from dupeguru.qt.util import move_to_screen_center, horizontal_wrap, create_actions +from dupeguru.qt.search_edit import SearchEdit + +from dupeguru.core.app import AppMode +from dupeguru.qt.results_model import ResultsView +from dupeguru.qt.stats_label import StatsLabel +from dupeguru.qt.prioritize_dialog import PrioritizeDialog +from dupeguru.qt.se.results_model import ResultsModel as ResultsModelStandard +from dupeguru.qt.me.results_model import ResultsModel as ResultsModelMusic +from dupeguru.qt.pe.results_model import ResultsModel as ResultsModelPicture tr = trget("ui") diff --git a/dupeguru/qt/results_model.py b/dupeguru/qt/results_model.py index 5219761d..a449713a 100644 --- a/dupeguru/qt/results_model.py +++ b/dupeguru/qt/results_model.py @@ -10,7 +10,7 @@ from PyQt6.QtGui import QBrush, QFont, QFontMetrics from PyQt6.QtWidgets import QTableView -from qt.table import Table +from dupeguru.qt.table import Table class ResultsModel(Table): diff --git a/dupeguru/qt/se/details_dialog.py b/dupeguru/qt/se/details_dialog.py index fd8f8877..f5a749cc 100644 --- a/dupeguru/qt/se/details_dialog.py +++ b/dupeguru/qt/se/details_dialog.py @@ -7,9 +7,9 @@ from PyQt6.QtCore import QSize from PyQt6.QtWidgets import QAbstractItemView -from hscommon.trans import trget -from qt.details_dialog import DetailsDialog as DetailsDialogBase -from qt.details_table import DetailsTable +from dupeguru.hscommon.trans import trget +from dupeguru.qt.details_dialog import DetailsDialog as DetailsDialogBase +from dupeguru.qt.details_table import DetailsTable tr = trget("ui") diff --git a/dupeguru/qt/se/preferences_dialog.py b/dupeguru/qt/se/preferences_dialog.py index f881b0b6..3365099c 100644 --- a/dupeguru/qt/se/preferences_dialog.py +++ b/dupeguru/qt/se/preferences_dialog.py @@ -8,13 +8,13 @@ from PyQt6.QtCore import QSize from PyQt6.QtWidgets import QSpinBox, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget, QCheckBox -from hscommon.trans import trget +from dupeguru.hscommon.trans import trget -from core.app import AppMode -from core.scanner import ScanType -from qt.preferences import Preferences +from dupeguru.core.app import AppMode +from dupeguru.core.scanner import ScanType +from dupeguru.qt.preferences import Preferences -from qt.preferences_dialog import PreferencesDialogBase, Sections +from dupeguru.qt.preferences_dialog import PreferencesDialogBase, Sections tr = trget("ui") diff --git a/dupeguru/qt/se/results_model.py b/dupeguru/qt/se/results_model.py index 23f872fa..9c122990 100644 --- a/dupeguru/qt/se/results_model.py +++ b/dupeguru/qt/se/results_model.py @@ -4,8 +4,8 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from qt.column import Column -from qt.results_model import ResultsModel as ResultsModelBase +from dupeguru.qt.column import Column +from dupeguru.qt.results_model import ResultsModel as ResultsModelBase class ResultsModel(ResultsModelBase): diff --git a/dupeguru/qt/search_edit.py b/dupeguru/qt/search_edit.py index b235e79f..91556cab 100644 --- a/dupeguru/qt/search_edit.py +++ b/dupeguru/qt/search_edit.py @@ -10,7 +10,7 @@ from PyQt6.QtGui import QIcon, QPixmap, QPainter, QPalette from PyQt6.QtWidgets import QToolButton, QLineEdit, QStyle, QStyleOptionFrame -from hscommon.trans import trget +from dupeguru.hscommon.trans import trget tr = trget("ui") diff --git a/dupeguru/qt/tabbed_window.py b/dupeguru/qt/tabbed_window.py index f751f9d3..381b666a 100644 --- a/dupeguru/qt/tabbed_window.py +++ b/dupeguru/qt/tabbed_window.py @@ -13,12 +13,12 @@ QTabBar, QStackedWidget, ) -from hscommon.trans import trget -from qt.util import move_to_screen_center, create_actions -from qt.directories_dialog import DirectoriesDialog -from qt.result_window import ResultWindow -from qt.ignore_list_dialog import IgnoreListDialog -from qt.exclude_list_dialog import ExcludeListDialog +from dupeguru.hscommon.trans import trget +from dupeguru.qt.util import move_to_screen_center, create_actions +from dupeguru.qt.directories_dialog import DirectoriesDialog +from dupeguru.qt.result_window import ResultWindow +from dupeguru.qt.ignore_list_dialog import IgnoreListDialog +from dupeguru.qt.exclude_list_dialog import ExcludeListDialog tr = trget("ui") diff --git a/dupeguru/qt/table.py b/dupeguru/qt/table.py index 612279ac..e6e16ad7 100644 --- a/dupeguru/qt/table.py +++ b/dupeguru/qt/table.py @@ -15,7 +15,7 @@ QItemSelection, ) -from qt.column import Columns, Column +from dupeguru.qt.column import Columns, Column class Table(QAbstractTableModel): diff --git a/dupeguru/qt/util.py b/dupeguru/qt/util.py index 46c2894f..cbecc2cb 100644 --- a/dupeguru/qt/util.py +++ b/dupeguru/qt/util.py @@ -13,9 +13,9 @@ import logging from typing import List, Union -from core.util import executable_folder -from hscommon.util import first -from hscommon.plat import ISWINDOWS +from dupeguru.core.util import executable_folder +from dupeguru.hscommon.util import first +from dupeguru.hscommon.plat import ISWINDOWS from PyQt6.QtCore import QStandardPaths, QSettings from PyQt6.QtGui import QPixmap, QIcon, QGuiApplication, QAction diff --git a/help/en/developer/core/app.rst b/help/en/developer/core/app.rst index b183bcca..5c2ab72b 100644 --- a/help/en/developer/core/app.rst +++ b/help/en/developer/core/app.rst @@ -1,5 +1,5 @@ core.app ======== -.. automodule:: core.app +.. automodule:: dupeguru.core.app :members: diff --git a/help/en/developer/core/directories.rst b/help/en/developer/core/directories.rst index 0854f091..fe7b23ba 100644 --- a/help/en/developer/core/directories.rst +++ b/help/en/developer/core/directories.rst @@ -1,5 +1,5 @@ core.directories ================ -.. automodule:: core.directories +.. automodule:: dupeguru.core.directories :members: diff --git a/help/en/developer/core/engine.rst b/help/en/developer/core/engine.rst index 658111c3..4e8bc873 100644 --- a/help/en/developer/core/engine.rst +++ b/help/en/developer/core/engine.rst @@ -1,7 +1,7 @@ core.engine =========== -.. automodule:: core.engine +.. automodule:: dupeguru.core.engine .. autoclass:: Match diff --git a/help/en/developer/core/fs.rst b/help/en/developer/core/fs.rst index c6275428..4564f120 100644 --- a/help/en/developer/core/fs.rst +++ b/help/en/developer/core/fs.rst @@ -1,5 +1,5 @@ core.fs ======= -.. automodule:: core.fs +.. automodule:: dupeguru.core.fs :members: diff --git a/help/en/developer/core/gui/deletion_options.rst b/help/en/developer/core/gui/deletion_options.rst index 2fbc7f8a..1ba48a19 100644 --- a/help/en/developer/core/gui/deletion_options.rst +++ b/help/en/developer/core/gui/deletion_options.rst @@ -1,5 +1,5 @@ core.gui.deletion_options ========================= -.. automodule:: core.gui.deletion_options +.. automodule:: dupeguru.core.gui.deletion_options :members: diff --git a/help/en/developer/core/gui/index.rst b/help/en/developer/core/gui/index.rst index 74da0305..a87a2b38 100644 --- a/help/en/developer/core/gui/index.rst +++ b/help/en/developer/core/gui/index.rst @@ -1,7 +1,7 @@ core.gui ======== -.. automodule:: core.gui +.. automodule:: dupeguru.core.gui :members: .. toctree:: diff --git a/help/en/developer/core/results.rst b/help/en/developer/core/results.rst index 58e02560..75976da0 100644 --- a/help/en/developer/core/results.rst +++ b/help/en/developer/core/results.rst @@ -1,5 +1,5 @@ core.results ============ -.. automodule:: core.results +.. automodule:: dupeguru.core.results :members: diff --git a/help/en/developer/hscommon/build.rst b/help/en/developer/hscommon/build.rst index b3b106bd..700d58c4 100644 --- a/help/en/developer/hscommon/build.rst +++ b/help/en/developer/hscommon/build.rst @@ -1,5 +1,5 @@ hscommon.build ============== -.. automodule:: hscommon.build +.. automodule:: dupeguru.hscommon.build :members: diff --git a/help/en/developer/hscommon/conflict.rst b/help/en/developer/hscommon/conflict.rst index 34724d10..37e542a4 100644 --- a/help/en/developer/hscommon/conflict.rst +++ b/help/en/developer/hscommon/conflict.rst @@ -1,5 +1,5 @@ hscommon.conflict ================= -.. automodule:: hscommon.conflict +.. automodule:: dupeguru.hscommon.conflict :members: diff --git a/help/en/developer/hscommon/desktop.rst b/help/en/developer/hscommon/desktop.rst index e30ccafd..89379f00 100644 --- a/help/en/developer/hscommon/desktop.rst +++ b/help/en/developer/hscommon/desktop.rst @@ -1,5 +1,5 @@ hscommon.desktop ================ -.. automodule:: hscommon.desktop +.. automodule:: dupeguru.hscommon.desktop :members: diff --git a/help/en/developer/hscommon/gui/base.rst b/help/en/developer/hscommon/gui/base.rst index 739cf367..426fdbfd 100644 --- a/help/en/developer/hscommon/gui/base.rst +++ b/help/en/developer/hscommon/gui/base.rst @@ -1,7 +1,7 @@ hscommon.gui.base ================= -.. automodule:: hscommon.gui.base +.. automodule:: dupeguru.hscommon.gui.base .. autosummary:: diff --git a/help/en/developer/hscommon/gui/column.rst b/help/en/developer/hscommon/gui/column.rst index 3b202b1f..cb87c462 100644 --- a/help/en/developer/hscommon/gui/column.rst +++ b/help/en/developer/hscommon/gui/column.rst @@ -1,7 +1,7 @@ hscommon.gui.column ============================ -.. automodule:: hscommon.gui.column +.. automodule:: dupeguru.hscommon.gui.column .. autosummary:: diff --git a/help/en/developer/hscommon/gui/progress_window.rst b/help/en/developer/hscommon/gui/progress_window.rst index f62a16d8..1479bc08 100644 --- a/help/en/developer/hscommon/gui/progress_window.rst +++ b/help/en/developer/hscommon/gui/progress_window.rst @@ -1,7 +1,7 @@ hscommon.gui.progress_window ============================ -.. automodule:: hscommon.gui.progress_window +.. automodule:: dupeguru.hscommon.gui.progress_window .. autosummary:: diff --git a/help/en/developer/hscommon/gui/selectable_list.rst b/help/en/developer/hscommon/gui/selectable_list.rst index b0200386..384385bb 100644 --- a/help/en/developer/hscommon/gui/selectable_list.rst +++ b/help/en/developer/hscommon/gui/selectable_list.rst @@ -1,7 +1,7 @@ hscommon.gui.selectable_list ============================ -.. automodule:: hscommon.gui.selectable_list +.. automodule:: dupeguru.hscommon.gui.selectable_list .. autosummary:: diff --git a/help/en/developer/hscommon/gui/table.rst b/help/en/developer/hscommon/gui/table.rst index d1ed1dd8..06bd85a2 100644 --- a/help/en/developer/hscommon/gui/table.rst +++ b/help/en/developer/hscommon/gui/table.rst @@ -1,7 +1,7 @@ hscommon.gui.table ================== -.. automodule:: hscommon.gui.table +.. automodule:: dupeguru.hscommon.gui.table .. autosummary:: diff --git a/help/en/developer/hscommon/gui/text_field.rst b/help/en/developer/hscommon/gui/text_field.rst index 1799af02..7924cb5b 100644 --- a/help/en/developer/hscommon/gui/text_field.rst +++ b/help/en/developer/hscommon/gui/text_field.rst @@ -1,7 +1,7 @@ hscommon.gui.text_field ======================= -.. automodule:: hscommon.gui.text_field +.. automodule:: dupeguru.hscommon.gui.text_field .. autosummary:: diff --git a/help/en/developer/hscommon/gui/tree.rst b/help/en/developer/hscommon/gui/tree.rst index fb2dd253..91782f08 100644 --- a/help/en/developer/hscommon/gui/tree.rst +++ b/help/en/developer/hscommon/gui/tree.rst @@ -1,7 +1,7 @@ hscommon.gui.tree ================= -.. automodule:: hscommon.gui.tree +.. automodule:: dupeguru.hscommon.gui.tree .. autosummary:: diff --git a/help/en/developer/hscommon/jobprogress/job.rst b/help/en/developer/hscommon/jobprogress/job.rst index c60a9da4..74b15db6 100644 --- a/help/en/developer/hscommon/jobprogress/job.rst +++ b/help/en/developer/hscommon/jobprogress/job.rst @@ -1,7 +1,7 @@ hscommon.jobprogress.job ======================== -.. automodule:: hscommon.jobprogress.job +.. automodule:: dupeguru.hscommon.jobprogress.job .. autosummary:: diff --git a/help/en/developer/hscommon/jobprogress/performer.rst b/help/en/developer/hscommon/jobprogress/performer.rst index e714c7a0..3f6e828f 100644 --- a/help/en/developer/hscommon/jobprogress/performer.rst +++ b/help/en/developer/hscommon/jobprogress/performer.rst @@ -1,7 +1,7 @@ hscommon.jobprogress.performer ============================== -.. automodule:: hscommon.jobprogress.performer +.. automodule:: dupeguru.hscommon.jobprogress.performer .. autosummary:: diff --git a/help/en/developer/hscommon/notify.rst b/help/en/developer/hscommon/notify.rst index 4d61d584..8bfc8f41 100644 --- a/help/en/developer/hscommon/notify.rst +++ b/help/en/developer/hscommon/notify.rst @@ -1,5 +1,5 @@ hscommon.notify =============== -.. automodule:: hscommon.notify +.. automodule:: dupeguru.hscommon.notify :members: diff --git a/help/en/developer/hscommon/path.rst b/help/en/developer/hscommon/path.rst index e2bcab3d..8bfe0250 100644 --- a/help/en/developer/hscommon/path.rst +++ b/help/en/developer/hscommon/path.rst @@ -1,5 +1,5 @@ hscommon.path ============= -.. automodule:: hscommon.path +.. automodule:: dupeguru.hscommon.path :members: diff --git a/help/en/developer/hscommon/util.rst b/help/en/developer/hscommon/util.rst index 3dc3f539..d93329a2 100644 --- a/help/en/developer/hscommon/util.rst +++ b/help/en/developer/hscommon/util.rst @@ -1,5 +1,5 @@ hscommon.util ============= -.. automodule:: hscommon.util +.. automodule:: dupeguru.hscommon.util :members: diff --git a/package.py b/package.py index 90140c58..a022de69 100644 --- a/package.py +++ b/package.py @@ -15,7 +15,7 @@ import distro import re -from hscommon.build import ( +from dupeguru.hscommon.build import ( print_and_do, copy_packages, build_debian_changelog, diff --git a/run.py b/run.py index 07de6bea..2124c499 100644 --- a/run.py +++ b/run.py @@ -14,12 +14,12 @@ from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QApplication -from hscommon.trans import install_gettext_trans_under_qt -from qt.error_report_dialog import install_excepthook -from qt.util import setup_qt_logging, create_qsettings -from qt import dg_rc # noqa: F401 -from qt.platform import BASE_PATH -from core import __version__, __appname__ +from dupeguru.hscommon.trans import install_gettext_trans_under_qt +from dupeguru.qt.error_report_dialog import install_excepthook +from dupeguru.qt.util import setup_qt_logging, create_qsettings +from dupeguru.qt import dg_rc # noqa: F401 +from dupeguru.qt.platform import BASE_PATH +from dupeguru.core import __version__, __appname__ # SIGQUIT is not defined on Windows if sys.platform == "win32": @@ -75,7 +75,7 @@ def main(): timer.timeout.connect(lambda: None) # Many strings are translated at import time, so this is why we only import after the translator # has been installed - from qt.app import DupeGuru + from dupeguru.qt.app import DupeGuru app.setWindowIcon(QIcon(f"images:{DupeGuru.LOGO_NAME}_32.png")) global dgapp From d6e29780aaf5e47e26d36aa4086b7ee60c1eac36 Mon Sep 17 00:00:00 2001 From: Carl Byington Date: Mon, 3 Aug 2026 10:43:45 -0600 Subject: [PATCH 26/26] update changelog --- help/changelog | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/help/changelog b/help/changelog index 80d58800..8817d638 100644 --- a/help/changelog +++ b/help/changelog @@ -1,3 +1,17 @@ +=== 4.3.1^20260802git (2026-08-02) +* guard copy_or_move when no location path matches dupe parent +* the application name in the dock is "dupeguru" +* use Qt6 rather than Qt5 +* fix to show dupeguru icon in the launcher +* configurable scan speed for picture mode (up to 16x faster) +* fix SQL injection in cache modules +* fix XXE vulnerabilities in XML parsing +* fix command injection in Custom Command +* updated Polish, French, Russian translations +* force Qt6 to use the X11 interfaces since Fedora now runs wayland + and under wayland, Qt6 application window does not use the proper + styling. + === 4.3.1 (2022-07-08) * Fix issue where cache db exceptions could prevent files being hashed (#1015) * Add extra guard for non-zero length files without digests to prevent false duplicates