Merge branch 'master' into exclude_list

This commit is contained in:
glubsy 2020-10-28 03:58:05 +01:00
commit 680cb581c1
15 changed files with 93 additions and 46 deletions

View File

@ -1,6 +1,8 @@
To know who contributed to dupeGuru, you can look at the commit log, but not all contributions To know who contributed to dupeGuru, you can look at the commit log, but not all contributions
result in a commit. This file lists contributors who don't necessarily appear in the commit log. result in a commit. This file lists contributors who don't necessarily appear in the commit log.
* Jason Cho, Exchange icon
* schollidesign (https://findicons.com/pack/1035/human_o2), Zoom-in, Zoom-out, Zoom-best-fit, Zoom-original icons
* Jérôme Cantin, Main icon * Jérôme Cantin, Main icon
* Gregor Tätzner, German localization * Gregor Tätzner, German localization
* Frank Weber, German localization * Frank Weber, German localization

View File

@ -595,6 +595,12 @@ class DupeGuru(Broadcaster):
self.exclude_list.load_from_xml(p) self.exclude_list.load_from_xml(p)
self.exclude_list_dialog.refresh() self.exclude_list_dialog.refresh()
def load_directories(self, filepath):
# Clear out previous entries
self.directories.__init__()
self.directories.load_from_file(filepath)
self.notify("directories_changed")
def load_from(self, filename): def load_from(self, filename):
"""Start an async job to load results from ``filename``. """Start an async job to load results from ``filename``.
@ -794,6 +800,16 @@ class DupeGuru(Broadcaster):
except OSError as e: except OSError as e:
self.view.show_message(tr("Couldn't write to file: {}").format(str(e))) self.view.show_message(tr("Couldn't write to file: {}").format(str(e)))
def save_directories_as(self, filename):
"""Save directories in ``filename``.
:param str filename: path of the file to save directories (as XML) to.
"""
try:
self.directories.save_to_file(filename)
except OSError as e:
self.view.show_message(tr("Couldn't write to file: {}").format(str(e)))
def start_scanning(self): def start_scanning(self):
"""Starts an async job to scan for duplicates. """Starts an async job to scan for duplicates.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -92,12 +92,14 @@ class DirectoriesDialog(QMainWindow):
self.app.showResultsWindow, self.app.showResultsWindow,
), ),
("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered), ("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered),
("actionLoadDirectories", "", "", tr("Load Directories..."), self.loadDirectoriesTriggered),
("actionSaveDirectories", "", "", tr("Save Directories..."), self.saveDirectoriesTriggered),
] ]
createActions(ACTIONS, self) createActions(ACTIONS, self)
# if self.app.use_tabs: if self.app.use_tabs:
# # Keep track of actions which should only be accessible from this class # Keep track of actions which should only be accessible from this window
# for action, _, _, _, _ in ACTIONS: self.specific_actions.add(self.actionLoadDirectories)
# self.specific_actions.add(getattr(self, action)) self.specific_actions.add(self.actionSaveDirectories)
def _setupMenu(self): def _setupMenu(self):
if not self.app.use_tabs: if not self.app.use_tabs:
@ -127,6 +129,9 @@ class DirectoriesDialog(QMainWindow):
self.menuFile.addSeparator() self.menuFile.addSeparator()
self.menuFile.addAction(self.app.actionClearPictureCache) self.menuFile.addAction(self.app.actionClearPictureCache)
self.menuFile.addSeparator() self.menuFile.addSeparator()
self.menuFile.addAction(self.actionLoadDirectories)
self.menuFile.addAction(self.actionSaveDirectories)
self.menuFile.addSeparator()
self.menuFile.addAction(self.app.actionQuit) self.menuFile.addAction(self.app.actionQuit)
self.menuView.addAction(self.app.actionDirectoriesWindow) self.menuView.addAction(self.app.actionDirectoriesWindow)
@ -328,9 +333,25 @@ class DirectoriesDialog(QMainWindow):
self.app.model.load_from(destination) self.app.model.load_from(destination)
self.app.recentResults.insertItem(destination) self.app.recentResults.insertItem(destination)
def loadDirectoriesTriggered(self):
title = tr("Select a directories file to load")
files = ";;".join([tr("dupeGuru Results (*.dupegurudirs)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, "", files)[0]
if destination:
self.app.model.load_directories(destination)
def removeFolderButtonClicked(self): def removeFolderButtonClicked(self):
self.directoriesModel.model.remove_selected() self.directoriesModel.model.remove_selected()
def saveDirectoriesTriggered(self):
title = tr("Select a file to save your directories to")
files = tr("dupeGuru Directories (*.dupegurudirs)")
destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files)
if destination:
if not destination.endswith(".dupegurudirs"):
destination = "{}.dupegurudirs".format(destination)
self.app.model.save_directories_as(destination)
def scanButtonClicked(self): def scanButtonClicked(self):
if self.app.model.results.is_modified: if self.app.model.results.is_modified:
title = tr("Start a new scan") title = tr("Start a new scan")

View File

@ -19,14 +19,8 @@ class DetailsDialog(DetailsDialogBase):
self.setWindowTitle(tr("Details")) self.setWindowTitle(tr("Details"))
self.resize(502, 295) self.resize(502, 295)
self.setMinimumSize(QSize(250, 250)) self.setMinimumSize(QSize(250, 250))
# self.verticalLayout = QVBoxLayout(self)
# self.verticalLayout.setSpacing(0)
# self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.tableView = DetailsTable(self) self.tableView = DetailsTable(self)
self.tableView.setAlternatingRowColors(True) self.tableView.setAlternatingRowColors(True)
self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableView.setShowGrid(False) self.tableView.setShowGrid(False)
# self.verticalLayout.addWidget(self.tableView)
# self.centralWidget = QWidget()
# self.centralWidget.setLayout(self.verticalLayout)
self.setWidget(self.tableView) self.setWidget(self.tableView)

View File

@ -128,11 +128,7 @@ class DetailsDialog(DetailsDialogBase):
# This works when expanding but it's ugly: # This works when expanding but it's ugly:
if self.selectedImageViewer.size().width() > self.referenceImageViewer.size().width(): 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()) self.selectedImageViewer.resize(self.referenceImageViewer.size())
# print(f"""After selected size: {self.selectedImageViewer.size()}\n""",
# f"""After reference size: {self.referenceImageViewer.size()}""")
# model --> view # model --> view
def refresh(self): def refresh(self):

View File

@ -758,11 +758,15 @@ class QWidgetImageViewer(QWidget):
return return
self.disconnectMouseSignals() self.disconnectMouseSignals()
def contextMenuEvent(self, event):
"""Block parent's (main window) context menu on right click."""
event.accept()
def mousePressEvent(self, event): def mousePressEvent(self, event):
if self.bestFit or not self.isEnabled(): if self.bestFit or not self.isEnabled():
event.ignore() event.ignore()
return return
if event.button() == Qt.LeftButton: if event.button() & (Qt.LeftButton | Qt.MidButton | Qt.RightButton):
self._drag = True self._drag = True
else: else:
self._drag = False self._drag = False
@ -790,8 +794,8 @@ class QWidgetImageViewer(QWidget):
if self.bestFit or not self.isEnabled(): if self.bestFit or not self.isEnabled():
event.ignore() event.ignore()
return return
if event.button() == Qt.LeftButton: # if event.button() == Qt.LeftButton:
self._drag = False self._drag = False
self._app.restoreOverrideCursor() self._app.restoreOverrideCursor()
self.setMouseTracking(False) self.setMouseTracking(False)
@ -956,11 +960,18 @@ class ScrollAreaImageViewer(QScrollArea):
self._horizontalScrollBar.valueChanged.connect( self._horizontalScrollBar.valueChanged.connect(
self.controller.onHScrollBarChanged, Qt.UniqueConnection) self.controller.onHScrollBarChanged, Qt.UniqueConnection)
def contextMenuEvent(self, event):
"""Block parent's (main window) context menu on right click."""
# Even though we don't have a context menu right now, and the default
# contextMenuPolicy is DefaultContextMenu, we leverage that handler to
# avoid raising the Result window's Actions menu
event.accept()
def mousePressEvent(self, event): def mousePressEvent(self, event):
if self.bestFit: if self.bestFit:
event.ignore() event.ignore()
return return
if event.button() == Qt.LeftButton: if event.button() & (Qt.LeftButton | Qt.MidButton | Qt.RightButton):
self._drag = True self._drag = True
else: else:
self._drag = False self._drag = False
@ -985,8 +996,7 @@ class ScrollAreaImageViewer(QScrollArea):
if self.bestFit: if self.bestFit:
event.ignore() event.ignore()
return return
if event.button() == Qt.LeftButton: self._drag = False
self._drag = False
self._app.restoreOverrideCursor() self._app.restoreOverrideCursor()
self.setMouseTracking(False) self.setMouseTracking(False)
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
@ -1203,11 +1213,15 @@ class GraphicsViewViewer(QGraphicsView):
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
def contextMenuEvent(self, event):
"""Block parent's (main window) context menu on right click."""
event.accept()
def mousePressEvent(self, event): def mousePressEvent(self, event):
if self.bestFit: if self.bestFit:
event.ignore() event.ignore()
return return
if event.button() == Qt.LeftButton: if event.button() & (Qt.LeftButton | Qt.MidButton | Qt.RightButton):
self._drag = True self._drag = True
else: else:
self._drag = False self._drag = False
@ -1223,8 +1237,7 @@ class GraphicsViewViewer(QGraphicsView):
if self.bestFit: if self.bestFit:
event.ignore() event.ignore()
return return
if event.button() == Qt.LeftButton: self._drag = False
self._drag = False
self._app.restoreOverrideCursor() self._app.restoreOverrideCursor()
self.setMouseTracking(False) self.setMouseTracking(False)
self.updateCenterPoint() self.updateCenterPoint()

View File

@ -29,6 +29,15 @@ class File(PhotoBase):
def _plat_get_blocks(self, block_count_per_side, orientation): def _plat_get_blocks(self, block_count_per_side, orientation):
image = QImage(str(self.path)) image = QImage(str(self.path))
image = image.convertToFormat(QImage.Format_RGB888) image = image.convertToFormat(QImage.Format_RGB888)
if type(orientation) == str:
logging.warning("Orientation for file '%s' was a str '%s', not an int.",
str(self.path), orientation)
try:
orientation = int(orientation)
except Exception as e:
logging.exception("Skipping transformation because could not \
convert str to int. %s", e)
return getblocks(image, block_count_per_side)
# MYSTERY TO SOLVE: For reasons I cannot explain, orientations 5 and 7 don't work for # MYSTERY TO SOLVE: For reasons I cannot explain, orientations 5 and 7 don't work for
# duplicate scanning. The transforms seems to work fine (if I try to save the image after # duplicate scanning. The transforms seems to work fine (if I try to save the image after
# the transform, we see that the image has been correctly flipped and rotated), but the # the transform, we see that the image has been correctly flipped and rotated), but the

View File

@ -49,6 +49,8 @@ class Preferences(PreferencesBase):
self.result_table_ref_foreground_color =\ self.result_table_ref_foreground_color =\
get("ResultTableRefForegroundColor", self.result_table_ref_foreground_color) get("ResultTableRefForegroundColor", self.result_table_ref_foreground_color)
self.result_table_ref_background_color =\
get("ResultTableRefBackgroundColor", self.result_table_ref_background_color)
self.result_table_delta_foreground_color =\ self.result_table_delta_foreground_color =\
get("ResultTableDeltaForegroundColor", self.result_table_delta_foreground_color) get("ResultTableDeltaForegroundColor", self.result_table_delta_foreground_color)
@ -100,6 +102,7 @@ class Preferences(PreferencesBase):
self.details_dialog_override_theme_icons = False if not ISLINUX else True self.details_dialog_override_theme_icons = False if not ISLINUX else True
self.details_dialog_viewers_show_scrollbars = True self.details_dialog_viewers_show_scrollbars = True
self.result_table_ref_foreground_color = QColor(Qt.blue) self.result_table_ref_foreground_color = QColor(Qt.blue)
self.result_table_ref_background_color = QColor(Qt.darkGray)
self.result_table_delta_foreground_color = QColor(255, 142, 40) # orange self.result_table_delta_foreground_color = QColor(255, 142, 40) # orange
self.resultWindowIsMaximized = False self.resultWindowIsMaximized = False
self.resultWindowRect = None self.resultWindowRect = None
@ -143,6 +146,7 @@ class Preferences(PreferencesBase):
set_("DetailsDialogViewersShowScrollbars", self.details_dialog_viewers_show_scrollbars) set_("DetailsDialogViewersShowScrollbars", self.details_dialog_viewers_show_scrollbars)
set_("DetailsTableDeltaForegroundColor", self.details_table_delta_foreground_color) set_("DetailsTableDeltaForegroundColor", self.details_table_delta_foreground_color)
set_("ResultTableRefForegroundColor", self.result_table_ref_foreground_color) set_("ResultTableRefForegroundColor", self.result_table_ref_foreground_color)
set_("ResultTableRefBackgroundColor", self.result_table_ref_background_color)
set_("ResultTableDeltaForegroundColor", self.result_table_delta_foreground_color) set_("ResultTableDeltaForegroundColor", self.result_table_delta_foreground_color)
set_("ResultWindowIsMaximized", self.resultWindowIsMaximized) set_("ResultWindowIsMaximized", self.resultWindowIsMaximized)
set_("MainWindowIsMaximized", self.mainWindowIsMaximized) set_("MainWindowIsMaximized", self.mainWindowIsMaximized)

View File

@ -175,6 +175,9 @@ On MacOS, the tab bar will fill up the window's width instead."))
self.result_table_ref_foreground_color = ColorPickerButton(self) self.result_table_ref_foreground_color = ColorPickerButton(self)
formlayout.addRow(tr("Reference foreground color:"), formlayout.addRow(tr("Reference foreground color:"),
self.result_table_ref_foreground_color) self.result_table_ref_foreground_color)
self.result_table_ref_background_color = ColorPickerButton(self)
gridlayout.addRow(tr("Reference background color:"),
self.result_table_ref_background_color)
self.result_table_delta_foreground_color = ColorPickerButton(self) self.result_table_delta_foreground_color = ColorPickerButton(self)
formlayout.addRow(tr("Delta foreground color:"), formlayout.addRow(tr("Delta foreground color:"),
self.result_table_delta_foreground_color) self.result_table_delta_foreground_color)
@ -291,6 +294,8 @@ use the modifier key to drag the floating window around") if ISLINUX else
prefs.details_table_delta_foreground_color) prefs.details_table_delta_foreground_color)
self.result_table_ref_foreground_color.setColor( self.result_table_ref_foreground_color.setColor(
prefs.result_table_ref_foreground_color) prefs.result_table_ref_foreground_color)
self.result_table_ref_background_color.setColor(
prefs.result_table_ref_background_color)
self.result_table_delta_foreground_color.setColor( self.result_table_delta_foreground_color.setColor(
prefs.result_table_delta_foreground_color) prefs.result_table_delta_foreground_color)
try: try:
@ -314,6 +319,7 @@ use the modifier key to drag the floating window around") if ISLINUX else
prefs.details_dialog_vertical_titlebar = ischecked(self.details_dialog_vertical_titlebar) prefs.details_dialog_vertical_titlebar = ischecked(self.details_dialog_vertical_titlebar)
prefs.details_table_delta_foreground_color = self.details_table_delta_foreground_color.color prefs.details_table_delta_foreground_color = self.details_table_delta_foreground_color.color
prefs.result_table_ref_foreground_color = self.result_table_ref_foreground_color.color prefs.result_table_ref_foreground_color = self.result_table_ref_foreground_color.color
prefs.result_table_ref_background_color = self.result_table_ref_background_color.color
prefs.result_table_delta_foreground_color = self.result_table_delta_foreground_color.color prefs.result_table_delta_foreground_color = self.result_table_delta_foreground_color.color
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex() prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
prefs.custom_command = str(self.customCommandEdit.text()) prefs.custom_command = str(self.customCommandEdit.text())

View File

@ -29,6 +29,8 @@ class ResultsModel(Table):
def _getData(self, row, column, role): def _getData(self, row, column, role):
if column.name == "marked": if column.name == "marked":
if role == Qt.BackgroundRole and row.isref:
return QBrush(self.prefs.result_table_ref_background_color)
if role == Qt.CheckStateRole and row.markable: if role == Qt.CheckStateRole and row.markable:
return Qt.Checked if row.marked else Qt.Unchecked return Qt.Checked if row.marked else Qt.Unchecked
return None return None
@ -40,6 +42,9 @@ class ResultsModel(Table):
return QBrush(self.prefs.result_table_ref_foreground_color) return QBrush(self.prefs.result_table_ref_foreground_color)
elif row.is_cell_delta(column.name): elif row.is_cell_delta(column.name):
return QBrush(self.prefs.result_table_delta_foreground_color) return QBrush(self.prefs.result_table_delta_foreground_color)
elif role == Qt.BackgroundRole:
if row.isref:
return QBrush(self.prefs.result_table_ref_background_color)
elif role == Qt.FontRole: elif role == Qt.FontRole:
font = QFont(self.view.font()) font = QFont(self.view.font())
if self.prefs.reference_bold_font: if self.prefs.reference_bold_font:

View File

@ -19,14 +19,8 @@ class DetailsDialog(DetailsDialogBase):
self.setWindowTitle(tr("Details")) self.setWindowTitle(tr("Details"))
self.resize(502, 186) self.resize(502, 186)
self.setMinimumSize(QSize(200, 0)) self.setMinimumSize(QSize(200, 0))
# self.verticalLayout = QVBoxLayout()
# self.verticalLayout.setSpacing(0)
# self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.tableView = DetailsTable(self) self.tableView = DetailsTable(self)
self.tableView.setAlternatingRowColors(True) self.tableView.setAlternatingRowColors(True)
self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableView.setShowGrid(False) self.tableView.setShowGrid(False)
# self.verticalLayout.addWidget(self.tableView)
# self.centralWidget = QWidget()
# self.centralWidget.setLayout(self.verticalLayout)
self.setWidget(self.tableView) self.setWidget(self.tableView)

View File

@ -69,21 +69,6 @@ class AboutBox(QDialog):
self.verticalLayout.addWidget(self.label_3) self.verticalLayout.addWidget(self.label_3)
self.label_3.setText(tr("Licensed under GPLv3")) self.label_3.setText(tr("Licensed under GPLv3"))
self.label = QLabel(self) 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(
"""<img src=":/exchange" alt="Exchange" width="16" height="16"> icon
made by <a href="http://jasoncho.ca/"> Jason Cho</a> (used with permission).
<br>
<img src=":/zoom_in" alt="Zoom In" width="16" height="16">
<img src=":/zoom_out" alt="Zoom Out" width="16" height="16">
<img src=":/zoom_best_fit" alt="Zoomt Best Fit" width="16" height="16">
<img src=":/zoom_original" alt="Zoom Original" width="16" height="16">
icons made by <a href="https://findicons.com/pack/1035/human_o2">schollidesign</a>
(licensed under GPL)."""))
self.verticalLayout.addWidget(self.label_4)
font = QFont() font = QFont()
font.setWeight(75) font.setWeight(75)
font.setBold(True) font.setBold(True)

View File

@ -121,8 +121,10 @@ class Preferences(QObject):
self._settings.setValue(name, normalize_for_serialization(value)) self._settings.setValue(name, normalize_for_serialization(value))
def saveGeometry(self, name, widget): def saveGeometry(self, name, widget):
# We save geometry under a 5-sized int array: first item is a flag for whether the widget # We save geometry under a 7-sized int array: first item is a flag
# is maximized and the other 4 are (x, y, w, h). # for whether the widget is maximized, second item is a flag for whether
# the widget is docked, third item is a Qt::DockWidgetArea enum value,
# and the other 4 are (x, y, w, h).
m = 1 if widget.isMaximized() else 0 m = 1 if widget.isMaximized() else 0
d = 1 if isinstance(widget, QDockWidget) and not widget.isFloating() else 0 d = 1 if isinstance(widget, QDockWidget) and not widget.isFloating() else 0
area = widget.parent.dockWidgetArea(widget) if d else 0 area = widget.parent.dockWidgetArea(widget) if d else 0