diff --git a/images/exchange.icns b/images/exchange.icns new file mode 100644 index 00000000..f93828b4 Binary files /dev/null and b/images/exchange.icns differ diff --git a/images/exchange.ico b/images/exchange.ico new file mode 100644 index 00000000..cf5226c9 Binary files /dev/null and b/images/exchange.ico differ diff --git a/images/exchange.png b/images/exchange.png new file mode 100644 index 00000000..6ed902b7 Binary files /dev/null and b/images/exchange.png differ diff --git a/images/exchange_purple.png b/images/exchange_purple.png new file mode 100644 index 00000000..21a6296a Binary files /dev/null and b/images/exchange_purple.png differ diff --git a/images/exchange_purple_upscaled.png b/images/exchange_purple_upscaled.png new file mode 100644 index 00000000..1f31231c Binary files /dev/null and b/images/exchange_purple_upscaled.png differ diff --git a/images/exchange_purple_waifu_s4_tta8.png b/images/exchange_purple_waifu_s4_tta8.png new file mode 100644 index 00000000..21bbcf53 Binary files /dev/null and b/images/exchange_purple_waifu_s4_tta8.png differ diff --git a/images/exchange_purple_waifu_s4_tta8.xcf b/images/exchange_purple_waifu_s4_tta8.xcf new file mode 100644 index 00000000..f3c922cb Binary files /dev/null and b/images/exchange_purple_waifu_s4_tta8.xcf differ diff --git a/images/exchange_waifu_s4_tta8.png b/images/exchange_waifu_s4_tta8.png new file mode 100644 index 00000000..2f7a1cd9 Binary files /dev/null and b/images/exchange_waifu_s4_tta8.png differ diff --git a/images/old_zoom_best_fit.png b/images/old_zoom_best_fit.png new file mode 100644 index 00000000..444d4dcf Binary files /dev/null and b/images/old_zoom_best_fit.png differ diff --git a/images/old_zoom_in.png b/images/old_zoom_in.png new file mode 100644 index 00000000..fbcbe2c1 Binary files /dev/null and b/images/old_zoom_in.png differ diff --git a/images/old_zoom_original.png b/images/old_zoom_original.png new file mode 100644 index 00000000..0bb910d6 Binary files /dev/null and b/images/old_zoom_original.png differ diff --git a/images/old_zoom_out.png b/images/old_zoom_out.png new file mode 100644 index 00000000..f7e84c98 Binary files /dev/null and b/images/old_zoom_out.png differ diff --git a/qt/app.py b/qt/app.py index ac59dd0a..7a5b60f6 100644 --- a/qt/app.py +++ b/qt/app.py @@ -7,7 +7,7 @@ import sys import os.path as op -from PyQt5.QtCore import QTimer, QObject, QUrl, pyqtSignal +from PyQt5.QtCore import QTimer, QObject, QUrl, pyqtSignal, Qt from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QApplication, QFileDialog, QDialog, QMessageBox @@ -54,11 +54,11 @@ class DupeGuru(QObject): def _setup(self): core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = PlatSpecificPhoto self._setupActions() + self.details_dialog = None self._update_options() self.recentResults = Recent(self, "recentResults") self.recentResults.mustOpenItem.connect(self.model.load_from) self.resultWindow = None - self.details_dialog = None self.directories_dialog = DirectoriesDialog(self) self.progress_window = ProgressWindow( self.directories_dialog, self.model.progress_window @@ -152,6 +152,9 @@ class DupeGuru(QObject): self.model.options["match_scaled"] = self.prefs.match_scaled self.model.options["picture_cache_type"] = self.prefs.picture_cache_type + if self.details_dialog: + self.details_dialog.update_options() + # --- Private def _get_details_dialog_class(self): if self.model.app_mode == AppMode.Picture: @@ -284,6 +287,8 @@ class DupeGuru(QObject): self.resultWindow.setParent(None) self.resultWindow = ResultWindow(self.directories_dialog, self) self.details_dialog = self._get_details_dialog_class()(self.resultWindow, self) + self.resultWindow.addDockWidget( + Qt.BottomDockWidgetArea, self.details_dialog) def show_results_window(self): self.showResultsWindow() diff --git a/qt/details_dialog.py b/qt/details_dialog.py index 57cc650b..7b3f07d5 100644 --- a/qt/details_dialog.py +++ b/qt/details_dialog.py @@ -7,16 +7,19 @@ # http://www.gnu.org/licenses/gpl-3.0.html from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QMainWindow +from PyQt5.QtWidgets import QDockWidget, QWidget from .details_table import DetailsModel +from hscommon.plat import ISLINUX -class DetailsDialog(QMainWindow): +class DetailsDialog(QDockWidget): def __init__(self, parent, app, **kwargs): super().__init__(parent, Qt.Tool, **kwargs) + self.parent = parent self.app = app self.model = app.model.details_panel + self.setAllowedAreas(Qt.AllDockWidgetAreas) self._setupUi() # To avoid saving uninitialized geometry on appWillSavePrefs, we track whether our dialog # has been shown. If it has, we know that our geometry should be saved. @@ -35,10 +38,31 @@ class DetailsDialog(QMainWindow): def show(self): self._shown_once = True super().show() + self.update_options() + + def update_options(self): + # This disables the title bar (if we had not set one before already) + # essentially making it a simple floating window, not dockable anymore + if not self.app.prefs.details_dialog_titlebar_enabled \ + and not self.titleBarWidget(): + self.setTitleBarWidget(QWidget()) + # Windows (and MacOS?) users cannot move a floating window which + # has not native decoration so we force it to dock for now + if not ISLINUX: + self.setFloating(False) + elif self.titleBarWidget() is not None: + # resets to the default title bar + self.setTitleBarWidget(None) + + features = self.features() + if self.app.prefs.details_dialog_vertical_titlebar: + self.setFeatures(features | QDockWidget.DockWidgetVerticalTitleBar) + elif features & QDockWidget.DockWidgetVerticalTitleBar: + self.setFeatures(features ^ QDockWidget.DockWidgetVerticalTitleBar) # --- Events def appWillSavePrefs(self): - if self._shown_once: + if self._shown_once and self.isFloating(): self.app.prefs.saveGeometry("DetailsWindowRect", self) # --- model --> view diff --git a/qt/dg.qrc b/qt/dg.qrc index 545a9806..760f2a85 100644 --- a/qt/dg.qrc +++ b/qt/dg.qrc @@ -5,5 +5,10 @@ ../images/plus_8.png ../images/minus_8.png ../qtlib/images/search_clear_13.png + ../images/exchange_purple_upscaled.png + ../images/old_zoom_in.png + ../images/old_zoom_out.png + ../images/old_zoom_original.png + ../images/old_zoom_best_fit.png diff --git a/qt/me/details_dialog.py b/qt/me/details_dialog.py index 935a34c6..ecb947d0 100644 --- a/qt/me/details_dialog.py +++ b/qt/me/details_dialog.py @@ -5,7 +5,7 @@ # http://www.gnu.org/licenses/gpl-3.0.html from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView +from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView, QWidget from hscommon.trans import trget from ..details_dialog import DetailsDialog as DetailsDialogBase @@ -27,3 +27,6 @@ class DetailsDialog(DetailsDialogBase): self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setShowGrid(False) self.verticalLayout.addWidget(self.tableView) + self.centralWidget = QWidget() + self.centralWidget.setLayout(self.verticalLayout) + self.setWidget(self.centralWidget) diff --git a/qt/pe/details_dialog.py b/qt/pe/details_dialog.py index 07ecdfcb..d96dba17 100644 --- a/qt/pe/details_dialog.py +++ b/qt/pe/details_dialog.py @@ -4,11 +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 +from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame) - +from PyQt5.QtGui import QResizeEvent from hscommon.trans import trget +from hscommon.plat import ISWINDOWS from ..details_dialog import DetailsDialog as DetailsDialogBase from ..details_table import DetailsTable from .image_viewer import ( @@ -25,9 +26,8 @@ class DetailsDialog(DetailsDialogBase): self.setWindowTitle(tr("Details")) self.resize(502, 502) self.setMinimumSize(QSize(250, 250)) - self.splitter = QSplitter(Qt.Vertical, self) - self.setCentralWidget(self.splitter) - self.topFrame = QFrame() + self.splitter = QSplitter(Qt.Vertical) + self.topFrame = EmittingFrame() self.topFrame.setFrameShape(QFrame.StyledPanel) self.horizontalLayout = QGridLayout() # Minimum width for the toolbar in the middle: @@ -73,6 +73,10 @@ class DetailsDialog(DetailsDialogBase): # Late population needed here for connections to the toolbar self.vController.setupViewers( self.selectedImageViewer, self.referenceImageViewer) + # self.setCentralWidget(self.splitter) # only as QMainWindow + self.setWidget(self.splitter) # only as QDockWidget + + self.topFrame.resized.connect(self.resizeEvent) def _update(self): if self.vController is None: # Not yet constructed! @@ -88,24 +92,9 @@ class DetailsDialog(DetailsDialogBase): self.vController.updateView(ref, dupe, group) # --- Override + @pyqtSlot(QResizeEvent) def resizeEvent(self, event): - # HACK referenceViewer might be 1 pixel shorter in width - # due to the toolbar in the middle keeping the same width, - # so resizing in the GridLayout's engine leads to not enough space - # left for the panel on the right. - # This ensures same size while shrinking at least: - self.horizontalLayout.setColumnMinimumWidth( - 0, self.selectedImageViewer.size().width()) - self.horizontalLayout.setColumnMinimumWidth( - 2, self.selectedImageViewer.size().width()) - # This works when expanding but it's ugly: - if self.selectedImageViewer.size().width() > self.referenceImageViewer.size().width(): - # print(f"""Before selected size: {self.selectedImageViewer.size()}\n""", - # f"""Before reference size: {self.referenceImageViewer.size()}""") - self.selectedImageViewer.resize(self.referenceImageViewer.size()) - # print(f"""After selected size: {self.selectedImageViewer.size()}\n""", - # f"""After reference size: {self.referenceImageViewer.size()}""") - + self.ensure_same_sizes() if self.vController is None or not self.vController.bestFit: return # Only update the scaled down pixmaps @@ -117,12 +106,44 @@ class DetailsDialog(DetailsDialogBase): self.tableView.setMaximumHeight( self.tableView.rowHeight(1) * self.tableModel.model.row_count() - + self.tableView.verticalHeader().sectionSize(0)) + + self.tableView.verticalHeader().sectionSize(0) + # Windows seems to add a few pixels more to the table somehow + + (5 if ISWINDOWS else 0)) DetailsDialogBase.show(self) + self.ensure_same_sizes() self._update() + def ensure_same_sizes(self): + # 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, + # so resizing in the GridLayout's engine leads to not enough space + # left for the panel on the right. + # This work as a QMainWindow, but doesn't work as a QDockWidget: + # resize can only grow. Might need some custom sizeHint somewhere... + # self.horizontalLayout.setColumnMinimumWidth( + # 0, self.selectedImageViewer.size().width()) + # self.horizontalLayout.setColumnMinimumWidth( + # 2, self.selectedImageViewer.size().width()) + + # This works when expanding but it's ugly: + if self.selectedImageViewer.size().width() > self.referenceImageViewer.size().width(): + # print(f"""Before selected size: {self.selectedImageViewer.size()}\n""", + # f"""Before reference size: {self.referenceImageViewer.size()}""") + self.selectedImageViewer.resize(self.referenceImageViewer.size()) + # print(f"""After selected size: {self.selectedImageViewer.size()}\n""", + # f"""After reference size: {self.referenceImageViewer.size()}""") + # model --> view def refresh(self): DetailsDialogBase.refresh(self) if self.isVisible(): self._update() + + +class EmittingFrame(QFrame): + """Emits a signal whenever is resized""" + resized = pyqtSignal(QResizeEvent) + + def resizeEvent(self, event): + self.resized.emit(event) diff --git a/qt/pe/image_viewer.py b/qt/pe/image_viewer.py index 54366e98..08a70db8 100644 --- a/qt/pe/image_viewer.py +++ b/qt/pe/image_viewer.py @@ -10,6 +10,7 @@ from PyQt5.QtWidgets import ( QToolBar, QToolButton, QAction, QWidget, QScrollArea, QApplication, QAbstractScrollArea, QStyle) from hscommon.trans import trget +from hscommon.plat import ISLINUX tr = trget("ui") MAX_SCALE = 12.0 @@ -21,7 +22,7 @@ def createActions(actions, target): for name, shortcut, icon, desc, func in actions: action = QAction(target) if icon: - action.setIcon(QIcon.fromTheme(icon)) + action.setIcon(icon) if shortcut: action.setShortcut(shortcut) action.setText(desc) @@ -48,28 +49,32 @@ class ViewerToolBar(QToolBar): ( "actionZoomIn", QKeySequence.ZoomIn, - "zoom-in", + QIcon.fromTheme("zoom-in") if ISLINUX + else QIcon(QPixmap(":/" + "zoom_in")), tr("Increase zoom"), controller.zoomIn, ), ( "actionZoomOut", QKeySequence.ZoomOut, - "zoom-out", + QIcon.fromTheme("zoom-out") if ISLINUX + else QIcon(QPixmap(":/" + "zoom_out")), tr("Decrease zoom"), controller.zoomOut, ), ( "actionNormalSize", tr("Ctrl+/"), - "zoom-original", + QIcon.fromTheme("zoom-original") if ISLINUX + else QIcon(QPixmap(":/" + "zoom_original")), tr("Normal size"), controller.zoomNormalSize, ), ( "actionBestFit", tr("Ctrl+*"), - "zoom-best-fit", + QIcon.fromTheme("zoom-best-fit") if ISLINUX + else QIcon(QPixmap(":/" + "zoom_best_fit")), tr("Best fit"), controller.zoomBestFit, ) @@ -83,7 +88,9 @@ class ViewerToolBar(QToolBar): self.buttonImgSwap.setToolButtonStyle(Qt.ToolButtonIconOnly) self.buttonImgSwap.setIcon( QIcon.fromTheme('view-refresh', - self.style().standardIcon(QStyle.SP_BrowserReload))) + self.style().standardIcon(QStyle.SP_BrowserReload)) + if ISLINUX + else QIcon(QPixmap(":/" + "exchange"))) self.buttonImgSwap.setText('Swap images') self.buttonImgSwap.setToolTip('Swap images') self.buttonImgSwap.pressed.connect(self.controller.swapImages) @@ -142,6 +149,7 @@ class BaseController(QObject): self.bestFit = True self.parent = parent # To change buttons' states self.cached_group = None + self.same_dimensions = True def setupViewers(self, selectedViewer, referenceViewer): self.selectedViewer = selectedViewer @@ -156,6 +164,8 @@ class BaseController(QObject): def updateView(self, ref, dupe, group): # To keep current scale accross dupes from the same group + previous_same_dimensions = self.same_dimensions + self.same_dimensions = True same_group = True if group != self.cached_group: same_group = False @@ -164,6 +174,7 @@ class BaseController(QObject): self.selectedPixmap = QPixmap(str(dupe.path)) if ref is dupe: # currently selected file is the actual reference file + # self.same_dimensions = False self.referencePixmap = QPixmap() self.scaledReferencePixmap = QPixmap() self.parent.verticalToolBar.buttonImgSwap.setEnabled(False) @@ -171,7 +182,10 @@ class BaseController(QObject): else: self.referencePixmap = QPixmap(str(ref.path)) self.parent.verticalToolBar.buttonImgSwap.setEnabled(True) + if ref.dimensions != dupe.dimensions: + self.same_dimensions = False self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) + self.updateButtonsAsPerDimensions(previous_same_dimensions) self.updateBothImages(same_group) self.centerViews(same_group and self.referencePixmap.isNull()) @@ -207,11 +221,11 @@ class BaseController(QObject): # zoomed in state, expand # only if not same_group, we need full update scaledpixmap = pixmap.scaled( - target_size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) + target_size, Qt.KeepAspectRatioByExpanding, Qt.FastTransformation) else: # best fit, keep ratio always scaledpixmap = pixmap.scaled( - target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation) + target_size, Qt.KeepAspectRatio, Qt.FastTransformation) viewer.setImage(scaledpixmap) return target_size @@ -294,6 +308,22 @@ class BaseController(QObject): 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): + if not self.same_dimensions: + self.parent.verticalToolBar.buttonZoomIn.setEnabled(False) + self.parent.verticalToolBar.buttonZoomOut.setEnabled(False) + if not self.bestFit: + self.zoomBestFit() + self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) + if not self.referencePixmap.isNull(): + self.parent.verticalToolBar.buttonImgSwap.setEnabled(True) + return + if not self.bestFit and not previous_same_dimensions: + self.zoomBestFit() + self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) + if self.referencePixmap.isNull(): + self.parent.verticalToolBar.buttonImgSwap.setEnabled(False) + @pyqtSlot() def zoomBestFit(self): """Setup before scaling to bestfit""" @@ -320,6 +350,7 @@ class BaseController(QObject): self.parent.verticalToolBar.buttonZoomOut.setEnabled(False) self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) self.parent.verticalToolBar.buttonBestFit.setEnabled(False) + self.parent.verticalToolBar.buttonImgSwap.setEnabled(True) def setBestFit(self, value): self.bestFit = value @@ -339,8 +370,12 @@ class BaseController(QObject): self.selectedViewer.scaleToNormalSize() self.referenceViewer.scaleToNormalSize() - self.parent.verticalToolBar.buttonZoomIn.setEnabled(True) - self.parent.verticalToolBar.buttonZoomOut.setEnabled(True) + if self.same_dimensions: + self.parent.verticalToolBar.buttonZoomIn.setEnabled(True) + self.parent.verticalToolBar.buttonZoomOut.setEnabled(True) + else: + # we can't allow swapping pixmaps of different dimensions + self.parent.verticalToolBar.buttonImgSwap.setEnabled(False) self.parent.verticalToolBar.buttonNormalSize.setEnabled(False) self.parent.verticalToolBar.buttonBestFit.setEnabled(True) @@ -361,8 +396,16 @@ class QWidgetController(BaseController): def __init__(self, parent): super().__init__(parent) + def _updateImage(self, *args): + ret = super()._updateImage(*args) + # Fix alignment when resizing window + self.centerViews() + return ret + @pyqtSlot(QPointF) def onDraggedMouse(self, delta): + if not self.same_dimensions: + return if self.sender() is self.referenceViewer: self.selectedViewer.onDraggedMouse(delta) else: @@ -400,8 +443,14 @@ class ScrollAreaController(BaseController): self.selectedViewer.ignore_signal = True self.referenceViewer.ignore_signal = True - self.selectedViewer.onDraggedMouse(delta) - self.referenceViewer.onDraggedMouse(delta) + if self.same_dimensions: + self.selectedViewer.onDraggedMouse(delta) + self.referenceViewer.onDraggedMouse(delta) + else: + if self.sender() is self.selectedViewer: + self.selectedViewer.onDraggedMouse(delta) + else: + self.referenceViewer.onDraggedMouse(delta) self.selectedViewer.ignore_signal = False self.referenceViewer.ignore_signal = False @@ -422,6 +471,8 @@ class ScrollAreaController(BaseController): @pyqtSlot(int) def onVScrollBarChanged(self, value): + if not self.same_dimensions: + return if self.sender() is self.referenceViewer._verticalScrollBar: if not self.selectedViewer.ignore_signal: self.selectedViewer._verticalScrollBar.setValue(value) @@ -431,6 +482,8 @@ class ScrollAreaController(BaseController): @pyqtSlot(int) def onHScrollBarChanged(self, value): + if not self.same_dimensions: + return if self.sender() is self.referenceViewer._horizontalScrollBar: if not self.selectedViewer.ignore_signal: self.selectedViewer._horizontalScrollBar.setValue(value) @@ -448,6 +501,8 @@ class ScrollAreaController(BaseController): def zoomBestFit(self): # Disable scrollbars to avoid GridLayout size rounding glitch super().zoomBestFit() + if self.referencePixmap.isNull(): + self.parent.verticalToolBar.buttonImgSwap.setEnabled(False) self.selectedViewer.toggleScrollBars() self.referenceViewer.toggleScrollBars() @@ -484,6 +539,8 @@ class GraphicsViewController(BaseController): @pyqtSlot(int) def onVScrollBarChanged(self, value): + if not self.same_dimensions: + return if self.sender() is self.referenceViewer._verticalScrollBar: if not self.selectedViewer.ignore_signal: self.selectedViewer._verticalScrollBar.setValue(value) @@ -493,6 +550,8 @@ class GraphicsViewController(BaseController): @pyqtSlot(int) def onHScrollBarChanged(self, value): + if not self.same_dimensions: + return if self.sender() is self.referenceViewer._horizontalScrollBar: if not self.selectedViewer.ignore_signal: self.selectedViewer._horizontalScrollBar.setValue(value) @@ -518,9 +577,16 @@ class GraphicsViewController(BaseController): self.parent.verticalToolBar.buttonZoomOut.setEnabled(False) self.parent.verticalToolBar.buttonZoomIn.setEnabled(False) self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) + if not self.referencePixmap.isNull(): + self.parent.verticalToolBar.buttonImgSwap.setEnabled(True) + # else: + # self.referenceViewer.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + # self.referenceViewer.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def updateView(self, ref, dupe, group): # Keep current scale accross dupes from the same group + previous_same_dimensions = self.same_dimensions + self.same_dimensions = True same_group = True if group != self.cached_group: same_group = False @@ -529,16 +595,19 @@ class GraphicsViewController(BaseController): self.selectedPixmap = QPixmap(str(dupe.path)) if ref is dupe: # currently selected file is the actual reference file + self.same_dimensions = False self.referencePixmap = QPixmap() self.parent.verticalToolBar.buttonImgSwap.setEnabled(False) self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) else: self.referencePixmap = QPixmap(str(ref.path)) self.parent.verticalToolBar.buttonImgSwap.setEnabled(True) + if ref.dimensions != dupe.dimensions: + self.same_dimensions = False self.parent.verticalToolBar.buttonNormalSize.setEnabled(True) - - self.selectedViewer.setImage(self.selectedPixmap) - self.referenceViewer.setImage(self.referencePixmap) + # self.selectedViewer.setImage(self.selectedPixmap) + # self.referenceViewer.setImage(self.referencePixmap) + self.updateButtonsAsPerDimensions(previous_same_dimensions) self.updateBothImages(same_group) def updateBothImages(self, same_group=False): @@ -557,7 +626,9 @@ class GraphicsViewController(BaseController): def _updateFitImage(self, pixmap, viewer): # If not same_group, we need full update""" + viewer.setImage(pixmap) if pixmap.isNull(): + # viewer._item = None return if viewer.bestFit: viewer.fitScale() @@ -717,7 +788,7 @@ class QWidgetImageViewer(QWidget): self.setMouseTracking(False) def wheelEvent(self, event): - if self.bestFit or not self.isEnabled(): + if self.bestFit or not self.controller.same_dimensions or not self.isEnabled(): event.ignore() return @@ -910,7 +981,7 @@ class ScrollAreaImageViewer(QScrollArea): super().mouseReleaseEvent(event) def wheelEvent(self, event): - if self.bestFit: + if self.bestFit or not self.controller.same_dimensions: event.ignore() return oldScale = self.current_scale @@ -1034,7 +1105,7 @@ class ScrollAreaImageViewer(QScrollArea): class GraphicsViewViewer(QGraphicsView): - """Re-Implementation using a more full fledged class.""" + """Re-Implementation a full-fledged GraphicsView but is a bit buggy.""" mouseDragged = pyqtSignal() mouseWheeled = pyqtSignal(float, QPointF) @@ -1163,7 +1234,7 @@ class GraphicsViewViewer(QGraphicsView): self._centerPoint = self.mapToScene(self.rect().center()) def wheelEvent(self, event): - if self.bestFit or MIN_SCALE > self.current_scale > MAX_SCALE: + if self.bestFit or MIN_SCALE > self.current_scale > MAX_SCALE or not self.controller.same_dimensions: event.ignore() return pointBeforeScale = QPointF(self.mapToScene(self.mapFromGlobal(QCursor.pos()))) @@ -1186,6 +1257,10 @@ class GraphicsViewViewer(QGraphicsView): self.other_viewer.ignore_signal = False def setImage(self, pixmap): + if pixmap.isNull(): + self.ignore_signal = True + elif self.ignore_signal: + self.ignore_signal = False self._pixmap = pixmap self._item.setPixmap(pixmap) self.translate(1, 1) @@ -1241,7 +1316,7 @@ class GraphicsViewViewer(QGraphicsView): """Called when the pixmap is set back to original size.""" self.bestFit = False self.scaleAt(1.0) - self.toggleScrollBars() + self.toggleScrollBars(True) self.update() def adjustScrollBarsScaled(self, delta): diff --git a/qt/preferences.py b/qt/preferences.py index c9691cca..39e55b1e 100644 --- a/qt/preferences.py +++ b/qt/preferences.py @@ -31,6 +31,8 @@ class Preferences(PreferencesBase): self.tableFontSize = get("TableFontSize", self.tableFontSize) self.reference_bold_font = get('ReferenceBoldFont', self.reference_bold_font) + self.details_dialog_titlebar_enabled = get('DetailsDialogTitleBarEnabled', self.details_dialog_titlebar_enabled) + self.details_dialog_vertical_titlebar = get('DetailsDialogVerticalTitleBar', self.details_dialog_vertical_titlebar) self.resultWindowIsMaximized = get( "ResultWindowIsMaximized", self.resultWindowIsMaximized ) @@ -67,6 +69,8 @@ class Preferences(PreferencesBase): self.tableFontSize = QApplication.font().pointSize() self.reference_bold_font = True + self.details_dialog_titlebar_enabled = True + self.details_dialog_vertical_titlebar = True self.resultWindowIsMaximized = False self.resultWindowRect = None self.directoriesWindowRect = None @@ -100,6 +104,8 @@ class Preferences(PreferencesBase): set_("TableFontSize", self.tableFontSize) set_('ReferenceBoldFont', self.reference_bold_font) + set_('DetailsDialogTitleBarEnabled', self.details_dialog_titlebar_enabled) + set_('DetailsDialogVerticalTitleBar', self.details_dialog_vertical_titlebar) set_("ResultWindowIsMaximized", self.resultWindowIsMaximized) self.set_rect("ResultWindowRect", self.resultWindowRect) self.set_rect("DirectoriesWindowRect", self.directoriesWindowRect) diff --git a/qt/preferences_dialog.py b/qt/preferences_dialog.py index eb3462e3..95eb1b67 100644 --- a/qt/preferences_dialog.py +++ b/qt/preferences_dialog.py @@ -117,8 +117,21 @@ class PreferencesDialogBase(QDialog): self.widgetsVLayout.addLayout( horizontalWrap([self.fontSizeLabel, self.fontSizeSpinBox, None]) ) - self._setupAddCheckbox("reference_bold_font", tr("Bold font for reference.")) + self._setupAddCheckbox("reference_bold_font", + tr("Bold font for reference")) self.widgetsVLayout.addWidget(self.reference_bold_font) + + self._setupAddCheckbox("details_dialog_titlebar_enabled", + tr("Details dialog displays a title bar and is dockable")) + self.widgetsVLayout.addWidget(self.details_dialog_titlebar_enabled) + self._setupAddCheckbox("details_dialog_vertical_titlebar", + tr("Details dialog displays a vertical title bar (Linux only)")) + self.widgetsVLayout.addWidget(self.details_dialog_vertical_titlebar) + self.details_dialog_vertical_titlebar.setEnabled( + self.details_dialog_titlebar_enabled.isChecked()) + self.details_dialog_titlebar_enabled.stateChanged.connect( + self.details_dialog_vertical_titlebar.setEnabled) + self.languageLabel = QLabel(tr("Language:"), self) self.languageComboBox = QComboBox(self) for lang in self.supportedLanguages: @@ -190,6 +203,8 @@ class PreferencesDialogBase(QDialog): setchecked(self.ignoreHardlinkMatches, prefs.ignore_hardlink_matches) setchecked(self.debugModeBox, prefs.debug_mode) setchecked(self.reference_bold_font, prefs.reference_bold_font) + setchecked(self.details_dialog_titlebar_enabled , prefs.details_dialog_titlebar_enabled) + setchecked(self.details_dialog_vertical_titlebar, prefs.details_dialog_vertical_titlebar) self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type) self.customCommandEdit.setText(prefs.custom_command) self.fontSizeSpinBox.setValue(prefs.tableFontSize) @@ -210,6 +225,8 @@ class PreferencesDialogBase(QDialog): prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches) prefs.debug_mode = ischecked(self.debugModeBox) prefs.reference_bold_font = ischecked(self.reference_bold_font) + prefs.details_dialog_titlebar_enabled = ischecked(self.details_dialog_titlebar_enabled) + prefs.details_dialog_vertical_titlebar = ischecked(self.details_dialog_vertical_titlebar) prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex() prefs.custom_command = str(self.customCommandEdit.text()) prefs.tableFontSize = self.fontSizeSpinBox.value() diff --git a/qt/se/details_dialog.py b/qt/se/details_dialog.py index 812c649f..0f922dc4 100644 --- a/qt/se/details_dialog.py +++ b/qt/se/details_dialog.py @@ -5,7 +5,7 @@ # http://www.gnu.org/licenses/gpl-3.0.html from PyQt5.QtCore import QSize -from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView +from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView, QWidget from hscommon.trans import trget from ..details_dialog import DetailsDialog as DetailsDialogBase @@ -27,3 +27,6 @@ class DetailsDialog(DetailsDialogBase): self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setShowGrid(False) self.verticalLayout.addWidget(self.tableView) + self.centralWidget = QWidget() + self.centralWidget.setLayout(self.verticalLayout) + self.setWidget(self.centralWidget) diff --git a/qtlib/about_box.py b/qtlib/about_box.py index 88512666..99c3a059 100644 --- a/qtlib/about_box.py +++ b/qtlib/about_box.py @@ -42,7 +42,7 @@ class AboutBox(QDialog): self.setWindowTitle( tr("About {}").format(QCoreApplication.instance().applicationName()) ) - self.resize(400, 190) + self.resize(400, 290) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -69,6 +69,21 @@ class AboutBox(QDialog): self.verticalLayout.addWidget(self.label_3) self.label_3.setText(tr("Licensed under GPLv3")) self.label = QLabel(self) + self.label_4 = QLabel(self) + self.label_4.setWordWrap(True) + self.label_4.setTextFormat(Qt.RichText) + self.label_4.setOpenExternalLinks(True) + self.label_4.setText(tr( + """Exchange icon + made by Jason Cho (used with permission). +
+Zoom In +Zoom Out +Zoomt Best Fit +Zoom Original + icons made by schollidesign + (licensed under GPL).""")) + self.verticalLayout.addWidget(self.label_4) font = QFont() font.setWeight(75) font.setBold(True)