From e41a6b878cea95e7ad65d2714268f7237fe2e073 Mon Sep 17 00:00:00 2001 From: glubsy Date: Tue, 30 Jun 2020 01:02:56 +0200 Subject: [PATCH 01/13] Allow moving rows around in details table * Replaces the "Attribute" column with a horizontal header * We ignore the first value in each row from the model and instead populate a horizontal header with the value in order to allow --- qt/details_table.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/qt/details_table.py b/qt/details_table.py index 1e353f17..92c2fdf1 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -13,7 +13,7 @@ from hscommon.trans import trget tr = trget("ui") -HEADER = [tr("Attribute"), tr("Selected"), tr("Reference")] +HEADER = [tr("Selected"), tr("Reference")] class DetailsModel(QAbstractTableModel): @@ -29,7 +29,7 @@ class DetailsModel(QAbstractTableModel): return None if role != Qt.DisplayRole: return None - column = index.column() + column = index.column() +1 row = index.row() return self.model.row(row)[column] @@ -40,6 +40,12 @@ class DetailsModel(QAbstractTableModel): and section < len(HEADER) ): return HEADER[section] + elif ( + orientation == Qt.Vertical + and role == Qt.DisplayRole + and section < self.model.row_count() + ): + return self.model.row(section)[0] return None def rowCount(self, parent): @@ -51,6 +57,7 @@ class DetailsTable(QTableView): QTableView.__init__(self, *args) self.setAlternatingRowColors(True) self.setSelectionBehavior(QTableView.SelectRows) + self.setSelectionMode(QTableView.SingleSelection) self.setShowGrid(False) self.setWordWrap(False) @@ -61,9 +68,12 @@ class DetailsTable(QTableView): hheader.setHighlightSections(False) hheader.setStretchLastSection(False) hheader.resizeSection(0, 100) - hheader.setSectionResizeMode(0, QHeaderView.Fixed) + hheader.setSectionResizeMode(0, QHeaderView.Stretch) hheader.setSectionResizeMode(1, QHeaderView.Stretch) - hheader.setSectionResizeMode(2, QHeaderView.Stretch) vheader = self.verticalHeader() - vheader.setVisible(False) + vheader.setVisible(True) vheader.setDefaultSectionSize(18) + # FIXME hardcoded value is not ideal, perhaps resize to contents once first? + # vheader.setSectionResizeMode(QHeaderView.ResizeToContents) + vheader.setSectionResizeMode(QHeaderView.Fixed) + vheader.setSectionsMovable(True) From eb6946343b48d4e3c2a18aabc0d45d11e7efd099 Mon Sep 17 00:00:00 2001 From: glubsy Date: Tue, 30 Jun 2020 01:19:25 +0200 Subject: [PATCH 02/13] Remove superflous top-left corner button --- qt/details_table.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qt/details_table.py b/qt/details_table.py index 92c2fdf1..795aca93 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -29,7 +29,8 @@ class DetailsModel(QAbstractTableModel): return None if role != Qt.DisplayRole: return None - column = index.column() +1 + # Skip first value "Attribute" + column = index.column() + 1 row = index.row() return self.model.row(row)[column] @@ -60,6 +61,7 @@ class DetailsTable(QTableView): self.setSelectionMode(QTableView.SingleSelection) self.setShowGrid(False) self.setWordWrap(False) + self.setCornerButtonEnabled(False) def setModel(self, model): QTableView.setModel(self, model) @@ -73,7 +75,7 @@ class DetailsTable(QTableView): vheader = self.verticalHeader() vheader.setVisible(True) vheader.setDefaultSectionSize(18) - # FIXME hardcoded value is not ideal, perhaps resize to contents once first? + # hardcoded value above is not ideal, perhaps resize to contents first? # vheader.setSectionResizeMode(QHeaderView.ResizeToContents) vheader.setSectionResizeMode(QHeaderView.Fixed) vheader.setSectionsMovable(True) From c6f5031dd81ab4a01744bb3840f149ee314f94e5 Mon Sep 17 00:00:00 2001 From: glubsy Date: Tue, 30 Jun 2020 04:20:27 +0200 Subject: [PATCH 03/13] Add color and bold font if difference in model * Could be better optimized if there is a way to set those variables earlier in the model or somewhere in the viewer when it requests the data. * Right now it compares strings(?) many times for every role we handle, which is not ideal. --- qt/details_table.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/qt/details_table.py b/qt/details_table.py index 795aca93..45eae4b3 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -8,6 +8,7 @@ from PyQt5.QtCore import Qt, QAbstractTableModel from PyQt5.QtWidgets import QHeaderView, QTableView +from PyQt5.QtGui import QFont, QBrush, QColor from hscommon.trans import trget @@ -27,12 +28,25 @@ class DetailsModel(QAbstractTableModel): def data(self, index, role): if not index.isValid(): return None - if role != Qt.DisplayRole: - return None # Skip first value "Attribute" column = index.column() + 1 row = index.row() - return self.model.row(row)[column] + + if self.model.row(row)[0] == "Dupe Count": + if role != Qt.DisplayRole: + return None + return self.model.row(row)[column] + + if role == Qt.DisplayRole: + return self.model.row(row)[column] + if role == Qt.ForegroundRole and self.model.row(row)[1] != self.model.row(row)[2]: + return QBrush(QColor(250, 20, 20)) # red + if role == Qt.FontRole and self.model.row(row)[1] != self.model.row(row)[2]: + font = QFont(self.model.view.font()) # or simply QFont() + font.setBold(True) + return font + + return None # QVariant() def headerData(self, section, orientation, role): if ( @@ -46,6 +60,7 @@ class DetailsModel(QAbstractTableModel): and role == Qt.DisplayRole and section < self.model.row_count() ): + # Read "Attribute" cell for horizontal header return self.model.row(section)[0] return None @@ -58,7 +73,7 @@ class DetailsTable(QTableView): QTableView.__init__(self, *args) self.setAlternatingRowColors(True) self.setSelectionBehavior(QTableView.SelectRows) - self.setSelectionMode(QTableView.SingleSelection) + self.setSelectionMode(QTableView.NoSelection) self.setShowGrid(False) self.setWordWrap(False) self.setCornerButtonEnabled(False) From 5cbe342d5b15b86896698eaeaa53407e63882e6b Mon Sep 17 00:00:00 2001 From: glubsy Date: Tue, 30 Jun 2020 18:32:20 +0200 Subject: [PATCH 04/13] Ignore formatting if no data returned from model --- qt/details_table.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qt/details_table.py b/qt/details_table.py index 45eae4b3..0dd0791e 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -32,7 +32,10 @@ class DetailsModel(QAbstractTableModel): column = index.column() + 1 row = index.row() - if self.model.row(row)[0] == "Dupe Count": + ignored_fields = ["Dupe Count"] + if self.model.row(row)[0] in ignored_fields or \ + self.model.row(row)[1] == "---" or \ + self.model.row(row)[2] == "---": if role != Qt.DisplayRole: return None return self.model.row(row)[column] @@ -45,7 +48,6 @@ class DetailsModel(QAbstractTableModel): font = QFont(self.model.view.font()) # or simply QFont() font.setBold(True) return font - return None # QVariant() def headerData(self, section, orientation, role): From c973224fa4dfc09f8611175b2ae3c188f85f5323 Mon Sep 17 00:00:00 2001 From: glubsy Date: Wed, 1 Jul 2020 03:05:59 +0200 Subject: [PATCH 05/13] Fix flake8 identation warnings --- qt/details_table.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qt/details_table.py b/qt/details_table.py index 0dd0791e..c42aa0a6 100644 --- a/qt/details_table.py +++ b/qt/details_table.py @@ -33,9 +33,9 @@ class DetailsModel(QAbstractTableModel): row = index.row() ignored_fields = ["Dupe Count"] - if self.model.row(row)[0] in ignored_fields or \ - self.model.row(row)[1] == "---" or \ - self.model.row(row)[2] == "---": + if (self.model.row(row)[0] in ignored_fields + or self.model.row(row)[1] == "---" + or self.model.row(row)[2] == "---"): if role != Qt.DisplayRole: return None return self.model.row(row)[column] @@ -45,10 +45,10 @@ class DetailsModel(QAbstractTableModel): if role == Qt.ForegroundRole and self.model.row(row)[1] != self.model.row(row)[2]: return QBrush(QColor(250, 20, 20)) # red if role == Qt.FontRole and self.model.row(row)[1] != self.model.row(row)[2]: - font = QFont(self.model.view.font()) # or simply QFont() + font = QFont(self.model.view.font()) # or simply QFont() font.setBold(True) return font - return None # QVariant() + return None # QVariant() def headerData(self, section, orientation, role): if ( From 61fc4f07ae347834273ae3f2437e591397d30ae7 Mon Sep 17 00:00:00 2001 From: glubsy Date: Tue, 7 Jul 2020 16:54:08 +0200 Subject: [PATCH 06/13] Fix updating result window action upon creation * Result Window action was not being properly updated after the ResultWindow had been created. There was no way of retrieving the window after it had been closed. --- qt/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/app.py b/qt/app.py index ac59dd0a..17ffc210 100644 --- a/qt/app.py +++ b/qt/app.py @@ -283,6 +283,7 @@ class DupeGuru(QObject): self.resultWindow.close() self.resultWindow.setParent(None) self.resultWindow = ResultWindow(self.directories_dialog, self) + self.directories_dialog._updateActionsState() self.details_dialog = self._get_details_dialog_class()(self.resultWindow, self) def show_results_window(self): From db228ec8a3ed06834ebc12c2c5aa1133c46e7981 Mon Sep 17 00:00:00 2001 From: glubsy Date: Sun, 12 Jul 2020 16:17:18 +0200 Subject: [PATCH 07/13] Fix word wrap in ignore list dialog --- qt/ignore_list_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/ignore_list_dialog.py b/qt/ignore_list_dialog.py index 99d2efe7..85d118db 100644 --- a/qt/ignore_list_dialog.py +++ b/qt/ignore_list_dialog.py @@ -48,6 +48,7 @@ class IgnoreListDialog(QDialog): self.tableView.verticalHeader().setDefaultSectionSize(18) self.tableView.verticalHeader().setHighlightSections(False) self.tableView.verticalHeader().setVisible(False) + self.tableView.setWordWrap(False) self.verticalLayout.addWidget(self.tableView) self.removeSelectedButton = QPushButton(tr("Remove Selected")) self.clearButton = QPushButton(tr("Clear")) From f19b5d6ea6cf14d89844653953ec05683b995210 Mon Sep 17 00:00:00 2001 From: glubsy Date: Fri, 24 Jul 2020 03:12:40 +0200 Subject: [PATCH 08/13] Fix error in package script for (Arch) Linux * While packaging, the "build/help" and "build/locale" directories are not found. * Work around the issue with try/except statements. --- package.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/package.py b/package.py index be949bce..f89a5a60 100644 --- a/package.py +++ b/package.py @@ -43,8 +43,14 @@ def copy_files_to_package(destpath, packages, with_so): shutil.copy("run.py", op.join(destpath, "run.py")) extra_ignores = ["*.so"] if not with_so else None copy_packages(packages, destpath, extra_ignores=extra_ignores) - shutil.copytree(op.join("build", "help"), op.join(destpath, "help")) - shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale")) + try: + shutil.copytree(op.join("build", "help"), op.join(destpath, "help")) + except Exception as e: + print(f"Warning: exception: {e}") + try: + shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale")) + except Exception as e: + print(f"Warning: exception: {e}") compileall.compile_dir(destpath) From 49a1beb22506902e8f0203f8ab4595211b80f1b5 Mon Sep 17 00:00:00 2001 From: glubsy Date: Fri, 24 Jul 2020 03:33:13 +0200 Subject: [PATCH 09/13] Avoid using workarounds in package script * Just like the Windows package function counterpart, better abort building the package if the help and locale files have not been build instead of ignoring the error --- package.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/package.py b/package.py index f89a5a60..693baee8 100644 --- a/package.py +++ b/package.py @@ -43,14 +43,17 @@ def copy_files_to_package(destpath, packages, with_so): shutil.copy("run.py", op.join(destpath, "run.py")) extra_ignores = ["*.so"] if not with_so else None copy_packages(packages, destpath, extra_ignores=extra_ignores) - try: - shutil.copytree(op.join("build", "help"), op.join(destpath, "help")) - except Exception as e: - print(f"Warning: exception: {e}") - try: - shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale")) - except Exception as e: - print(f"Warning: exception: {e}") + # include locale files if they are built otherwise exit as it will break + # the localization + if not op.exists("build/locale"): + print("Locale files not built, exiting...") + return + # include help files if they are built otherwise exit as they should be included? + if not op.exists("build/help"): + print("Help files not built, exiting...") + return + shutil.copytree(op.join("build", "help"), op.join(destpath, "help")) + shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale")) compileall.compile_dir(destpath) From f0adf35db45b10d85bcc1217614e3063bce40480 Mon Sep 17 00:00:00 2001 From: glubsy Date: Fri, 24 Jul 2020 03:48:07 +0200 Subject: [PATCH 10/13] Add helpful message in build files are missing --- package.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.py b/package.py index 693baee8..d66eedda 100644 --- a/package.py +++ b/package.py @@ -46,11 +46,11 @@ def copy_files_to_package(destpath, packages, with_so): # include locale files if they are built otherwise exit as it will break # the localization if not op.exists("build/locale"): - print("Locale files not built, exiting...") + print("Locale files are missing. Have you run \"build.py --loc\"? Exiting...") return # include help files if they are built otherwise exit as they should be included? if not op.exists("build/help"): - print("Help files not built, exiting...") + print("Help files are missing. Have you run \"build.py --help\"? Exiting...") return shutil.copytree(op.join("build", "help"), op.join(destpath, "help")) shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale")) @@ -161,11 +161,11 @@ def package_windows(): # include locale files if they are built otherwise exit as it will break # the localization if not op.exists("build/locale"): - print("Locale files not built, exiting...") + print("Locale files are missing. Have you run \"build.py --loc\"? Exiting...") return # include help files if they are built otherwise exit as they should be included? if not op.exists("build/help"): - print("Help files not built, exiting...") + print("Help files are missing. Have you run \"build.py --help\"? Exiting...") return # create version information file from template try: From d193e1fd12f426503ffabdc5b8926b23c14835d4 Mon Sep 17 00:00:00 2001 From: glubsy Date: Fri, 24 Jul 2020 03:50:08 +0200 Subject: [PATCH 11/13] Fix typo in error message --- package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.py b/package.py index d66eedda..aa876b36 100644 --- a/package.py +++ b/package.py @@ -50,7 +50,7 @@ def copy_files_to_package(destpath, packages, with_so): return # include help files if they are built otherwise exit as they should be included? if not op.exists("build/help"): - print("Help files are missing. Have you run \"build.py --help\"? Exiting...") + print("Help files are missing. Have you run \"build.py --doc\"? Exiting...") return shutil.copytree(op.join("build", "help"), op.join(destpath, "help")) shutil.copytree(op.join("build", "locale"), op.join(destpath, "locale")) @@ -165,7 +165,7 @@ def package_windows(): return # include help files if they are built otherwise exit as they should be included? if not op.exists("build/help"): - print("Help files are missing. Have you run \"build.py --help\"? Exiting...") + print("Help files are missing. Have you run \"build.py --doc\"? Exiting...") return # create version information file from template try: From 63b2f95cfaa0525a08e8c56eb26a0b5815ab9b35 Mon Sep 17 00:00:00 2001 From: glubsy Date: Sat, 25 Jul 2020 23:37:41 +0200 Subject: [PATCH 12/13] Work around frozen progress dialog * It seems that matchblock.getmatches() returns too early and the (multi-)processes become zombies * This is a workaround which seems to work by sleeping for one second and avoid zombie processes --- core/pe/matchblock.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/pe/matchblock.py b/core/pe/matchblock.py index a13a15aa..6f2a0a27 100644 --- a/core/pe/matchblock.py +++ b/core/pe/matchblock.py @@ -9,6 +9,7 @@ import logging import multiprocessing from itertools import combinations +from time import sleep from hscommon.util import extract, iterconsume from hscommon.trans import tr @@ -254,6 +255,9 @@ def getmatches(pictures, cache_path, threshold, match_scaled=False, j=job.nulljo ref.dimensions # pre-read dimensions for display in results other.dimensions result.append(get_match(ref, other, percentage)) + # HACK this is a workaround for when the progress bar gets stuck sometimes, + # as we enter a deadlock somewhere and the sub-processes become zombies. + sleep(1) return result From 5f5f9232c1ce63235c6c2a5aa80c3b8b1ec761be Mon Sep 17 00:00:00 2001 From: glubsy Date: Tue, 28 Jul 2020 16:44:06 +0200 Subject: [PATCH 13/13] Properly wait for multiprocesses to exit * Fix for #693 --- core/pe/matchblock.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/pe/matchblock.py b/core/pe/matchblock.py index 6f2a0a27..953d39ab 100644 --- a/core/pe/matchblock.py +++ b/core/pe/matchblock.py @@ -9,7 +9,6 @@ import logging import multiprocessing from itertools import combinations -from time import sleep from hscommon.util import extract, iterconsume from hscommon.trans import tr @@ -255,9 +254,7 @@ def getmatches(pictures, cache_path, threshold, match_scaled=False, j=job.nulljo ref.dimensions # pre-read dimensions for display in results other.dimensions result.append(get_match(ref, other, percentage)) - # HACK this is a workaround for when the progress bar gets stuck sometimes, - # as we enter a deadlock somewhere and the sub-processes become zombies. - sleep(1) + pool.join() return result