diff --git a/qt/me/details_dialog.py b/qt/me/details_dialog.py index 43619af6..6b73fa6c 100644 --- a/qt/me/details_dialog.py +++ b/qt/me/details_dialog.py @@ -15,7 +15,7 @@ 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)) diff --git a/qt/me/preferences_dialog.py b/qt/me/preferences_dialog.py index abcd7a16..34f0162f 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 typing import Callable from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import ( - QVBoxLayout, - QHBoxLayout, - QLabel, - QSizePolicy, - QSpacerItem, - QWidget, -) +from PyQt5.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) @@ -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/details_dialog.py b/qt/pe/details_dialog.py index 9f6083b6..67bfa7b9 100644 --- a/qt/pe/details_dialog.py +++ b/qt/pe/details_dialog.py @@ -4,10 +4,12 @@ # 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 typing import Union +from PyQt5.QtCore import Qt, QSize, pyqtSignal +from PyQt5.QtWidgets import QWidget, QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame from PyQt5.QtGui import QResizeEvent from hscommon.trans import trget +from qt import app from qt.details_dialog import DetailsDialog as DetailsDialogBase from qt.details_table import DetailsTable from qt.pe.image_viewer import ViewerToolBar, ScrollAreaImageViewer, ScrollAreaController @@ -16,15 +18,15 @@ tr = trget("ui") class DetailsDialog(DetailsDialogBase): - def __init__(self, parent, app): - self.vController = None + def __init__(self, parent: QWidget, app: "app.DupeGuru") -> None: + self.vController: Union[ScrollAreaController, None] = None super().__init__(parent, app) - def _setupUi(self): + def _setupUi(self) -> None: 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.horizontalLayout = QGridLayout() @@ -47,8 +49,8 @@ class DetailsDialog(DetailsDialogBase): 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) @@ -73,7 +75,7 @@ class DetailsDialog(DetailsDialogBase): self.topFrame.resized.connect(self.resizeEvent) - def _update(self): + def _update(self) -> None: if self.vController is None: # Not yet constructed! return if not self.app.model.selected_dupes: @@ -87,15 +89,14 @@ class DetailsDialog(DetailsDialogBase): self.vController.updateView(ref, dupe, group) # --- Override - @pyqtSlot(QResizeEvent) - def resizeEvent(self, event): + def resizeEvent(self, event: QResizeEvent) -> None: self.ensure_same_sizes() if self.vController is None or not self.vController.bestFit: return # Only update the scaled down pixmaps self.vController.updateBothImages() - def show(self): + def show(self) -> None: # Give the splitter a maximum height to reach. This is assuming that # all rows below their headers have the same height self.tableView.setMaximumHeight( @@ -108,7 +109,7 @@ class DetailsDialog(DetailsDialogBase): self.ensure_same_sizes() self._update() - def ensure_same_sizes(self): + def ensure_same_sizes(self) -> None: # HACK This ensures same size while shrinking. # ReferenceViewer might be 1 pixel shorter in width # due to the toolbar in the middle keeping the same width, @@ -126,7 +127,7 @@ class DetailsDialog(DetailsDialogBase): self.selectedImageViewer.resize(self.referenceImageViewer.size()) # model --> view - def refresh(self): + def refresh(self) -> None: DetailsDialogBase.refresh(self) if self.isVisible(): self._update() @@ -137,5 +138,5 @@ class EmittingFrame(QFrame): resized = pyqtSignal(QResizeEvent) - def resizeEvent(self, event): + def resizeEvent(self, event: QResizeEvent) -> None: self.resized.emit(event) diff --git a/qt/pe/image_viewer.py b/qt/pe/image_viewer.py index 61c815eb..6a578e4a 100644 --- a/qt/pe/image_viewer.py +++ b/qt/pe/image_viewer.py @@ -2,6 +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 typing import Union, cast 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 ( @@ -17,6 +18,7 @@ from PyQt5.QtWidgets import ( QAbstractScrollArea, QStyle, ) +from qt.details_dialog import DetailsDialog from hscommon.trans import trget from hscommon.plat import ISLINUX @@ -26,7 +28,7 @@ MAX_SCALE = 12.0 MIN_SCALE = 0.1 -def create_actions(actions, target): +def create_actions(actions: list, target: QObject) -> None: # actions are list of (name, shortcut, icon, desc, func) for name, shortcut, icon, desc, func in actions: action = QAction(target) @@ -40,9 +42,9 @@ def create_actions(actions, target): class ViewerToolBar(QToolBar): - def __init__(self, parent, controller): + def __init__(self, parent: DetailsDialog, controller: "BaseController") -> None: super().__init__(parent) - self.parent = parent + self.setParent(parent) self.controller = controller self.setupActions(controller) self.createButtons() @@ -52,24 +54,21 @@ class ViewerToolBar(QToolBar): self.buttonNormalSize.setEnabled(False) self.buttonBestFit.setEnabled(False) - def setupActions(self, controller): + def setupActions(self, controller: "BaseController") -> None: + override_icons = cast(DetailsDialog, self.parent()).app.prefs.details_dialog_override_theme_icons # actions are list of (name, shortcut, icon, desc, func) ACTIONS = [ ( "actionZoomIn", QKeySequence.ZoomIn, - QIcon.fromTheme("zoom-in") - if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons - else QIcon(QPixmap(":/" + "zoom_in")), + QIcon.fromTheme("zoom-in") if ISLINUX and not override_icons else QIcon(QPixmap(":/" + "zoom_in")), tr("Increase zoom"), controller.zoomIn, ), ( "actionZoomOut", QKeySequence.ZoomOut, - QIcon.fromTheme("zoom-out") - if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons - else QIcon(QPixmap(":/" + "zoom_out")), + QIcon.fromTheme("zoom-out") if ISLINUX and not override_icons else QIcon(QPixmap(":/" + "zoom_out")), tr("Decrease zoom"), controller.zoomOut, ), @@ -77,7 +76,7 @@ class ViewerToolBar(QToolBar): "actionNormalSize", tr("Ctrl+/"), QIcon.fromTheme("zoom-original") - if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons + if ISLINUX and not override_icons else QIcon(QPixmap(":/" + "zoom_original")), tr("Normal size"), controller.zoomNormalSize, @@ -86,7 +85,7 @@ class ViewerToolBar(QToolBar): "actionBestFit", tr("Ctrl+*"), QIcon.fromTheme("zoom-best-fit") - if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons + if ISLINUX and not override_icons else QIcon(QPixmap(":/" + "zoom_best_fit")), tr("Best fit"), controller.zoomBestFit, @@ -96,12 +95,12 @@ class ViewerToolBar(QToolBar): # the popup menu work in the toolbar (if resized below minimum height) create_actions(ACTIONS, self) - def createButtons(self): + def createButtons(self) -> None: 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)) - if ISLINUX and not self.parent.app.prefs.details_dialog_override_theme_icons + QIcon.fromTheme("view-refresh", self.style().standardIcon(QStyle.StandardPixmap.SP_BrowserReload)) + if ISLINUX and not cast(DetailsDialog, self.parent()).app.prefs.details_dialog_override_theme_icons else QIcon(QPixmap(":/" + "exchange")) ) self.buttonImgSwap.setText("Swap images") @@ -110,22 +109,22 @@ class ViewerToolBar(QToolBar): 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) @@ -141,10 +140,10 @@ class BaseController(QObject): Base proxy interface to keep image viewers synchronized. Relays function calls, keep tracks of things.""" - def __init__(self, parent): + def __init__(self, parent: QObject) -> None: super().__init__() - self.selectedViewer = None - self.referenceViewer = None + self.selectedViewer: Union[ScrollAreaImageViewer, None] = None + self.referenceViewer: Union[ScrollAreaImageViewer, None] = None # cached pixmaps self.selectedPixmap = QPixmap() self.referencePixmap = QPixmap() @@ -152,22 +151,24 @@ class BaseController(QObject): self.scaledReferencePixmap = QPixmap() self.current_scale = 1.0 self.bestFit = True - self.parent = parent # To change buttons' states + self.setParent(parent) # To change buttons' states self.cached_group = None self.same_dimensions = True - def setupViewers(self, selected_viewer, reference_viewer): + def setupViewers(self, selected_viewer: "ScrollAreaImageViewer", reference_viewer: "ScrollAreaImageViewer") -> None: self.selectedViewer = selected_viewer self.referenceViewer = reference_viewer self.selectedViewer.controller = self self.referenceViewer.controller = self self._setupConnections() - def _setupConnections(self): - self.selectedViewer.connectMouseSignals() - self.referenceViewer.connectMouseSignals() + def _setupConnections(self) -> None: + if self.selectedViewer is not None: + self.selectedViewer.connectMouseSignals() + if self.referenceViewer is not None: + self.referenceViewer.connectMouseSignals() - def updateView(self, ref, dupe, group): + def updateView(self, ref, dupe, group) -> None: # To keep current scale accross dupes from the same group previous_same_dimensions = self.same_dimensions self.same_dimensions = True @@ -206,13 +207,15 @@ class BaseController(QObject): if ignore_update: self.selectedViewer.ignore_signal = False - def _updateImage(self, pixmap, viewer, same_group=False): + def _updateImage( + self, pixmap: QPixmap, viewer: "ScrollAreaImageViewer", same_group: bool = False + ) -> Union[QSize, None]: # WARNING this is called on every resize event, might need to split # into a separate function depending on the implementation used if pixmap.isNull(): # This should disable the blank widget viewer.setImage(pixmap) - return + return None target_size = viewer.size() if not viewer.bestFit: if same_group: @@ -220,14 +223,18 @@ class BaseController(QObject): 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 - def resetState(self): + def resetState(self) -> None: """Only called when the group of dupes has changed. We reset our controller internal state and buttons, center view on viewers.""" self.selectedPixmap = QPixmap() @@ -248,7 +255,7 @@ class BaseController(QObject): self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) self.parent.verticalToolBar.buttonBestFit.setEnabled(False) # active mode by default - def resetViewersState(self): + def resetViewersState(self) -> None: """No item from the model, disable and clear everything.""" # only called by the details dialog self.selectedPixmap = QPixmap() @@ -277,36 +284,40 @@ class BaseController(QObject): self.referenceViewer.setEnabled(False) @pyqtSlot() - def zoomIn(self): + def zoomIn(self) -> None: self.scaleImagesBy(1.25) @pyqtSlot() - def zoomOut(self): + def zoomOut(self) -> None: self.scaleImagesBy(0.8) @pyqtSlot(float) - def scaleImagesBy(self, factor): + def scaleImagesBy(self, factor: float) -> None: """Compute new scale from factor and scale.""" self.current_scale *= factor - self.selectedViewer.scaleBy(factor) - self.referenceViewer.scaleBy(factor) + if self.selectedViewer is not None: + self.selectedViewer.scaleBy(factor) + if self.referenceViewer is not None: + self.referenceViewer.scaleBy(factor) self.updateButtons() @pyqtSlot(float) - def scaleImagesAt(self, scale): + def scaleImagesAt(self, scale: float) -> None: """Scale at a pre-computed scale.""" self.current_scale = scale - self.selectedViewer.scaleAt(scale) - self.referenceViewer.scaleAt(scale) + if self.selectedViewer is not None: + self.selectedViewer.scaleAt(scale) + if self.referenceViewer is not None: + self.referenceViewer.scaleAt(scale) self.updateButtons() - def updateButtons(self): + def updateButtons(self) -> None: self.parent.verticalToolBar.buttonZoomIn.setEnabled(self.current_scale < MAX_SCALE) self.parent.verticalToolBar.buttonZoomOut.setEnabled(self.current_scale > MIN_SCALE) self.parent.verticalToolBar.buttonNormalSize.setEnabled(round(self.current_scale, 1) != 1.0) self.parent.verticalToolBar.buttonBestFit.setEnabled(self.bestFit is False) - def updateButtonsAsPerDimensions(self, previous_same_dimensions): + def updateButtonsAsPerDimensions(self, previous_same_dimensions: bool) -> None: if not self.same_dimensions: self.parent.verticalToolBar.buttonZoomIn.setEnabled(False) self.parent.verticalToolBar.buttonZoomOut.setEnabled(False) @@ -323,7 +334,7 @@ class BaseController(QObject): self.parent.verticalToolBar.buttonImgSwap.setEnabled(False) @pyqtSlot() - def zoomBestFit(self): + def zoomBestFit(self) -> None: """Setup before scaling to bestfit""" self.setBestFit(True) self.current_scale = 1.0 @@ -352,7 +363,7 @@ class BaseController(QObject): self.referenceViewer.bestFit = value @pyqtSlot() - def zoomNormalSize(self): + def zoomNormalSize(self) -> None: self.setBestFit(False) self.current_scale = 1.0 @@ -373,14 +384,14 @@ class BaseController(QObject): self.parent.verticalToolBar.buttonNormalSize.setEnabled(False) self.parent.verticalToolBar.buttonBestFit.setEnabled(True) - def centerViews(self, only_selected=False): + def centerViews(self, only_selected: bool = False) -> None: self.selectedViewer.centerViewAndUpdate() if only_selected: return self.referenceViewer.centerViewAndUpdate() @pyqtSlot() - def swapImages(self): + def swapImages(self) -> None: # swap the columns in the details table as well self.parent.tableView.horizontalHeader().swapSections(0, 1) @@ -388,17 +399,17 @@ class BaseController(QObject): class QWidgetController(BaseController): """Specialized version for QWidget-based viewers.""" - def __init__(self, parent): + def __init__(self, parent: QObject) -> None: super().__init__(parent) - def _updateImage(self, *args): + def _updateImage(self, *args) -> Union[QSize, None]: ret = super()._updateImage(*args) # Fix alignment when resizing window self.centerViews() return ret @pyqtSlot(QPointF) - def onDraggedMouse(self, delta): + def onDraggedMouse(self, delta) -> None: if not self.same_dimensions: return if self.sender() is self.referenceViewer: @@ -407,7 +418,7 @@ class QWidgetController(BaseController): self.referenceViewer.onDraggedMouse(delta) @pyqtSlot() - def swapImages(self): + def swapImages(self) -> None: self.selectedViewer._pixmap.swap(self.referenceViewer._pixmap) self.selectedViewer.centerViewAndUpdate() self.referenceViewer.centerViewAndUpdate() @@ -417,15 +428,15 @@ class QWidgetController(BaseController): class ScrollAreaController(BaseController): """Specialized version fro QLabel-based viewers.""" - def __init__(self, parent): + def __init__(self, parent: QObject) -> None: super().__init__(parent) - def _setupConnections(self): + def _setupConnections(self) -> None: super()._setupConnections() self.selectedViewer.connectScrollBars() self.referenceViewer.connectScrollBars() - def updateBothImages(self, same_group=False): + def updateBothImages(self, same_group: bool = False) -> None: super().updateBothImages(same_group) if not self.referenceViewer.isEnabled(): return @@ -433,7 +444,7 @@ class ScrollAreaController(BaseController): self.referenceViewer._verticalScrollBar.setValue(self.selectedViewer._verticalScrollBar.value()) @pyqtSlot(QPoint) - def onDraggedMouse(self, delta): + def onDraggedMouse(self, delta) -> None: self.selectedViewer.ignore_signal = True self.referenceViewer.ignore_signal = True @@ -450,21 +461,21 @@ class ScrollAreaController(BaseController): self.referenceViewer.ignore_signal = False @pyqtSlot() - def swapImages(self): + def swapImages(self) -> None: self.referenceViewer._pixmap.swap(self.selectedViewer._pixmap) self.referenceViewer.setCachedPixmap() self.selectedViewer.setCachedPixmap() super().swapImages() @pyqtSlot(float, QPointF) - def onMouseWheel(self, scale, delta): + def onMouseWheel(self, scale: float, delta: QPointF) -> None: self.scaleImagesAt(scale) self.selectedViewer.adjustScrollBarsScaled(delta) # Signal from scrollbars will automatically change the other: # self.referenceViewer.adjustScrollBarsScaled(delta) @pyqtSlot(int) - def onVScrollBarChanged(self, value): + def onVScrollBarChanged(self, value: int) -> None: if not self.same_dimensions: return if self.sender() is self.referenceViewer._verticalScrollBar: @@ -475,7 +486,7 @@ class ScrollAreaController(BaseController): self.referenceViewer._verticalScrollBar.setValue(value) @pyqtSlot(int) - def onHScrollBarChanged(self, value): + def onHScrollBarChanged(self, value: int) -> None: if not self.same_dimensions: return if self.sender() is self.referenceViewer._horizontalScrollBar: @@ -486,13 +497,13 @@ class ScrollAreaController(BaseController): self.referenceViewer._horizontalScrollBar.setValue(value) @pyqtSlot(float) - def scaleImagesBy(self, factor): + def scaleImagesBy(self, factor: float) -> None: super().scaleImagesBy(factor) # The other is automatically updated via sigals self.selectedViewer.adjustScrollBarsFactor(factor) @pyqtSlot() - def zoomBestFit(self): + def zoomBestFit(self) -> None: # Disable scrollbars to avoid GridLayout size rounding glitch super().zoomBestFit() if self.referencePixmap.isNull(): diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index caa6eb00..b88bc065 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -4,6 +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 typing import Callable, Union, cast from PyQt5.QtCore import Qt, QSize, pyqtSlot from PyQt5.QtWidgets import ( QDialog, @@ -28,11 +29,12 @@ from PyQt5.QtWidgets import ( QGroupBox, QFormLayout, ) -from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtGui import QPixmap, QIcon, QShowEvent from hscommon import desktop, plat from hscommon.trans import trget from hscommon.plat import ISLINUX +from qt import app from qt.util import horizontal_wrap, move_to_screen_center from qt.preferences import get_langnames from enum import Flag, auto @@ -52,8 +54,10 @@ 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: "app.DupeGuru", **kwargs) -> None: + flags = Qt.WindowType( + 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 +69,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:")) @@ -84,7 +88,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") @@ -104,7 +108,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 +124,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 +175,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 +217,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.")) @@ -221,22 +225,22 @@ use the modifier key to drag the floating window around" self.debugVLayout.addWidget(self.profile_scan_box) self.debug_location_label = QLabel( tr('Logs located in: {}').format(self.app.model.appdata, self.app.model.appdata), - wordWrap=True, ) + self.debug_location_label.setWordWrap(True) 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) cb.setText(label) setattr(self, name, cb) - def _setupPreferenceWidgets(self): + def _setupPreferenceWidgets(self) -> None: # Edition-specific pass - def _setupUi(self): + def _setupUi(self) -> None: self.setWindowTitle(tr("Options")) self.setSizeGripEnabled(False) self.setModal(True) @@ -262,7 +266,7 @@ use the modifier key to drag the floating window around" ) 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 +274,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: Preferences, setchecked: Callable[[QCheckBox, bool], None], section: Sections) -> None: # Edition-specific pass - def _save(self, prefs, ischecked): + def _save(self, prefs: Preferences, ischecked: Callable[[QCheckBox], bool]) -> None: # Edition-specific pass - def load(self, prefs=None, section=Sections.ALL): + def load(self, prefs: Union[Preferences, None] = 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 +327,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,11 +367,11 @@ 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: QDialogButtonBox) -> None: role = self.buttonBox.buttonRole(button) if role == QDialogButtonBox.ResetRole: current_tab = self.tabwidget.currentWidget() @@ -380,30 +384,32 @@ 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.setParent(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, cast(QWidget, self.parent()) + ) 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: