From 2f23f34b919ab903e186d00545f65848ef05cd09 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Fri, 8 Jul 2022 23:15:59 -0500 Subject: [PATCH] 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 | 18 +++--- qt/preferences.py | 23 ++++---- qt/preferences_dialog.py | 81 +++++++++++++++------------ qt/se/preferences_dialog.py | 32 +++++------ 7 files changed, 148 insertions(+), 143 deletions(-) diff --git a/qt/app.py b/qt/app.py index 27ac6ed5..7138ed3a 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 @@ tr = trget("ui") 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 @@ class DupeGuru(QObject): 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 @@ class DupeGuru(QObject): # 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 @@ class DupeGuru(QObject): ] 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 @@ -200,7 +203,7 @@ class DupeGuru(QObject): 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: @@ -208,7 +211,7 @@ class DupeGuru(QObject): 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: @@ -216,7 +219,7 @@ class DupeGuru(QObject): 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 @@ -224,18 +227,18 @@ class DupeGuru(QObject): 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)) @@ -250,29 +253,31 @@ class DupeGuru(QObject): 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: @@ -282,14 +287,14 @@ class DupeGuru(QObject): 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() @@ -304,7 +309,7 @@ class DupeGuru(QObject): 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 = ( @@ -324,28 +329,28 @@ class DupeGuru(QObject): 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 @@ -354,23 +359,22 @@ class DupeGuru(QObject): # 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() @@ -379,10 +383,10 @@ class DupeGuru(QObject): 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): @@ -391,7 +395,7 @@ class DupeGuru(QObject): url = QUrl("https://dupeguru.voltaicideas.net/help/en/") QDesktopServices.openUrl(url) - def handleSIGTERM(self): + def handleSIGTERM(self) -> None: self.shutdown() # --- model --> view @@ -401,20 +405,20 @@ class DupeGuru(QObject): 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) @@ -429,17 +433,17 @@ class DupeGuru(QObject): 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 @@ tr = trget("ui") 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 2ded026d..95d84a89 100644 --- a/qt/pe/preferences_dialog.py +++ b/qt/pe/preferences_dialog.py @@ -4,21 +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.QtWidgets import QFormLayout -from PyQt5.QtCore import Qt +from typing import Callable +from PyQt6.QtWidgets import QFormLayout, QCheckBox +from PyQt6.QtCore import Qt from hscommon.trans import trget from hscommon.plat import ISLINUX +from qt.preferences import Preferences from qt.radio_box import RadioBox 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,12 +39,12 @@ class PreferencesDialog(PreferencesDialogBase): self.cacheTypeRadio = RadioBox(self, items=["Sqlite", "Shelve"], spread=False) cache_form = QFormLayout() - cache_form.setLabelAlignment(Qt.AlignLeft) + cache_form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) cache_form.addRow(tr("Picture cache mode:"), self.cacheTypeRadio) self.widgetsVLayout.addLayout(cache_form) 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( @@ -62,7 +64,7 @@ show scrollbars to span the view around" ) 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) self.cacheTypeRadio.selected_index = 1 if prefs.picture_cache_type == "shelve" else 0 @@ -73,7 +75,7 @@ show scrollbars to span the view around" 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.picture_cache_type = "shelve" if self.cacheTypeRadio.selected_index == 1 else "sqlite" prefs.details_dialog_override_theme_icons = ischecked(self.details_dialog_override_theme_icons) diff --git a/qt/preferences.py b/qt/preferences.py index d0b84820..7c7f472d 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 @@ class PreferencesBase(QObject): 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 @@ class PreferencesBase(QObject): 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 @@ class PreferencesBase(QObject): 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) @@ -225,7 +226,7 @@ class Preferences(PreferencesBase): self.match_scaled = get("MatchScaled", self.match_scaled) self.picture_cache_type = get("PictureCacheType", self.picture_cache_type) - def reset(self): + def reset(self) -> None: self.filter_hardness = 95 self.mix_file_kind = True self.use_regexp = False @@ -247,8 +248,8 @@ class Preferences(PreferencesBase): # 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 @@ -276,7 +277,7 @@ class Preferences(PreferencesBase): self.match_scaled = False self.picture_cache_type = "sqlite" - 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 caa6eb00..9702bfa5 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 @@ from PyQt5.QtWidgets import ( 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 enum import Flag, auto from qt.preferences import Preferences +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from qt.app import DupeGuru + tr = trget("ui") @@ -52,8 +58,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])) @@ -65,7 +71,7 @@ class PreferencesDialogBase(QDialog): 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:")) @@ -76,7 +82,7 @@ class PreferencesDialogBase(QDialog): 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()) @@ -84,7 +90,7 @@ class PreferencesDialogBase(QDialog): 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") @@ -96,7 +102,7 @@ class PreferencesDialogBase(QDialog): 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")) @@ -104,7 +110,7 @@ class PreferencesDialogBase(QDialog): 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:")) @@ -120,7 +126,7 @@ class PreferencesDialogBase(QDialog): 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) @@ -171,7 +177,7 @@ On MacOS, the tab bar will fill up the window's width instead." 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()) @@ -213,7 +219,7 @@ use the modifier key to drag the floating window around" details_groupbox.setLayout(self.details_groupbox_layout) self.displayVLayout.addWidget(details_groupbox) - 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.")) @@ -225,7 +231,7 @@ use the modifier key to drag the floating window around" ) 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) @@ -236,7 +242,7 @@ use the modifier key to drag the floating window around" # Edition-specific pass - def _setupUi(self): + def _setupUi(self) -> None: self.setWindowTitle(tr("Options")) self.setSizeGripEnabled(False) self.setModal(True) @@ -258,11 +264,13 @@ use the modifier key to drag the floating window around" # 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_debug, tr("Debug")) @@ -270,20 +278,20 @@ use the modifier key to drag the floating window around" self.widgetsVLayout.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) @@ -323,12 +331,12 @@ use the modifier key to drag the floating window around" 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) @@ -363,13 +371,13 @@ use the modifier key to drag the floating window around" 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: @@ -380,30 +388,31 @@ use the modifier key to drag the floating window around" 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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 @@ class PreferencesDialog(PreferencesDialogBase): 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)