1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-22 14:41:39 +00:00

Format files with black

- Format all files with black
- Update tox.ini flake8 arguments to be compatible
- Add black to requirements-extra.txt
- Reduce ignored flake8 rules and fix a few violations
This commit is contained in:
2019-12-31 20:16:27 -06:00
parent 359d6498f7
commit 7ba8aa3514
141 changed files with 5241 additions and 3648 deletions

147
qt/app.py
View File

@@ -36,11 +36,12 @@ from .me.preferences_dialog import PreferencesDialog as PreferencesDialogMusic
from .pe.preferences_dialog import PreferencesDialog as PreferencesDialogPicture
from .pe.photo import File as PlatSpecificPhoto
tr = trget('ui')
tr = trget("ui")
class DupeGuru(QObject):
LOGO_NAME = 'logo_se'
NAME = 'dupeGuru'
LOGO_NAME = "logo_se"
NAME = "dupeGuru"
def __init__(self, **kwargs):
super().__init__(**kwargs)
@@ -49,20 +50,28 @@ class DupeGuru(QObject):
self.model = DupeGuruModel(view=self)
self._setup()
#--- Private
# --- Private
def _setup(self):
core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = PlatSpecificPhoto
self._setupActions()
self._update_options()
self.recentResults = Recent(self, 'recentResults')
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)
self.problemDialog = ProblemDialog(parent=self.directories_dialog, model=self.model.problem_dialog)
self.ignoreListDialog = IgnoreListDialog(parent=self.directories_dialog, model=self.model.ignore_list_dialog)
self.deletionOptions = DeletionOptions(parent=self.directories_dialog, model=self.model.deletion_options)
self.progress_window = ProgressWindow(
self.directories_dialog, self.model.progress_window
)
self.problemDialog = ProblemDialog(
parent=self.directories_dialog, model=self.model.problem_dialog
)
self.ignoreListDialog = IgnoreListDialog(
parent=self.directories_dialog, model=self.model.ignore_list_dialog
)
self.deletionOptions = DeletionOptions(
parent=self.directories_dialog, model=self.model.deletion_options
)
self.about_box = AboutBox(self.directories_dialog, self)
self.directories_dialog.show()
@@ -80,46 +89,70 @@ class DupeGuru(QObject):
# Setup actions that are common to both the directory dialog and the results window.
# (name, shortcut, icon, desc, func)
ACTIONS = [
('actionQuit', 'Ctrl+Q', '', tr("Quit"), self.quitTriggered),
('actionPreferences', 'Ctrl+P', '', tr("Options"), self.preferencesTriggered),
('actionIgnoreList', '', '', tr("Ignore List"), self.ignoreListTriggered),
('actionClearPictureCache', 'Ctrl+Shift+P', '', tr("Clear Picture Cache"), self.clearPictureCacheTriggered),
('actionShowHelp', 'F1', '', tr("dupeGuru Help"), self.showHelpTriggered),
('actionAbout', '', '', tr("About dupeGuru"), self.showAboutBoxTriggered),
('actionOpenDebugLog', '', '', tr("Open Debug Log"), self.openDebugLogTriggered),
("actionQuit", "Ctrl+Q", "", tr("Quit"), self.quitTriggered),
(
"actionPreferences",
"Ctrl+P",
"",
tr("Options"),
self.preferencesTriggered,
),
("actionIgnoreList", "", "", tr("Ignore List"), self.ignoreListTriggered),
(
"actionClearPictureCache",
"Ctrl+Shift+P",
"",
tr("Clear Picture Cache"),
self.clearPictureCacheTriggered,
),
("actionShowHelp", "F1", "", tr("dupeGuru Help"), self.showHelpTriggered),
("actionAbout", "", "", tr("About dupeGuru"), self.showAboutBoxTriggered),
(
"actionOpenDebugLog",
"",
"",
tr("Open Debug Log"),
self.openDebugLogTriggered,
),
]
createActions(ACTIONS, self)
def _update_options(self):
self.model.options['mix_file_kind'] = self.prefs.mix_file_kind
self.model.options['escape_filter_regexp'] = not self.prefs.use_regexp
self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches
self.model.options['copymove_dest_type'] = self.prefs.destination_type
self.model.options['scan_type'] = self.prefs.get_scan_type(self.model.app_mode)
self.model.options['min_match_percentage'] = self.prefs.filter_hardness
self.model.options['word_weighting'] = self.prefs.word_weighting
self.model.options['match_similar_words'] = self.prefs.match_similar
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
self.model.options['size_threshold'] = threshold * 1024 # threshold is in KB. the scanner wants bytes
self.model.options["mix_file_kind"] = self.prefs.mix_file_kind
self.model.options["escape_filter_regexp"] = not self.prefs.use_regexp
self.model.options["clean_empty_dirs"] = self.prefs.remove_empty_folders
self.model.options[
"ignore_hardlink_matches"
] = self.prefs.ignore_hardlink_matches
self.model.options["copymove_dest_type"] = self.prefs.destination_type
self.model.options["scan_type"] = self.prefs.get_scan_type(self.model.app_mode)
self.model.options["min_match_percentage"] = self.prefs.filter_hardness
self.model.options["word_weighting"] = self.prefs.word_weighting
self.model.options["match_similar_words"] = self.prefs.match_similar
threshold = (
self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
)
self.model.options["size_threshold"] = (
threshold * 1024
) # threshold is in KB. the scanner wants bytes
scanned_tags = set()
if self.prefs.scan_tag_track:
scanned_tags.add('track')
scanned_tags.add("track")
if self.prefs.scan_tag_artist:
scanned_tags.add('artist')
scanned_tags.add("artist")
if self.prefs.scan_tag_album:
scanned_tags.add('album')
scanned_tags.add("album")
if self.prefs.scan_tag_title:
scanned_tags.add('title')
scanned_tags.add("title")
if self.prefs.scan_tag_genre:
scanned_tags.add('genre')
scanned_tags.add("genre")
if self.prefs.scan_tag_year:
scanned_tags.add('year')
self.model.options['scanned_tags'] = scanned_tags
self.model.options['match_scaled'] = self.prefs.match_scaled
self.model.options['picture_cache_type'] = self.prefs.picture_cache_type
scanned_tags.add("year")
self.model.options["scanned_tags"] = scanned_tags
self.model.options["match_scaled"] = self.prefs.match_scaled
self.model.options["picture_cache_type"] = self.prefs.picture_cache_type
#--- Private
# --- Private
def _get_details_dialog_class(self):
if self.model.app_mode == AppMode.Picture:
return DetailsDialogPicture
@@ -136,7 +169,7 @@ class DupeGuru(QObject):
else:
return PreferencesDialogStandard
#--- Public
# --- Public
def add_selected_to_ignore_list(self):
self.model.add_selected_to_ignore_list()
@@ -166,17 +199,19 @@ class DupeGuru(QObject):
self.model.save()
QApplication.quit()
#--- Signals
# --- Signals
willSavePrefs = pyqtSignal()
SIGTERM = pyqtSignal()
#--- Events
# --- Events
def finishedLaunching(self):
if sys.getfilesystemencoding() == 'ascii':
if sys.getfilesystemencoding() == "ascii":
# No need to localize this, it's a debugging message.
msg = "Something is wrong with the way your system locale is set. If the files you're "\
"scanning have accented letters, you'll probably get a crash. It is advised that "\
msg = (
"Something is wrong with the way your system locale is set. If the files you're "
"scanning have accented letters, you'll probably get a crash. It is advised that "
"you set your system locale properly."
)
QMessageBox.warning(self.directories_dialog, "Wrong Locale", msg)
def clearPictureCacheTriggered(self):
@@ -191,11 +226,13 @@ class DupeGuru(QObject):
self.model.ignore_list_dialog.show()
def openDebugLogTriggered(self):
debugLogPath = op.join(self.model.appdata, 'debug.log')
debugLogPath = op.join(self.model.appdata, "debug.log")
desktop.open_path(debugLogPath)
def preferencesTriggered(self):
preferences_dialog = self._get_preferences_dialog_class()(self.directories_dialog, self)
preferences_dialog = self._get_preferences_dialog_class()(
self.directories_dialog, self
)
preferences_dialog.load()
result = preferences_dialog.exec()
if result == QDialog.Accepted:
@@ -212,17 +249,17 @@ class DupeGuru(QObject):
def showHelpTriggered(self):
base_path = platform.HELP_PATH
help_path = op.abspath(op.join(base_path, 'index.html'))
help_path = op.abspath(op.join(base_path, "index.html"))
if op.exists(help_path):
url = QUrl.fromLocalFile(help_path)
else:
url = QUrl('https://www.hardcoded.net/dupeguru/help/en/')
url = QUrl("https://www.hardcoded.net/dupeguru/help/en/")
QDesktopServices.openUrl(url)
def handleSIGTERM(self):
self.shutdown()
#--- model --> view
# --- model --> view
def get_default(self, key):
return self.prefs.get_value(key)
@@ -231,10 +268,10 @@ class DupeGuru(QObject):
def show_message(self, msg):
window = QApplication.activeWindow()
QMessageBox.information(window, '', msg)
QMessageBox.information(window, "", msg)
def ask_yes_no(self, prompt):
return self.confirm('', prompt)
return self.confirm("", prompt)
def create_results_window(self):
"""Creates resultWindow and details_dialog depending on the selected ``app_mode``.
@@ -256,11 +293,13 @@ class DupeGuru(QObject):
def select_dest_folder(self, prompt):
flags = QFileDialog.ShowDirsOnly
return QFileDialog.getExistingDirectory(self.resultWindow, prompt, '', flags)
return QFileDialog.getExistingDirectory(self.resultWindow, prompt, "", flags)
def select_dest_file(self, prompt, extension):
files = tr("{} file (*.{})").format(extension.upper(), extension)
destination, chosen_filter = QFileDialog.getSaveFileName(self.resultWindow, prompt, '', files)
if not destination.endswith('.{}'.format(extension)):
destination = '{}.{}'.format(destination, extension)
destination, chosen_filter = QFileDialog.getSaveFileName(
self.resultWindow, prompt, "", files
)
if not destination.endswith(".{}".format(extension)):
destination = "{}.{}".format(destination, extension)
return destination

View File

@@ -12,7 +12,8 @@ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QCheckBox, QDialogButt
from hscommon.trans import trget
from qtlib.radio_box import RadioBox
tr = trget('ui')
tr = trget("ui")
class DeletionOptions(QDialog):
def __init__(self, parent, model, **kwargs):
@@ -41,7 +42,9 @@ class DeletionOptions(QDialog):
self.linkMessageLabel = QLabel(text)
self.linkMessageLabel.setWordWrap(True)
self.verticalLayout.addWidget(self.linkMessageLabel)
self.linkTypeRadio = RadioBox(items=[tr("Symlink"), tr("Hardlink")], spread=False)
self.linkTypeRadio = RadioBox(
items=[tr("Symlink"), tr("Hardlink")], spread=False
)
self.verticalLayout.addWidget(self.linkTypeRadio)
if not self.model.supports_links():
self.linkCheckbox.setEnabled(False)
@@ -60,11 +63,11 @@ class DeletionOptions(QDialog):
self.buttonBox.addButton(tr("Cancel"), QDialogButtonBox.RejectRole)
self.verticalLayout.addWidget(self.buttonBox)
#--- Signals
# --- Signals
def linkCheckboxChanged(self, changed: int):
self.model.link_deleted = bool(changed)
#--- model --> view
# --- model --> view
def update_msg(self, msg: str):
self.msgLabel.setText(msg)
@@ -80,4 +83,3 @@ class DeletionOptions(QDialog):
def set_hardlink_option_enabled(self, is_enabled: bool):
self.linkTypeRadio.setEnabled(is_enabled)

View File

@@ -1,9 +1,9 @@
# Created By: Virgil Dupras
# Created On: 2010-02-05
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# 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
@@ -11,6 +11,7 @@ from PyQt5.QtWidgets import QDialog
from .details_table import DetailsModel
class DetailsDialog(QDialog):
def __init__(self, parent, app, **kwargs):
super().__init__(parent, Qt.Tool, **kwargs)
@@ -20,28 +21,27 @@ class DetailsDialog(QDialog):
# 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.
self._shown_once = False
self.app.prefs.restoreGeometry('DetailsWindowRect', self)
self.app.prefs.restoreGeometry("DetailsWindowRect", self)
self.tableModel = DetailsModel(self.model)
# tableView is defined in subclasses
self.tableView.setModel(self.tableModel)
self.model.view = self
self.app.willSavePrefs.connect(self.appWillSavePrefs)
def _setupUi(self): # Virtual
def _setupUi(self): # Virtual
pass
def show(self):
self._shown_once = True
super().show()
#--- Events
# --- Events
def appWillSavePrefs(self):
if self._shown_once:
self.app.prefs.saveGeometry('DetailsWindowRect', self)
#--- model --> view
self.app.prefs.saveGeometry("DetailsWindowRect", self)
# --- model --> view
def refresh(self):
self.tableModel.beginResetModel()
self.tableModel.endResetModel()

View File

@@ -1,9 +1,9 @@
# Created By: Virgil Dupras
# Created On: 2009-05-17
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# 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, QAbstractTableModel
@@ -11,18 +11,19 @@ from PyQt5.QtWidgets import QHeaderView, QTableView
from hscommon.trans import trget
tr = trget('ui')
tr = trget("ui")
HEADER = [tr("Attribute"), tr("Selected"), tr("Reference")]
class DetailsModel(QAbstractTableModel):
def __init__(self, model, **kwargs):
super().__init__(**kwargs)
self.model = model
def columnCount(self, parent):
return len(HEADER)
def data(self, index, role):
if not index.isValid():
return None
@@ -31,15 +32,19 @@ class DetailsModel(QAbstractTableModel):
column = index.column()
row = index.row()
return self.model.row(row)[column]
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADER):
if (
orientation == Qt.Horizontal
and role == Qt.DisplayRole
and section < len(HEADER)
):
return HEADER[section]
return None
def rowCount(self, parent):
return self.model.row_count()
class DetailsTable(QTableView):
def __init__(self, *args):
@@ -47,7 +52,7 @@ class DetailsTable(QTableView):
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QTableView.SelectRows)
self.setShowGrid(False)
def setModel(self, model):
QTableView.setModel(self, model)
# The model needs to be set to set header stuff
@@ -61,4 +66,3 @@ class DetailsTable(QTableView):
vheader = self.verticalHeader()
vheader.setVisible(False)
vheader.setDefaultSectionSize(18)

View File

@@ -6,9 +6,21 @@
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtWidgets import (
QWidget, QFileDialog, QHeaderView, QVBoxLayout, QHBoxLayout, QTreeView,
QAbstractItemView, QSpacerItem, QSizePolicy, QPushButton, QMainWindow, QMenuBar, QMenu, QLabel,
QComboBox
QWidget,
QFileDialog,
QHeaderView,
QVBoxLayout,
QHBoxLayout,
QTreeView,
QAbstractItemView,
QSpacerItem,
QSizePolicy,
QPushButton,
QMainWindow,
QMenuBar,
QMenu,
QLabel,
QComboBox,
)
from PyQt5.QtGui import QPixmap, QIcon
@@ -21,17 +33,20 @@ from qtlib.util import moveToScreenCenter, createActions
from . import platform
from .directories_model import DirectoriesModel, DirectoriesDelegate
tr = trget('ui')
tr = trget("ui")
class DirectoriesDialog(QMainWindow):
def __init__(self, app, **kwargs):
super().__init__(None, **kwargs)
self.app = app
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
self.recentFolders = Recent(self.app, 'recentFolders')
self.recentFolders = Recent(self.app, "recentFolders")
self._setupUi()
self._updateScanTypeList()
self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView)
self.directoriesModel = DirectoriesModel(
self.app.model.directory_tree, view=self.treeView
)
self.directoriesDelegate = DirectoriesDelegate()
self.treeView.setItemDelegate(self.directoriesDelegate)
self._setupColumns()
@@ -61,9 +76,21 @@ class DirectoriesDialog(QMainWindow):
def _setupActions(self):
# (name, shortcut, icon, desc, func)
ACTIONS = [
('actionLoadResults', 'Ctrl+L', '', tr("Load Results..."), self.loadResultsTriggered),
('actionShowResultsWindow', '', '', tr("Results Window"), self.app.showResultsWindow),
('actionAddFolder', '', '', tr("Add Folder..."), self.addFolderTriggered),
(
"actionLoadResults",
"Ctrl+L",
"",
tr("Load Results..."),
self.loadResultsTriggered,
),
(
"actionShowResultsWindow",
"",
"",
tr("Results Window"),
self.app.showResultsWindow,
),
("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered),
]
createActions(ACTIONS, self)
@@ -117,9 +144,7 @@ class DirectoriesDialog(QMainWindow):
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(label)
self.appModeRadioBox = RadioBox(
self,
items=[tr("Standard"), tr("Music"), tr("Picture")],
spread=False
self, items=[tr("Standard"), tr("Music"), tr("Picture")], spread=False
)
hl.addWidget(self.appModeRadioBox)
self.verticalLayout.addLayout(hl)
@@ -129,21 +154,28 @@ class DirectoriesDialog(QMainWindow):
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(label)
self.scanTypeComboBox = QComboBox(self)
self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
self.scanTypeComboBox.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
)
self.scanTypeComboBox.setMaximumWidth(400)
hl.addWidget(self.scanTypeComboBox)
self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget)
self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(self.showPreferencesButton)
self.verticalLayout.addLayout(hl)
self.promptLabel = QLabel(tr("Select folders to scan and press \"Scan\"."), self.centralwidget)
self.promptLabel = QLabel(
tr('Select folders to scan and press "Scan".'), self.centralwidget
)
self.verticalLayout.addWidget(self.promptLabel)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.treeView.setAcceptDrops(True)
triggers = QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed\
triggers = (
QAbstractItemView.DoubleClicked
| QAbstractItemView.EditKeyPressed
| QAbstractItemView.SelectedClicked
)
self.treeView.setEditTriggers(triggers)
self.treeView.setDragDropOverwriteMode(True)
self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
@@ -208,7 +240,9 @@ class DirectoriesDialog(QMainWindow):
def _updateScanTypeList(self):
try:
self.scanTypeComboBox.currentIndexChanged[int].disconnect(self.scanTypeChanged)
self.scanTypeComboBox.currentIndexChanged[int].disconnect(
self.scanTypeChanged
)
except TypeError:
# Not connected, ignore
pass
@@ -223,7 +257,7 @@ class DirectoriesDialog(QMainWindow):
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
self.app._update_options()
#--- QWidget overrides
# --- QWidget overrides
def closeEvent(self, event):
event.accept()
if self.app.model.results.is_modified:
@@ -234,11 +268,13 @@ class DirectoriesDialog(QMainWindow):
if event.isAccepted():
self.app.shutdown()
#--- Events
# --- Events
def addFolderTriggered(self):
title = tr("Select a folder to add to the scanning list")
flags = QFileDialog.ShowDirsOnly
dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
dirpath = str(
QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags)
)
if not dirpath:
return
self.lastAddedFolder = dirpath
@@ -264,8 +300,8 @@ class DirectoriesDialog(QMainWindow):
def loadResultsTriggered(self):
title = tr("Select a results file to load")
files = ';;'.join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, '', files)[0]
files = ";;".join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, "", files)[0]
if destination:
self.app.model.load_from(destination)
self.app.recentResults.insertItem(destination)
@@ -283,9 +319,10 @@ class DirectoriesDialog(QMainWindow):
def scanTypeChanged(self, index):
scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
self.app.prefs.set_scan_type(self.app.model.app_mode, scan_options[index].scan_type)
self.app.prefs.set_scan_type(
self.app.model.app_mode, scan_options[index].scan_type
)
self.app._update_options()
def selectionChanged(self, selected, deselected):
self._updateRemoveButton()

View File

@@ -10,19 +10,24 @@ import urllib.parse
from PyQt5.QtCore import pyqtSignal, Qt, QRect, QUrl, QModelIndex, QItemSelection
from PyQt5.QtWidgets import (
QComboBox, QStyledItemDelegate, QStyle, QStyleOptionComboBox,
QStyleOptionViewItem, QApplication
QComboBox,
QStyledItemDelegate,
QStyle,
QStyleOptionComboBox,
QStyleOptionViewItem,
QApplication,
)
from PyQt5.QtGui import QBrush
from hscommon.trans import trget
from qtlib.tree_model import RefNode, TreeModel
tr = trget('ui')
tr = trget("ui")
HEADERS = [tr("Name"), tr("State")]
STATES = [tr("Normal"), tr("Reference"), tr("Excluded")]
class DirectoriesDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QComboBox(parent)
@@ -39,10 +44,12 @@ class DirectoriesDelegate(QStyledItemDelegate):
# On OS X (with Qt4.6.0), adding State_Enabled to the flags causes the whole drawing to
# fail (draw nothing), but it's an OS X only glitch. On Windows, it works alright.
cboption.state |= QStyle.State_Enabled
QApplication.style().drawComplexControl(QStyle.CC_ComboBox, cboption, painter)
QApplication.style().drawComplexControl(
QStyle.CC_ComboBox, cboption, painter
)
painter.setBrush(option.palette.text())
rect = QRect(option.rect)
rect.setLeft(rect.left()+4)
rect.setLeft(rect.left() + 4)
painter.drawText(rect, Qt.AlignLeft, option.text)
else:
super().paint(painter, option, index)
@@ -68,7 +75,9 @@ class DirectoriesModel(TreeModel):
self.view = view
self.view.setModel(self)
self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged)
self.view.selectionModel().selectionChanged[
(QItemSelection, QItemSelection)
].connect(self.selectionChanged)
def _createNode(self, ref, row):
return RefNode(self, None, ref, row)
@@ -102,11 +111,11 @@ class DirectoriesModel(TreeModel):
def dropMimeData(self, mimeData, action, row, column, parentIndex):
# the data in mimeData is urlencoded **in utf-8**!!! What we do is to decode, the mime data
# with 'ascii', which works since it's urlencoded. Then, we pass that to urllib.
if not mimeData.hasFormat('text/uri-list'):
if not mimeData.hasFormat("text/uri-list"):
return False
data = bytes(mimeData.data('text/uri-list')).decode('ascii')
data = bytes(mimeData.data("text/uri-list")).decode("ascii")
unquoted = urllib.parse.unquote(data)
urls = unquoted.split('\r\n')
urls = unquoted.split("\r\n")
paths = [QUrl(url).toLocalFile() for url in urls if url]
for path in paths:
self.model.add_directory(path)
@@ -129,7 +138,7 @@ class DirectoriesModel(TreeModel):
return None
def mimeTypes(self):
return ['text/uri-list']
return ["text/uri-list"]
def setData(self, index, value, role):
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
@@ -144,18 +153,20 @@ class DirectoriesModel(TreeModel):
# work with ActionMove either. So screw that, and accept anything.
return Qt.ActionMask
#--- Events
# --- Events
def selectionChanged(self, selected, deselected):
newNodes = [modelIndex.internalPointer().ref for modelIndex in self.view.selectionModel().selectedRows()]
newNodes = [
modelIndex.internalPointer().ref
for modelIndex in self.view.selectionModel().selectedRows()
]
self.model.selected_nodes = newNodes
#--- Signals
# --- Signals
foldersAdded = pyqtSignal(list)
#--- model --> view
# --- model --> view
def refresh(self):
self.reset()
def refresh_states(self):
self.refreshData()

View File

@@ -7,13 +7,20 @@
# http://www.gnu.org/licenses/gpl-3.0.html
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QPushButton, QTableView, QAbstractItemView
from PyQt5.QtWidgets import (
QDialog,
QVBoxLayout,
QPushButton,
QTableView,
QAbstractItemView,
)
from hscommon.trans import trget
from qtlib.util import horizontalWrap
from .ignore_list_table import IgnoreListTable
tr = trget('ui')
tr = trget("ui")
class IgnoreListDialog(QDialog):
def __init__(self, parent, model, **kwargs):
@@ -46,13 +53,11 @@ class IgnoreListDialog(QDialog):
self.clearButton = QPushButton(tr("Clear"))
self.closeButton = QPushButton(tr("Close"))
self.verticalLayout.addLayout(
horizontalWrap([
self.removeSelectedButton, self.clearButton,
None, self.closeButton
])
horizontalWrap(
[self.removeSelectedButton, self.clearButton, None, self.closeButton]
)
)
#--- model --> view
# --- model --> view
def show(self):
super().show()

View File

@@ -1,15 +1,16 @@
# Created On: 2012-03-13
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from qtlib.column import Column
from qtlib.table import Table
class IgnoreListTable(Table):
COLUMNS = [
Column('path1', defaultWidth=230),
Column('path2', defaultWidth=230),
Column("path1", defaultWidth=230),
Column("path2", defaultWidth=230),
]

View File

@@ -11,7 +11,8 @@ from hscommon.trans import trget
from ..details_dialog import DetailsDialog as DetailsDialogBase
from ..details_table import DetailsTable
tr = trget('ui')
tr = trget("ui")
class DetailsDialog(DetailsDialogBase):
def _setupUi(self):
@@ -26,4 +27,3 @@ class DetailsDialog(DetailsDialogBase):
self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableView.setShowGrid(False)
self.verticalLayout.addWidget(self.tableView)

View File

@@ -6,7 +6,12 @@
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import (
QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget,
QVBoxLayout,
QHBoxLayout,
QLabel,
QSizePolicy,
QSpacerItem,
QWidget,
)
from hscommon.trans import trget
@@ -15,7 +20,8 @@ from core.scanner import ScanType
from ..preferences_dialog import PreferencesDialogBase
tr = trget('ui')
tr = trget("ui")
class PreferencesDialog(PreferencesDialogBase):
def _setupPreferenceWidgets(self):
@@ -33,33 +39,40 @@ class PreferencesDialog(PreferencesDialogBase):
self.horizontalLayout_2.setSpacing(0)
spacerItem1 = QSpacerItem(15, 20, QSizePolicy.Fixed, QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self._setupAddCheckbox('tagTrackBox', tr("Track"), self.widget)
self._setupAddCheckbox("tagTrackBox", tr("Track"), self.widget)
self.horizontalLayout_2.addWidget(self.tagTrackBox)
self._setupAddCheckbox('tagArtistBox', tr("Artist"), self.widget)
self._setupAddCheckbox("tagArtistBox", tr("Artist"), self.widget)
self.horizontalLayout_2.addWidget(self.tagArtistBox)
self._setupAddCheckbox('tagAlbumBox', tr("Album"), self.widget)
self._setupAddCheckbox("tagAlbumBox", tr("Album"), self.widget)
self.horizontalLayout_2.addWidget(self.tagAlbumBox)
self._setupAddCheckbox('tagTitleBox', tr("Title"), self.widget)
self._setupAddCheckbox("tagTitleBox", tr("Title"), self.widget)
self.horizontalLayout_2.addWidget(self.tagTitleBox)
self._setupAddCheckbox('tagGenreBox', tr("Genre"), self.widget)
self._setupAddCheckbox("tagGenreBox", tr("Genre"), self.widget)
self.horizontalLayout_2.addWidget(self.tagGenreBox)
self._setupAddCheckbox('tagYearBox', tr("Year"), self.widget)
self._setupAddCheckbox("tagYearBox", tr("Year"), self.widget)
self.horizontalLayout_2.addWidget(self.tagYearBox)
self.verticalLayout_4.addLayout(self.horizontalLayout_2)
self.widgetsVLayout.addWidget(self.widget)
self._setupAddCheckbox('wordWeightingBox', tr("Word weighting"))
self._setupAddCheckbox("wordWeightingBox", tr("Word weighting"))
self.widgetsVLayout.addWidget(self.wordWeightingBox)
self._setupAddCheckbox('matchSimilarBox', tr("Match similar words"))
self._setupAddCheckbox("matchSimilarBox", tr("Match similar words"))
self.widgetsVLayout.addWidget(self.matchSimilarBox)
self._setupAddCheckbox('mixFileKindBox', tr("Can mix file kind"))
self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind"))
self.widgetsVLayout.addWidget(self.mixFileKindBox)
self._setupAddCheckbox('useRegexpBox', tr("Use regular expressions when filtering"))
self._setupAddCheckbox(
"useRegexpBox", tr("Use regular expressions when filtering")
)
self.widgetsVLayout.addWidget(self.useRegexpBox)
self._setupAddCheckbox('removeEmptyFoldersBox', tr("Remove empty folders on delete or move"))
self._setupAddCheckbox(
"removeEmptyFoldersBox", tr("Remove empty folders on delete or move")
)
self.widgetsVLayout.addWidget(self.removeEmptyFoldersBox)
self._setupAddCheckbox('ignoreHardlinkMatches', tr("Ignore duplicates hardlinking to the same file"))
self._setupAddCheckbox(
"ignoreHardlinkMatches",
tr("Ignore duplicates hardlinking to the same file"),
)
self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches)
self._setupAddCheckbox('debugModeBox', tr("Debug mode (restart required)"))
self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)"))
self.widgetsVLayout.addWidget(self.debugModeBox)
self._setupBottomPart()
@@ -76,8 +89,10 @@ class PreferencesDialog(PreferencesDialogBase):
# Update UI state based on selected scan type
scan_type = prefs.get_scan_type(AppMode.Music)
word_based = scan_type in (
ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder,
ScanType.Tag
ScanType.Filename,
ScanType.Fields,
ScanType.FieldsNoOrder,
ScanType.Tag,
)
tag_based = scan_type == ScanType.Tag
self.filterHardnessSlider.setEnabled(word_based)
@@ -99,4 +114,3 @@ class PreferencesDialog(PreferencesDialogBase):
prefs.scan_tag_year = ischecked(self.tagYearBox)
prefs.match_similar = ischecked(self.matchSimilarBox)
prefs.word_weighting = ischecked(self.wordWeightingBox)

View File

@@ -7,26 +7,26 @@
from qtlib.column import Column
from ..results_model import ResultsModel as ResultsModelBase
class ResultsModel(ResultsModelBase):
COLUMNS = [
Column('marked', defaultWidth=30),
Column('name', defaultWidth=200),
Column('folder_path', defaultWidth=180),
Column('size', defaultWidth=60),
Column('duration', defaultWidth=60),
Column('bitrate', defaultWidth=50),
Column('samplerate', defaultWidth=60),
Column('extension', defaultWidth=40),
Column('mtime', defaultWidth=120),
Column('title', defaultWidth=120),
Column('artist', defaultWidth=120),
Column('album', defaultWidth=120),
Column('genre', defaultWidth=80),
Column('year', defaultWidth=40),
Column('track', defaultWidth=40),
Column('comment', defaultWidth=120),
Column('percentage', defaultWidth=60),
Column('words', defaultWidth=120),
Column('dupe_count', defaultWidth=80),
Column("marked", defaultWidth=30),
Column("name", defaultWidth=200),
Column("folder_path", defaultWidth=180),
Column("size", defaultWidth=60),
Column("duration", defaultWidth=60),
Column("bitrate", defaultWidth=50),
Column("samplerate", defaultWidth=60),
Column("extension", defaultWidth=40),
Column("mtime", defaultWidth=120),
Column("title", defaultWidth=120),
Column("artist", defaultWidth=120),
Column("album", defaultWidth=120),
Column("genre", defaultWidth=80),
Column("year", defaultWidth=40),
Column("track", defaultWidth=40),
Column("comment", defaultWidth=120),
Column("percentage", defaultWidth=60),
Column("words", defaultWidth=120),
Column("dupe_count", defaultWidth=80),
]

View File

@@ -6,7 +6,7 @@
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from ._block_qt import getblocks # NOQA
from ._block_qt import getblocks # NOQA
# Converted to C
# def getblock(image):

View File

@@ -6,13 +6,20 @@
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QVBoxLayout, QAbstractItemView, QHBoxLayout, QLabel, QSizePolicy
from PyQt5.QtWidgets import (
QVBoxLayout,
QAbstractItemView,
QHBoxLayout,
QLabel,
QSizePolicy,
)
from hscommon.trans import trget
from ..details_dialog import DetailsDialog as DetailsDialogBase
from ..details_table import DetailsTable
tr = trget('ui')
tr = trget("ui")
class DetailsDialog(DetailsDialogBase):
def __init__(self, parent, app):
@@ -33,7 +40,9 @@ class DetailsDialog(DetailsDialogBase):
sizePolicy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.selectedImage.sizePolicy().hasHeightForWidth())
sizePolicy.setHeightForWidth(
self.selectedImage.sizePolicy().hasHeightForWidth()
)
self.selectedImage.setSizePolicy(sizePolicy)
self.selectedImage.setScaledContents(False)
self.selectedImage.setAlignment(Qt.AlignCenter)
@@ -42,7 +51,9 @@ class DetailsDialog(DetailsDialogBase):
sizePolicy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.referenceImage.sizePolicy().hasHeightForWidth())
sizePolicy.setHeightForWidth(
self.referenceImage.sizePolicy().hasHeightForWidth()
)
self.referenceImage.setSizePolicy(sizePolicy)
self.referenceImage.setAlignment(Qt.AlignCenter)
self.horizontalLayout.addWidget(self.referenceImage)
@@ -77,18 +88,22 @@ class DetailsDialog(DetailsDialogBase):
def _updateImages(self):
if self.selectedPixmap is not None:
target_size = self.selectedImage.size()
scaledPixmap = self.selectedPixmap.scaled(target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
scaledPixmap = self.selectedPixmap.scaled(
target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation
)
self.selectedImage.setPixmap(scaledPixmap)
else:
self.selectedImage.setPixmap(QPixmap())
if self.referencePixmap is not None:
target_size = self.referenceImage.size()
scaledPixmap = self.referencePixmap.scaled(target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
scaledPixmap = self.referencePixmap.scaled(
target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation
)
self.referenceImage.setPixmap(scaledPixmap)
else:
self.referenceImage.setPixmap(QPixmap())
#--- Override
# --- Override
def resizeEvent(self, event):
self._updateImages()
@@ -101,4 +116,3 @@ class DetailsDialog(DetailsDialogBase):
DetailsDialogBase.refresh(self)
if self.isVisible():
self._update()

View File

@@ -12,6 +12,7 @@ from core.pe.photo import Photo as PhotoBase
from .block import getblocks
class File(PhotoBase):
def _plat_get_dimensions(self):
try:
@@ -53,4 +54,3 @@ class File(PhotoBase):
t.rotate(270)
image = image.transformed(t)
return getblocks(image, block_count_per_side)

View File

@@ -12,23 +12,33 @@ from core.app import AppMode
from ..preferences_dialog import PreferencesDialogBase
tr = trget('ui')
tr = trget("ui")
class PreferencesDialog(PreferencesDialogBase):
def _setupPreferenceWidgets(self):
self._setupFilterHardnessBox()
self.widgetsVLayout.addLayout(self.filterHardnessHLayout)
self._setupAddCheckbox('matchScaledBox', tr("Match pictures of different dimensions"))
self._setupAddCheckbox(
"matchScaledBox", tr("Match pictures of different dimensions")
)
self.widgetsVLayout.addWidget(self.matchScaledBox)
self._setupAddCheckbox('mixFileKindBox', tr("Can mix file kind"))
self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind"))
self.widgetsVLayout.addWidget(self.mixFileKindBox)
self._setupAddCheckbox('useRegexpBox', tr("Use regular expressions when filtering"))
self._setupAddCheckbox(
"useRegexpBox", tr("Use regular expressions when filtering")
)
self.widgetsVLayout.addWidget(self.useRegexpBox)
self._setupAddCheckbox('removeEmptyFoldersBox', tr("Remove empty folders on delete or move"))
self._setupAddCheckbox(
"removeEmptyFoldersBox", tr("Remove empty folders on delete or move")
)
self.widgetsVLayout.addWidget(self.removeEmptyFoldersBox)
self._setupAddCheckbox('ignoreHardlinkMatches', tr("Ignore duplicates hardlinking to the same file"))
self._setupAddCheckbox(
"ignoreHardlinkMatches",
tr("Ignore duplicates hardlinking to the same file"),
)
self.widgetsVLayout.addWidget(self.ignoreHardlinkMatches)
self._setupAddCheckbox('debugModeBox', tr("Debug mode (restart required)"))
self._setupAddCheckbox("debugModeBox", tr("Debug mode (restart required)"))
self.widgetsVLayout.addWidget(self.debugModeBox)
self.widgetsVLayout.addWidget(QLabel(tr("Picture cache mode:")))
self.cacheTypeRadio = RadioBox(self, items=["Sqlite", "Shelve"], spread=False)
@@ -37,7 +47,9 @@ class PreferencesDialog(PreferencesDialogBase):
def _load(self, prefs, setchecked):
setchecked(self.matchScaledBox, prefs.match_scaled)
self.cacheTypeRadio.selected_index = 1 if prefs.picture_cache_type == 'shelve' else 0
self.cacheTypeRadio.selected_index = (
1 if prefs.picture_cache_type == "shelve" else 0
)
# Update UI state based on selected scan type
scan_type = prefs.get_scan_type(AppMode.Picture)
@@ -46,5 +58,6 @@ class PreferencesDialog(PreferencesDialogBase):
def _save(self, prefs, ischecked):
prefs.match_scaled = ischecked(self.matchScaledBox)
prefs.picture_cache_type = 'shelve' if self.cacheTypeRadio.selected_index == 1 else 'sqlite'
prefs.picture_cache_type = (
"shelve" if self.cacheTypeRadio.selected_index == 1 else "sqlite"
)

View File

@@ -7,17 +7,17 @@
from qtlib.column import Column
from ..results_model import ResultsModel as ResultsModelBase
class ResultsModel(ResultsModelBase):
COLUMNS = [
Column('marked', defaultWidth=30),
Column('name', defaultWidth=200),
Column('folder_path', defaultWidth=180),
Column('size', defaultWidth=60),
Column('extension', defaultWidth=40),
Column('dimensions', defaultWidth=100),
Column('exif_timestamp', defaultWidth=120),
Column('mtime', defaultWidth=120),
Column('percentage', defaultWidth=60),
Column('dupe_count', defaultWidth=80),
Column("marked", defaultWidth=30),
Column("name", defaultWidth=200),
Column("folder_path", defaultWidth=180),
Column("size", defaultWidth=60),
Column("extension", defaultWidth=40),
Column("dimensions", defaultWidth=100),
Column("exif_timestamp", defaultWidth=120),
Column("mtime", defaultWidth=120),
Column("percentage", defaultWidth=60),
Column("dupe_count", defaultWidth=80),
]

View File

@@ -10,18 +10,18 @@ from hscommon.plat import ISWINDOWS, ISOSX, ISLINUX
if op.exists(__file__):
# We want to get the absolute path or our root folder. We know that in that folder we're
# inside qt/, so we just go back one level.
BASE_PATH = op.abspath(op.join(op.dirname(__file__), '..'))
BASE_PATH = op.abspath(op.join(op.dirname(__file__), ".."))
else:
# We're under a freezed environment. Our base path is ''.
BASE_PATH = ''
HELP_PATH = op.join(BASE_PATH, 'help')
BASE_PATH = ""
HELP_PATH = op.join(BASE_PATH, "help")
if ISWINDOWS:
INITIAL_FOLDER_IN_DIALOGS = 'C:\\'
INITIAL_FOLDER_IN_DIALOGS = "C:\\"
elif ISOSX:
INITIAL_FOLDER_IN_DIALOGS = '/'
INITIAL_FOLDER_IN_DIALOGS = "/"
elif ISLINUX:
INITIAL_FOLDER_IN_DIALOGS = '/'
INITIAL_FOLDER_IN_DIALOGS = "/"
else:
# unsupported platform, however '/' is a good guess for a path which is available
INITIAL_FOLDER_IN_DIALOGS = '/'
INITIAL_FOLDER_IN_DIALOGS = "/"

View File

@@ -11,40 +11,47 @@ from core.app import AppMode
from core.scanner import ScanType
from qtlib.preferences import Preferences as PreferencesBase
class Preferences(PreferencesBase):
def _load_values(self, settings):
get = self.get_value
self.filter_hardness = get('FilterHardness', self.filter_hardness)
self.mix_file_kind = get('MixFileKind', self.mix_file_kind)
self.ignore_hardlink_matches = get('IgnoreHardlinkMatches', self.ignore_hardlink_matches)
self.use_regexp = get('UseRegexp', self.use_regexp)
self.remove_empty_folders = get('RemoveEmptyFolders', self.remove_empty_folders)
self.debug_mode = get('DebugMode', self.debug_mode)
self.destination_type = get('DestinationType', self.destination_type)
self.custom_command = get('CustomCommand', self.custom_command)
self.language = get('Language', self.language)
self.filter_hardness = get("FilterHardness", self.filter_hardness)
self.mix_file_kind = get("MixFileKind", self.mix_file_kind)
self.ignore_hardlink_matches = get(
"IgnoreHardlinkMatches", self.ignore_hardlink_matches
)
self.use_regexp = get("UseRegexp", self.use_regexp)
self.remove_empty_folders = get("RemoveEmptyFolders", self.remove_empty_folders)
self.debug_mode = get("DebugMode", self.debug_mode)
self.destination_type = get("DestinationType", self.destination_type)
self.custom_command = get("CustomCommand", self.custom_command)
self.language = get("Language", self.language)
if not self.language and trans.installed_lang:
self.language = trans.installed_lang
self.tableFontSize = get('TableFontSize', self.tableFontSize)
self.resultWindowIsMaximized = get('ResultWindowIsMaximized', self.resultWindowIsMaximized)
self.resultWindowRect = self.get_rect('ResultWindowRect', self.resultWindowRect)
self.directoriesWindowRect = self.get_rect('DirectoriesWindowRect', self.directoriesWindowRect)
self.recentResults = get('RecentResults', self.recentResults)
self.recentFolders = get('RecentFolders', self.recentFolders)
self.tableFontSize = get("TableFontSize", self.tableFontSize)
self.resultWindowIsMaximized = get(
"ResultWindowIsMaximized", self.resultWindowIsMaximized
)
self.resultWindowRect = self.get_rect("ResultWindowRect", self.resultWindowRect)
self.directoriesWindowRect = self.get_rect(
"DirectoriesWindowRect", self.directoriesWindowRect
)
self.recentResults = get("RecentResults", self.recentResults)
self.recentFolders = get("RecentFolders", self.recentFolders)
self.word_weighting = get('WordWeighting', self.word_weighting)
self.match_similar = get('MatchSimilar', self.match_similar)
self.ignore_small_files = get('IgnoreSmallFiles', self.ignore_small_files)
self.small_file_threshold = get('SmallFileThreshold', self.small_file_threshold)
self.scan_tag_track = get('ScanTagTrack', self.scan_tag_track)
self.scan_tag_artist = get('ScanTagArtist', self.scan_tag_artist)
self.scan_tag_album = get('ScanTagAlbum', self.scan_tag_album)
self.scan_tag_title = get('ScanTagTitle', self.scan_tag_title)
self.scan_tag_genre = get('ScanTagGenre', self.scan_tag_genre)
self.scan_tag_year = get('ScanTagYear', self.scan_tag_year)
self.match_scaled = get('MatchScaled', self.match_scaled)
self.picture_cache_type = get('PictureCacheType', self.picture_cache_type)
self.word_weighting = get("WordWeighting", self.word_weighting)
self.match_similar = get("MatchSimilar", self.match_similar)
self.ignore_small_files = get("IgnoreSmallFiles", self.ignore_small_files)
self.small_file_threshold = get("SmallFileThreshold", self.small_file_threshold)
self.scan_tag_track = get("ScanTagTrack", self.scan_tag_track)
self.scan_tag_artist = get("ScanTagArtist", self.scan_tag_artist)
self.scan_tag_album = get("ScanTagAlbum", self.scan_tag_album)
self.scan_tag_title = get("ScanTagTitle", self.scan_tag_title)
self.scan_tag_genre = get("ScanTagGenre", self.scan_tag_genre)
self.scan_tag_year = get("ScanTagYear", self.scan_tag_year)
self.match_scaled = get("MatchScaled", self.match_scaled)
self.picture_cache_type = get("PictureCacheType", self.picture_cache_type)
def reset(self):
self.filter_hardness = 95
@@ -54,8 +61,8 @@ class Preferences(PreferencesBase):
self.remove_empty_folders = False
self.debug_mode = False
self.destination_type = 1
self.custom_command = ''
self.language = trans.installed_lang if trans.installed_lang else ''
self.custom_command = ""
self.language = trans.installed_lang if trans.installed_lang else ""
self.tableFontSize = QApplication.font().pointSize()
self.resultWindowIsMaximized = False
@@ -67,7 +74,7 @@ class Preferences(PreferencesBase):
self.word_weighting = True
self.match_similar = False
self.ignore_small_files = True
self.small_file_threshold = 10 # KB
self.small_file_threshold = 10 # KB
self.scan_tag_track = False
self.scan_tag_artist = True
self.scan_tag_album = True
@@ -75,53 +82,53 @@ class Preferences(PreferencesBase):
self.scan_tag_genre = False
self.scan_tag_year = False
self.match_scaled = False
self.picture_cache_type = 'sqlite'
self.picture_cache_type = "sqlite"
def _save_values(self, settings):
set_ = self.set_value
set_('FilterHardness', self.filter_hardness)
set_('MixFileKind', self.mix_file_kind)
set_('IgnoreHardlinkMatches', self.ignore_hardlink_matches)
set_('UseRegexp', self.use_regexp)
set_('RemoveEmptyFolders', self.remove_empty_folders)
set_('DebugMode', self.debug_mode)
set_('DestinationType', self.destination_type)
set_('CustomCommand', self.custom_command)
set_('Language', self.language)
set_("FilterHardness", self.filter_hardness)
set_("MixFileKind", self.mix_file_kind)
set_("IgnoreHardlinkMatches", self.ignore_hardlink_matches)
set_("UseRegexp", self.use_regexp)
set_("RemoveEmptyFolders", self.remove_empty_folders)
set_("DebugMode", self.debug_mode)
set_("DestinationType", self.destination_type)
set_("CustomCommand", self.custom_command)
set_("Language", self.language)
set_('TableFontSize', self.tableFontSize)
set_('ResultWindowIsMaximized', self.resultWindowIsMaximized)
self.set_rect('ResultWindowRect', self.resultWindowRect)
self.set_rect('DirectoriesWindowRect', self.directoriesWindowRect)
set_('RecentResults', self.recentResults)
set_('RecentFolders', self.recentFolders)
set_("TableFontSize", self.tableFontSize)
set_("ResultWindowIsMaximized", self.resultWindowIsMaximized)
self.set_rect("ResultWindowRect", self.resultWindowRect)
self.set_rect("DirectoriesWindowRect", self.directoriesWindowRect)
set_("RecentResults", self.recentResults)
set_("RecentFolders", self.recentFolders)
set_('WordWeighting', self.word_weighting)
set_('MatchSimilar', self.match_similar)
set_('IgnoreSmallFiles', self.ignore_small_files)
set_('SmallFileThreshold', self.small_file_threshold)
set_('ScanTagTrack', self.scan_tag_track)
set_('ScanTagArtist', self.scan_tag_artist)
set_('ScanTagAlbum', self.scan_tag_album)
set_('ScanTagTitle', self.scan_tag_title)
set_('ScanTagGenre', self.scan_tag_genre)
set_('ScanTagYear', self.scan_tag_year)
set_('MatchScaled', self.match_scaled)
set_('PictureCacheType', self.picture_cache_type)
set_("WordWeighting", self.word_weighting)
set_("MatchSimilar", self.match_similar)
set_("IgnoreSmallFiles", self.ignore_small_files)
set_("SmallFileThreshold", self.small_file_threshold)
set_("ScanTagTrack", self.scan_tag_track)
set_("ScanTagArtist", self.scan_tag_artist)
set_("ScanTagAlbum", self.scan_tag_album)
set_("ScanTagTitle", self.scan_tag_title)
set_("ScanTagGenre", self.scan_tag_genre)
set_("ScanTagYear", self.scan_tag_year)
set_("MatchScaled", self.match_scaled)
set_("PictureCacheType", self.picture_cache_type)
# scan_type is special because we save it immediately when we set it.
def get_scan_type(self, app_mode):
if app_mode == AppMode.Picture:
return self.get_value('ScanTypePicture', ScanType.FuzzyBlock)
return self.get_value("ScanTypePicture", ScanType.FuzzyBlock)
elif app_mode == AppMode.Music:
return self.get_value('ScanTypeMusic', ScanType.Tag)
return self.get_value("ScanTypeMusic", ScanType.Tag)
else:
return self.get_value('ScanTypeStandard', ScanType.Contents)
return self.get_value("ScanTypeStandard", ScanType.Contents)
def set_scan_type(self, app_mode, value):
if app_mode == AppMode.Picture:
self.set_value('ScanTypePicture', value)
self.set_value("ScanTypePicture", value)
elif app_mode == AppMode.Music:
self.set_value('ScanTypeMusic', value)
self.set_value("ScanTypeMusic", value)
else:
self.set_value('ScanTypeStandard', value)
self.set_value("ScanTypeStandard", value)

View File

@@ -6,8 +6,20 @@
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QVBoxLayout, QHBoxLayout, QLabel, QComboBox,
QSlider, QSizePolicy, QSpacerItem, QCheckBox, QLineEdit, QMessageBox, QSpinBox, QLayout
QDialog,
QDialogButtonBox,
QVBoxLayout,
QHBoxLayout,
QLabel,
QComboBox,
QSlider,
QSizePolicy,
QSpacerItem,
QCheckBox,
QLineEdit,
QMessageBox,
QSpinBox,
QLayout,
)
from hscommon.trans import trget
@@ -16,23 +28,42 @@ from qtlib.preferences import get_langnames
from .preferences import Preferences
tr = trget('ui')
tr = trget("ui")
SUPPORTED_LANGUAGES = [
'en', 'fr', 'de', 'el', 'zh_CN', 'cs', 'it', 'hy', 'ru', 'uk', 'pt_BR', 'vi', 'pl_PL', 'ko', 'es',
'nl',
"en",
"fr",
"de",
"el",
"zh_CN",
"cs",
"it",
"hy",
"ru",
"uk",
"pt_BR",
"vi",
"pl_PL",
"ko",
"es",
"nl",
]
class PreferencesDialogBase(QDialog):
def __init__(self, parent, app, **kwargs):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
super().__init__(parent, flags, **kwargs)
self.app = app
all_languages = get_langnames()
self.supportedLanguages = sorted(SUPPORTED_LANGUAGES, key=lambda lang: all_languages[lang])
self.supportedLanguages = sorted(
SUPPORTED_LANGUAGES, key=lambda lang: all_languages[lang]
)
self._setupUi()
self.filterHardnessSlider.valueChanged['int'].connect(self.filterHardnessLabel.setNum)
self.filterHardnessSlider.valueChanged["int"].connect(
self.filterHardnessLabel.setNum
)
self.buttonBox.clicked.connect(self.buttonClicked)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
@@ -51,7 +82,9 @@ class PreferencesDialogBase(QDialog):
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.filterHardnessSlider.sizePolicy().hasHeightForWidth())
sizePolicy.setHeightForWidth(
self.filterHardnessSlider.sizePolicy().hasHeightForWidth()
)
self.filterHardnessSlider.setSizePolicy(sizePolicy)
self.filterHardnessSlider.setMinimum(1)
self.filterHardnessSlider.setMaximum(100)
@@ -81,12 +114,16 @@ class PreferencesDialogBase(QDialog):
self.fontSizeLabel = QLabel(tr("Font size:"))
self.fontSizeSpinBox = QSpinBox()
self.fontSizeSpinBox.setMinimum(5)
self.widgetsVLayout.addLayout(horizontalWrap([self.fontSizeLabel, self.fontSizeSpinBox, None]))
self.widgetsVLayout.addLayout(
horizontalWrap([self.fontSizeLabel, self.fontSizeSpinBox, None])
)
self.languageLabel = QLabel(tr("Language:"), self)
self.languageComboBox = QComboBox(self)
for lang in self.supportedLanguages:
self.languageComboBox.addItem(get_langnames()[lang])
self.widgetsVLayout.addLayout(horizontalWrap([self.languageLabel, self.languageComboBox, None]))
self.widgetsVLayout.addLayout(
horizontalWrap([self.languageLabel, self.languageComboBox, None])
)
self.copyMoveLabel = QLabel(self)
self.copyMoveLabel.setText(tr("Copy and Move:"))
self.widgetsVLayout.addWidget(self.copyMoveLabel)
@@ -96,7 +133,9 @@ class PreferencesDialogBase(QDialog):
self.copyMoveDestinationComboBox.addItem(tr("Recreate absolute path"))
self.widgetsVLayout.addWidget(self.copyMoveDestinationComboBox)
self.customCommandLabel = QLabel(self)
self.customCommandLabel.setText(tr("Custom Command (arguments: %d for dupe, %r for ref):"))
self.customCommandLabel.setText(
tr("Custom Command (arguments: %d for dupe, %r for ref):")
)
self.widgetsVLayout.addWidget(self.customCommandLabel)
self.customCommandEdit = QLineEdit(self)
self.widgetsVLayout.addWidget(self.customCommandEdit)
@@ -121,7 +160,11 @@ class PreferencesDialogBase(QDialog):
self._setupPreferenceWidgets()
self.mainVLayout.addLayout(self.widgetsVLayout)
self.buttonBox = QDialogButtonBox(self)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok|QDialogButtonBox.RestoreDefaults)
self.buttonBox.setStandardButtons(
QDialogButtonBox.Cancel
| QDialogButtonBox.Ok
| QDialogButtonBox.RestoreDefaults
)
self.mainVLayout.addWidget(self.buttonBox)
self.layout().setSizeConstraint(QLayout.SetFixedSize)
@@ -169,18 +212,21 @@ class PreferencesDialogBase(QDialog):
lang = self.supportedLanguages[self.languageComboBox.currentIndex()]
oldlang = self.app.prefs.language
if oldlang not in self.supportedLanguages:
oldlang = 'en'
oldlang = "en"
if lang != oldlang:
QMessageBox.information(self, "", tr("dupeGuru has to restart for language changes to take effect."))
QMessageBox.information(
self,
"",
tr("dupeGuru has to restart for language changes to take effect."),
)
self.app.prefs.language = lang
self._save(prefs, ischecked)
def resetToDefaults(self):
self.load(Preferences())
#--- Events
# --- Events
def buttonClicked(self, button):
role = self.buttonBox.buttonRole(button)
if role == QDialogButtonBox.ResetRole:
self.resetToDefaults()

View File

@@ -8,8 +8,19 @@
from PyQt5.QtCore import Qt, QMimeData, QByteArray
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QListView,
QDialogButtonBox, QAbstractItemView, QLabel, QStyle, QSplitter, QWidget, QSizePolicy
QDialog,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QComboBox,
QListView,
QDialogButtonBox,
QAbstractItemView,
QLabel,
QStyle,
QSplitter,
QWidget,
QSizePolicy,
)
from hscommon.trans import trget
@@ -17,9 +28,10 @@ from qtlib.selectable_list import ComboboxModel, ListviewModel
from qtlib.util import verticalSpacer
from core.gui.prioritize_dialog import PrioritizeDialog as PrioritizeDialogModel
tr = trget('ui')
tr = trget("ui")
MIME_INDEXES = "application/dupeguru.rowindexes"
MIME_INDEXES = 'application/dupeguru.rowindexes'
class PrioritizationList(ListviewModel):
def flags(self, index):
@@ -27,7 +39,7 @@ class PrioritizationList(ListviewModel):
return Qt.ItemIsEnabled | Qt.ItemIsDropEnabled
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled
#--- Drag & Drop
# --- Drag & Drop
def dropMimeData(self, mimeData, action, row, column, parentIndex):
if not mimeData.hasFormat(MIME_INDEXES):
return False
@@ -36,13 +48,13 @@ class PrioritizationList(ListviewModel):
if parentIndex.isValid():
return False
strMimeData = bytes(mimeData.data(MIME_INDEXES)).decode()
indexes = list(map(int, strMimeData.split(',')))
indexes = list(map(int, strMimeData.split(",")))
self.model.move_indexes(indexes, row)
return True
def mimeData(self, indexes):
rows = {str(index.row()) for index in indexes}
data = ','.join(rows)
data = ",".join(rows)
mimeData = QMimeData()
mimeData.setData(MIME_INDEXES, QByteArray(data.encode()))
return mimeData
@@ -53,14 +65,19 @@ class PrioritizationList(ListviewModel):
def supportedDropActions(self):
return Qt.MoveAction
class PrioritizeDialog(QDialog):
def __init__(self, parent, app, **kwargs):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
super().__init__(parent, flags, **kwargs)
self._setupUi()
self.model = PrioritizeDialogModel(app=app.model)
self.categoryList = ComboboxModel(model=self.model.category_list, view=self.categoryCombobox)
self.criteriaList = ListviewModel(model=self.model.criteria_list, view=self.criteriaListView)
self.categoryList = ComboboxModel(
model=self.model.category_list, view=self.categoryCombobox
)
self.criteriaList = ListviewModel(
model=self.model.criteria_list, view=self.criteriaListView
)
self.prioritizationList = PrioritizationList(
model=self.model.prioritization_list, view=self.prioritizationListView
)
@@ -75,7 +92,7 @@ class PrioritizeDialog(QDialog):
self.setWindowTitle(tr("Re-Prioritize duplicates"))
self.resize(700, 400)
#widgets
# widgets
msg = tr(
"Add criteria to the right box and click OK to send the dupes that correspond the "
"best to these criteria to their respective group's "
@@ -85,15 +102,19 @@ class PrioritizeDialog(QDialog):
self.promptLabel.setWordWrap(True)
self.categoryCombobox = QComboBox()
self.criteriaListView = QListView()
self.addCriteriaButton = QPushButton(self.style().standardIcon(QStyle.SP_ArrowRight), "")
self.removeCriteriaButton = QPushButton(self.style().standardIcon(QStyle.SP_ArrowLeft), "")
self.addCriteriaButton = QPushButton(
self.style().standardIcon(QStyle.SP_ArrowRight), ""
)
self.removeCriteriaButton = QPushButton(
self.style().standardIcon(QStyle.SP_ArrowLeft), ""
)
self.prioritizationListView = QListView()
self.prioritizationListView.setAcceptDrops(True)
self.prioritizationListView.setDragEnabled(True)
self.prioritizationListView.setDragDropMode(QAbstractItemView.InternalMove)
self.prioritizationListView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.buttonBox = QDialogButtonBox()
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok)
# layout
self.mainLayout = QVBoxLayout(self)

View File

@@ -8,14 +8,22 @@
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QSpacerItem, QSizePolicy,
QLabel, QTableView, QAbstractItemView
QDialog,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QSpacerItem,
QSizePolicy,
QLabel,
QTableView,
QAbstractItemView,
)
from hscommon.trans import trget
from .problem_table import ProblemTable
tr = trget('ui')
tr = trget("ui")
class ProblemDialog(QDialog):
def __init__(self, parent, model, **kwargs):
@@ -62,4 +70,3 @@ class ProblemDialog(QDialog):
self.closeButton.setDefault(True)
self.horizontalLayout.addWidget(self.closeButton)
self.verticalLayout.addLayout(self.horizontalLayout)

View File

@@ -1,22 +1,22 @@
# Created By: Virgil Dupras
# Created On: 2010-04-12
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from qtlib.column import Column
from qtlib.table import Table
class ProblemTable(Table):
COLUMNS = [
Column('path', defaultWidth=150),
Column('msg', defaultWidth=150),
Column("path", defaultWidth=150),
Column("msg", defaultWidth=150),
]
def __init__(self, model, view, **kwargs):
super().__init__(model, view, **kwargs)
# we have to prevent Return from initiating editing.
# self.view.editSelected = lambda: None

View File

@@ -8,9 +8,19 @@
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtWidgets import (
QMainWindow, QMenu, QLabel, QFileDialog, QMenuBar, QWidget,
QVBoxLayout, QAbstractItemView, QStatusBar, QDialog, QPushButton, QCheckBox,
QDesktopWidget
QMainWindow,
QMenu,
QLabel,
QFileDialog,
QMenuBar,
QWidget,
QVBoxLayout,
QAbstractItemView,
QStatusBar,
QDialog,
QPushButton,
QCheckBox,
QDesktopWidget,
)
from hscommon.trans import trget
@@ -25,7 +35,8 @@ from .se.results_model import ResultsModel as ResultsModelStandard
from .me.results_model import ResultsModel as ResultsModelMusic
from .pe.results_model import ResultsModel as ResultsModelPicture
tr = trget('ui')
tr = trget("ui")
class ResultWindow(QMainWindow):
def __init__(self, parent, app, **kwargs):
@@ -54,41 +65,143 @@ class ResultWindow(QMainWindow):
def _setupActions(self):
# (name, shortcut, icon, desc, func)
ACTIONS = [
('actionDetails', 'Ctrl+I', '', tr("Details"), self.detailsTriggered),
('actionActions', '', '', tr("Actions"), self.actionsTriggered),
('actionPowerMarker', 'Ctrl+1', '', tr("Show Dupes Only"), self.powerMarkerTriggered),
('actionDelta', 'Ctrl+2', '', tr("Show Delta Values"), self.deltaTriggered),
('actionDeleteMarked', 'Ctrl+D', '', tr("Send Marked to Recycle Bin..."), self.deleteTriggered),
('actionMoveMarked', 'Ctrl+M', '', tr("Move Marked to..."), self.moveTriggered),
('actionCopyMarked', 'Ctrl+Shift+M', '', tr("Copy Marked to..."), self.copyTriggered),
('actionRemoveMarked', 'Ctrl+R', '', tr("Remove Marked from Results"), self.removeMarkedTriggered),
('actionReprioritize', '', '', tr("Re-Prioritize Results..."), self.reprioritizeTriggered),
("actionDetails", "Ctrl+I", "", tr("Details"), self.detailsTriggered),
("actionActions", "", "", tr("Actions"), self.actionsTriggered),
(
'actionRemoveSelected', 'Ctrl+Del', '',
tr("Remove Selected from Results"), self.removeSelectedTriggered
"actionPowerMarker",
"Ctrl+1",
"",
tr("Show Dupes Only"),
self.powerMarkerTriggered,
),
("actionDelta", "Ctrl+2", "", tr("Show Delta Values"), self.deltaTriggered),
(
"actionDeleteMarked",
"Ctrl+D",
"",
tr("Send Marked to Recycle Bin..."),
self.deleteTriggered,
),
(
'actionIgnoreSelected', 'Ctrl+Shift+Del', '',
tr("Add Selected to Ignore List"), self.addToIgnoreListTriggered
"actionMoveMarked",
"Ctrl+M",
"",
tr("Move Marked to..."),
self.moveTriggered,
),
(
'actionMakeSelectedReference', 'Ctrl+Space', '',
tr("Make Selected into Reference"), self.app.model.make_selected_reference
"actionCopyMarked",
"Ctrl+Shift+M",
"",
tr("Copy Marked to..."),
self.copyTriggered,
),
('actionOpenSelected', 'Ctrl+O', '', tr("Open Selected with Default Application"), self.openTriggered),
(
'actionRevealSelected', 'Ctrl+Shift+O', '',
tr("Open Containing Folder of Selected"), self.revealTriggered
"actionRemoveMarked",
"Ctrl+R",
"",
tr("Remove Marked from Results"),
self.removeMarkedTriggered,
),
(
"actionReprioritize",
"",
"",
tr("Re-Prioritize Results..."),
self.reprioritizeTriggered,
),
(
"actionRemoveSelected",
"Ctrl+Del",
"",
tr("Remove Selected from Results"),
self.removeSelectedTriggered,
),
(
"actionIgnoreSelected",
"Ctrl+Shift+Del",
"",
tr("Add Selected to Ignore List"),
self.addToIgnoreListTriggered,
),
(
"actionMakeSelectedReference",
"Ctrl+Space",
"",
tr("Make Selected into Reference"),
self.app.model.make_selected_reference,
),
(
"actionOpenSelected",
"Ctrl+O",
"",
tr("Open Selected with Default Application"),
self.openTriggered,
),
(
"actionRevealSelected",
"Ctrl+Shift+O",
"",
tr("Open Containing Folder of Selected"),
self.revealTriggered,
),
(
"actionRenameSelected",
"F2",
"",
tr("Rename Selected"),
self.renameTriggered,
),
("actionMarkAll", "Ctrl+A", "", tr("Mark All"), self.markAllTriggered),
(
"actionMarkNone",
"Ctrl+Shift+A",
"",
tr("Mark None"),
self.markNoneTriggered,
),
(
"actionInvertMarking",
"Ctrl+Alt+A",
"",
tr("Invert Marking"),
self.markInvertTriggered,
),
(
"actionMarkSelected",
"",
"",
tr("Mark Selected"),
self.markSelectedTriggered,
),
(
"actionExportToHTML",
"",
"",
tr("Export To HTML"),
self.app.model.export_to_xhtml,
),
(
"actionExportToCSV",
"",
"",
tr("Export To CSV"),
self.app.model.export_to_csv,
),
(
"actionSaveResults",
"Ctrl+S",
"",
tr("Save Results..."),
self.saveResultsTriggered,
),
(
"actionInvokeCustomCommand",
"Ctrl+Alt+I",
"",
tr("Invoke Custom Command"),
self.app.invokeCustomCommand,
),
('actionRenameSelected', 'F2', '', tr("Rename Selected"), self.renameTriggered),
('actionMarkAll', 'Ctrl+A', '', tr("Mark All"), self.markAllTriggered),
('actionMarkNone', 'Ctrl+Shift+A', '', tr("Mark None"), self.markNoneTriggered),
('actionInvertMarking', 'Ctrl+Alt+A', '', tr("Invert Marking"), self.markInvertTriggered),
('actionMarkSelected', '', '', tr("Mark Selected"), self.markSelectedTriggered),
('actionExportToHTML', '', '', tr("Export To HTML"), self.app.model.export_to_xhtml),
('actionExportToCSV', '', '', tr("Export To CSV"), self.app.model.export_to_csv),
('actionSaveResults', 'Ctrl+S', '', tr("Save Results..."), self.saveResultsTriggered),
('actionInvokeCustomCommand', 'Ctrl+Alt+I', '', tr("Invoke Custom Command"), self.app.invokeCustomCommand),
]
createActions(ACTIONS, self)
self.actionDelta.setCheckable(True)
@@ -154,7 +267,9 @@ class ResultWindow(QMainWindow):
# Columns menu
menu = self.menuColumns
self._column_actions = []
for index, (display, visible) in enumerate(self.app.model.result_table.columns.menu_items()):
for index, (display, visible) in enumerate(
self.app.model.result_table.columns.menu_items()
):
action = menu.addAction(display)
action.setCheckable(True)
action.setChecked(visible)
@@ -195,10 +310,17 @@ class ResultWindow(QMainWindow):
self.deltaValuesCheckBox = QCheckBox(tr("Delta Values"))
self.searchEdit = SearchEdit()
self.searchEdit.setMaximumWidth(300)
self.horizontalLayout = horizontalWrap([
self.actionsButton, self.detailsButton,
self.dupesOnlyCheckBox, self.deltaValuesCheckBox, None, self.searchEdit, 8
])
self.horizontalLayout = horizontalWrap(
[
self.actionsButton,
self.detailsButton,
self.dupesOnlyCheckBox,
self.deltaValuesCheckBox,
None,
self.searchEdit,
8,
]
)
self.horizontalLayout.setSpacing(8)
self.verticalLayout.addLayout(self.horizontalLayout)
self.resultsView = ResultsView(self.centralwidget)
@@ -237,14 +359,14 @@ class ResultWindow(QMainWindow):
else:
moveToScreenCenter(self)
#--- Private
# --- Private
def _update_column_actions_status(self):
# Update menu checked state
menu_items = self.app.model.result_table.columns.menu_items()
for action, (display, visible) in zip(self._column_actions, menu_items):
action.setChecked(visible)
#--- Actions
# --- Actions
def actionsTriggered(self):
self.actionsButton.showMenu()
@@ -318,14 +440,14 @@ class ResultWindow(QMainWindow):
def saveResultsTriggered(self):
title = tr("Select a file to save your results to")
files = tr("dupeGuru Results (*.dupeguru)")
destination, chosen_filter = QFileDialog.getSaveFileName(self, title, '', files)
destination, chosen_filter = QFileDialog.getSaveFileName(self, title, "", files)
if destination:
if not destination.endswith('.dupeguru'):
destination = '{}.dupeguru'.format(destination)
if not destination.endswith(".dupeguru"):
destination = "{}.dupeguru".format(destination)
self.app.model.save_as(destination)
self.app.recentResults.insertItem(destination)
#--- Events
# --- Events
def appWillSavePrefs(self):
prefs = self.app.prefs
prefs.resultWindowIsMaximized = self.isMaximized()

View File

@@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QTableView
from qtlib.table import Table
class ResultsModel(Table):
def __init__(self, app, view, **kwargs):
model = app.model.result_table
@@ -21,12 +22,12 @@ class ResultsModel(Table):
font.setPointSize(app.prefs.tableFontSize)
self.view.setFont(font)
fm = QFontMetrics(font)
view.verticalHeader().setDefaultSectionSize(fm.height()+2)
view.verticalHeader().setDefaultSectionSize(fm.height() + 2)
app.willSavePrefs.connect(self.appWillSavePrefs)
def _getData(self, row, column, role):
if column.name == 'marked':
if column.name == "marked":
if role == Qt.CheckStateRole and row.markable:
return Qt.Checked if row.marked else Qt.Unchecked
return None
@@ -37,33 +38,33 @@ class ResultsModel(Table):
if row.isref:
return QBrush(Qt.blue)
elif row.is_cell_delta(column.name):
return QBrush(QColor(255, 142, 40)) # orange
return QBrush(QColor(255, 142, 40)) # orange
elif role == Qt.FontRole:
isBold = row.isref
font = QFont(self.view.font())
font.setBold(isBold)
return font
elif role == Qt.EditRole:
if column.name == 'name':
if column.name == "name":
return row.data[column.name]
return None
def _getFlags(self, row, column):
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if column.name == 'marked':
if column.name == "marked":
if row.markable:
flags |= Qt.ItemIsUserCheckable
elif column.name == 'name':
elif column.name == "name":
flags |= Qt.ItemIsEditable
return flags
def _setData(self, row, column, value, role):
if role == Qt.CheckStateRole:
if column.name == 'marked':
if column.name == "marked":
row.marked = bool(value)
return True
elif role == Qt.EditRole:
if column.name == 'name':
if column.name == "name":
return self.model.rename_selected(value)
return False
@@ -71,7 +72,7 @@ class ResultsModel(Table):
column = self.model.COLUMNS[column]
self.model.sort(column.name, order == Qt.AscendingOrder)
#--- Properties
# --- Properties
@property
def power_marker(self):
return self.model.power_marker
@@ -88,11 +89,11 @@ class ResultsModel(Table):
def delta_values(self, value):
self.model.delta_values = value
#--- Events
# --- Events
def appWillSavePrefs(self):
self.model.columns.save_columns()
#--- model --> view
# --- model --> view
def invalidate_markings(self):
# redraw view
# HACK. this is the only way I found to update the widget without reseting everything
@@ -101,9 +102,9 @@ class ResultsModel(Table):
class ResultsView(QTableView):
#--- Override
# --- Override
def keyPressEvent(self, event):
if event.text() == ' ':
if event.text() == " ":
self.spacePressed.emit()
return
super().keyPressEvent(event)
@@ -112,5 +113,5 @@ class ResultsView(QTableView):
self.doubleClicked.emit(QModelIndex())
# We don't call the superclass' method because the default behavior is to rename the cell.
#--- Signals
# --- Signals
spacePressed = pyqtSignal()

View File

@@ -11,7 +11,8 @@ from hscommon.trans import trget
from ..details_dialog import DetailsDialog as DetailsDialogBase
from ..details_table import DetailsTable
tr = trget('ui')
tr = trget("ui")
class DetailsDialog(DetailsDialogBase):
def _setupUi(self):
@@ -26,4 +27,3 @@ class DetailsDialog(DetailsDialogBase):
self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
self.tableView.setShowGrid(False)
self.verticalLayout.addWidget(self.tableView)

View File

@@ -6,7 +6,13 @@
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import (
QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem, QWidget, QLineEdit
QVBoxLayout,
QHBoxLayout,
QLabel,
QSizePolicy,
QSpacerItem,
QWidget,
QLineEdit,
)
from hscommon.trans import trget
@@ -17,7 +23,8 @@ from core.scanner import ScanType
from ..preferences_dialog import PreferencesDialogBase
tr = trget('ui')
tr = trget("ui")
class PreferencesDialog(PreferencesDialogBase):
def _setupPreferenceWidgets(self):
@@ -26,24 +33,36 @@ class PreferencesDialog(PreferencesDialogBase):
self.widget = QWidget(self)
self.widget.setMinimumSize(QSize(0, 136))
self.verticalLayout_4 = QVBoxLayout(self.widget)
self._setupAddCheckbox('wordWeightingBox', tr("Word weighting"), self.widget)
self._setupAddCheckbox("wordWeightingBox", tr("Word weighting"), self.widget)
self.verticalLayout_4.addWidget(self.wordWeightingBox)
self._setupAddCheckbox('matchSimilarBox', tr("Match similar words"), self.widget)
self._setupAddCheckbox(
"matchSimilarBox", tr("Match similar words"), self.widget
)
self.verticalLayout_4.addWidget(self.matchSimilarBox)
self._setupAddCheckbox('mixFileKindBox', tr("Can mix file kind"), self.widget)
self._setupAddCheckbox("mixFileKindBox", tr("Can mix file kind"), self.widget)
self.verticalLayout_4.addWidget(self.mixFileKindBox)
self._setupAddCheckbox('useRegexpBox', tr("Use regular expressions when filtering"), self.widget)
self._setupAddCheckbox(
"useRegexpBox", tr("Use regular expressions when filtering"), self.widget
)
self.verticalLayout_4.addWidget(self.useRegexpBox)
self._setupAddCheckbox('removeEmptyFoldersBox', tr("Remove empty folders on delete or move"), self.widget)
self._setupAddCheckbox(
"removeEmptyFoldersBox",
tr("Remove empty folders on delete or move"),
self.widget,
)
self.verticalLayout_4.addWidget(self.removeEmptyFoldersBox)
self.horizontalLayout_2 = QHBoxLayout()
self._setupAddCheckbox('ignoreSmallFilesBox', tr("Ignore files smaller than"), self.widget)
self._setupAddCheckbox(
"ignoreSmallFilesBox", tr("Ignore files smaller than"), self.widget
)
self.horizontalLayout_2.addWidget(self.ignoreSmallFilesBox)
self.sizeThresholdEdit = QLineEdit(self.widget)
sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.sizeThresholdEdit.sizePolicy().hasHeightForWidth())
sizePolicy.setHeightForWidth(
self.sizeThresholdEdit.sizePolicy().hasHeightForWidth()
)
self.sizeThresholdEdit.setSizePolicy(sizePolicy)
self.sizeThresholdEdit.setMaximumSize(QSize(50, 16777215))
self.horizontalLayout_2.addWidget(self.sizeThresholdEdit)
@@ -54,11 +73,14 @@ class PreferencesDialog(PreferencesDialogBase):
self.horizontalLayout_2.addItem(spacerItem1)
self.verticalLayout_4.addLayout(self.horizontalLayout_2)
self._setupAddCheckbox(
'ignoreHardlinkMatches',
tr("Ignore duplicates hardlinking to the same file"), self.widget
"ignoreHardlinkMatches",
tr("Ignore duplicates hardlinking to the same file"),
self.widget,
)
self.verticalLayout_4.addWidget(self.ignoreHardlinkMatches)
self._setupAddCheckbox('debugModeBox', tr("Debug mode (restart required)"), self.widget)
self._setupAddCheckbox(
"debugModeBox", tr("Debug mode (restart required)"), self.widget
)
self.verticalLayout_4.addWidget(self.debugModeBox)
self.widgetsVLayout.addWidget(self.widget)
self._setupBottomPart()
@@ -81,4 +103,3 @@ class PreferencesDialog(PreferencesDialogBase):
prefs.word_weighting = ischecked(self.wordWeightingBox)
prefs.ignore_small_files = ischecked(self.ignoreSmallFilesBox)
prefs.small_file_threshold = tryint(self.sizeThresholdEdit.text())

View File

@@ -7,16 +7,16 @@
from qtlib.column import Column
from ..results_model import ResultsModel as ResultsModelBase
class ResultsModel(ResultsModelBase):
COLUMNS = [
Column('marked', defaultWidth=30),
Column('name', defaultWidth=200),
Column('folder_path', defaultWidth=180),
Column('size', defaultWidth=60),
Column('extension', defaultWidth=40),
Column('mtime', defaultWidth=120),
Column('percentage', defaultWidth=60),
Column('words', defaultWidth=120),
Column('dupe_count', defaultWidth=80),
Column("marked", defaultWidth=30),
Column("name", defaultWidth=200),
Column("folder_path", defaultWidth=180),
Column("size", defaultWidth=60),
Column("extension", defaultWidth=40),
Column("mtime", defaultWidth=120),
Column("percentage", defaultWidth=60),
Column("words", defaultWidth=120),
Column("dupe_count", defaultWidth=80),
]

View File

@@ -1,17 +1,17 @@
# Created By: Virgil Dupras
# Created On: 2010-02-12
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
class StatsLabel:
def __init__(self, model, view):
self.view = view
self.model = model
self.model.view = self
def refresh(self):
self.view.setText(self.model.display)