mirror of
https://github.com/arsenetar/dupeguru.git
synced 2024-10-29 21:05:57 +00:00
Some type-hinting for various qt base objects
This commit is contained in:
parent
b9aabb8545
commit
ade5d7f8c1
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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: <a href="{}">{}</a>').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:
|
||||
|
Loading…
Reference in New Issue
Block a user