diff --git a/core/app.py b/core/app.py index 5f2a8f94..55d9c308 100644 --- a/core/app.py +++ b/core/app.py @@ -55,6 +55,11 @@ class JobType: Copy = 'job_copy' Delete = 'job_delete' +class AppMode: + Standard = 0 + Music = 1 + Picture = 2 + JOBID2TITLE = { JobType.Scan: tr("Scanning for duplicates"), JobType.Load: tr("Loading"), @@ -149,6 +154,7 @@ class DupeGuru(Broadcaster): # open_path(path) # reveal_path(path) # ask_yes_no(prompt) --> bool + # create_results_window() # show_results_window() # show_problem_dialog() # select_dest_folder(prompt: str) --> str @@ -166,9 +172,8 @@ class DupeGuru(Broadcaster): self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME) if not op.exists(self.appdata): os.makedirs(self.appdata) + self.app_mode = AppMode.Standard self.discarded_file_count = 0 - self.fileclasses = [fs.File] - self.folderclass = fs.Folder self.directories = directories.Directories() self.results = results.Results(self) self.ignore_list = IgnoreList() @@ -187,10 +192,10 @@ class DupeGuru(Broadcaster): self.problem_dialog = ProblemDialog(self) self.ignore_list_dialog = IgnoreListDialog(self) self.stats_label = StatsLabel(self) - self.result_table = self._create_result_table() + self.result_table = None self.deletion_options = DeletionOptions() self.progress_window = ProgressWindow(self._job_completed) - children = [self.result_table, self.directory_tree, self.stats_label, self.details_panel] + children = [self.directory_tree, self.stats_label, self.details_panel] for child in children: child.connect() @@ -746,6 +751,11 @@ class DupeGuru(Broadcaster): if hasattr(scanner, k): setattr(scanner, k, v) self.results.groups = [] + if self.result_table is not None: + self.result_table.disconnect() + self.result_table = self._create_result_table() + self.result_table.connect() + self.view.create_results_window() self._results_changed() def do(j): diff --git a/core/gui/details_panel.py b/core/gui/details_panel.py index 7245c3f6..5490edf3 100644 --- a/core/gui/details_panel.py +++ b/core/gui/details_panel.py @@ -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 hscommon.gui.base import GUIObject @@ -11,14 +11,14 @@ from .base import DupeGuruGUIObject class DetailsPanel(GUIObject, DupeGuruGUIObject): def __init__(self, app): - GUIObject.__init__(self) + GUIObject.__init__(self, multibind=True) DupeGuruGUIObject.__init__(self, app) self._table = [] - + def _view_updated(self): self._refresh() self.view.refresh() - + #--- Private def _refresh(self): if self.app.selected_dupes: @@ -33,16 +33,16 @@ class DetailsPanel(GUIObject, DupeGuruGUIObject): data2 = self.app.get_display_info(ref, group, False) columns = self.app.result_table.COLUMNS[1:] # first column is the 'marked' column self._table = [(c.display, data1[c.name], data2[c.name]) for c in columns] - + #--- Public def row_count(self): return len(self._table) - + def row(self, row_index): return self._table[row_index] - + #--- Event Handlers def dupes_selected(self): self._refresh() self.view.refresh() - + diff --git a/core/tests/app_test.py b/core/tests/app_test.py index f3b1acb1..25a8e97d 100644 --- a/core/tests/app_test.py +++ b/core/tests/app_test.py @@ -384,7 +384,7 @@ class TestCaseDupeGuruWithResults: app.JOB = Job(1, lambda *args, **kw: False) # Cancels the task add_fake_files_to_directories(app.directories, self.objects) # We want the scan to at least start app.start_scanning() # will be cancelled immediately - eq_(len(self.rtable), 0) + eq_(len(app.result_table), 0) def test_selected_dupes_after_removal(self, do_setup): # Purge the app's `selected_dupes` attribute when removing dupes, or else it might cause a diff --git a/core/tests/base.py b/core/tests/base.py index 232496a9..693e655e 100644 --- a/core/tests/base.py +++ b/core/tests/base.py @@ -4,7 +4,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from hscommon.testutil import TestApp as TestAppBase, eq_, with_app # noqa +from hscommon.testutil import TestApp as TestAppBase, CallLogger, eq_, with_app # noqa from hscommon.path import Path from hscommon.util import get_file_ext, format_size from hscommon.gui.column import Column @@ -41,6 +41,8 @@ class DupeGuruView: def ask_yes_no(self, prompt): return True # always answer yes + def create_results_window(self): + pass class ResultTable(ResultTableBase): COLUMNS = [ @@ -59,12 +61,16 @@ class DupeGuru(DupeGuruBase): def __init__(self): DupeGuruBase.__init__(self, DupeGuruView()) self.appdata = '/tmp' + self.result_table = self._create_result_table() + self.result_table.connect() def _prioritization_categories(self): return prioritize.all_categories() def _create_result_table(self): - return ResultTable(self) + result = ResultTable(self) + result.view = CallLogger() + return result class NamedObject: @@ -141,7 +147,6 @@ class TestApp(TestAppBase): TestAppBase.__init__(self) self.app = DupeGuru() self.default_parent = self.app - self.rtable = link_gui(self.app.result_table) self.dtree = link_gui(self.app.directory_tree) self.dpanel = link_gui(self.app.details_panel) self.slabel = link_gui(self.app.stats_label) @@ -155,6 +160,11 @@ class TestApp(TestAppBase): link_gui(self.app.progress_window.jobdesc_textfield) link_gui(self.app.progress_window.progressdesc_textfield) + @property + def rtable(self): + # rtable is a property because its instance can be replaced during execution + return self.app.result_table + #--- Helpers def select_pri_criterion(self, name): # Select a main prioritize criterion by name instead of by index. Makes tests more diff --git a/core_me/app.py b/core_me/app.py deleted file mode 100644 index 82961d37..00000000 --- a/core_me/app.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2016 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 -# http://www.gnu.org/licenses/gpl-3.0.html - -from core.app import DupeGuru as DupeGuruBase -from . import prioritize -from . import __appname__ -from . import scanner, fs -from .result_table import ResultTable - -class DupeGuru(DupeGuruBase): - NAME = __appname__ - METADATA_TO_READ = [ - 'size', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist', - 'album', 'genre', 'year', 'track', 'comment' - ] - SCANNER_CLASS = scanner.ScannerME - - def __init__(self, view): - DupeGuruBase.__init__(self, view) - self.fileclasses = [fs.MusicFile] - - def _get_dupe_sort_key(self, dupe, get_group, key, delta): - if key == 'folder_path': - dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path) - return str(dupe_folder_path).lower() - return DupeGuruBase._get_dupe_sort_key(self, dupe, get_group, key, delta) - - def _get_group_sort_key(self, group, key): - if key == 'folder_path': - dupe_folder_path = getattr(group.ref, 'display_folder_path', group.ref.folder_path) - return str(dupe_folder_path).lower() - return DupeGuruBase._get_group_sort_key(self, group, key) - - def _prioritization_categories(self): - return prioritize.all_categories() - - def _create_result_table(self): - return ResultTable(self) diff --git a/core_se/app.py b/core_se/app.py index a764bb52..375e6851 100644 --- a/core_se/app.py +++ b/core_se/app.py @@ -4,24 +4,69 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.app import DupeGuru as DupeGuruBase +from core.app import DupeGuru as DupeGuruBase, AppMode from core import prioritize +import core_me.fs +import core_me.prioritize +import core_me.result_table +import core_me.scanner from . import __appname__, fs, scanner from .result_table import ResultTable class DupeGuru(DupeGuruBase): NAME = __appname__ - METADATA_TO_READ = ['size', 'mtime'] - SCANNER_CLASS = scanner.ScannerSE def __init__(self, view): DupeGuruBase.__init__(self, view) - self.fileclasses = [fs.File] self.folderclass = fs.Folder + def _get_dupe_sort_key(self, dupe, get_group, key, delta): + if self.app_mode == AppMode.Music: + if key == 'folder_path': + dupe_folder_path = getattr(dupe, 'display_folder_path', dupe.folder_path) + return str(dupe_folder_path).lower() + return DupeGuruBase._get_dupe_sort_key(self, dupe, get_group, key, delta) + + def _get_group_sort_key(self, group, key): + if self.app_mode == AppMode.Music: + if key == 'folder_path': + dupe_folder_path = getattr(group.ref, 'display_folder_path', group.ref.folder_path) + return str(dupe_folder_path).lower() + return DupeGuruBase._get_group_sort_key(self, group, key) + def _prioritization_categories(self): - return prioritize.all_categories() + if self.app_mode == AppMode.Music: + return prioritize.all_categories() + else: + return prioritize.all_categories() def _create_result_table(self): - return ResultTable(self) + if self.app_mode == AppMode.Music: + return core_me.result_table.ResultTable(self) + else: + return ResultTable(self) + + @property + def fileclasses(self): + if self.app_mode == AppMode.Music: + return [core_me.fs.MusicFile] + else: + return [fs.File] + + @property + def SCANNER_CLASS(self): + if self.app_mode == AppMode.Music: + return core_me.scanner.ScannerME + else: + return scanner.ScannerSE + + @property + def METADATA_TO_READ(self): + if self.app_mode == AppMode.Music: + return [ + 'size', 'mtime', 'duration', 'bitrate', 'samplerate', 'title', 'artist', + 'album', 'genre', 'year', 'track', 'comment' + ] + else: + return ['size', 'mtime'] diff --git a/hscommon b/hscommon index ea634cef..316af1bc 160000 --- a/hscommon +++ b/hscommon @@ -1 +1 @@ -Subproject commit ea634cefdf78ae9e4c7470e571fce859760f6f38 +Subproject commit 316af1bca53915f99b9bb874064cba6bee881cc1 diff --git a/qt/base/app.py b/qt/base/app.py index 3f41ded8..2366afb1 100644 --- a/qt/base/app.py +++ b/qt/base/app.py @@ -47,7 +47,6 @@ class DupeGuru(QObject): self.prefs.load() self.model = self.MODELCLASS(view=self) self._setup() - self.prefsChanged.emit(self.prefs) #--- Private def _setup(self): @@ -55,15 +54,14 @@ class DupeGuru(QObject): self._update_options() self.recentResults = Recent(self, 'recentResults') self.recentResults.mustOpenItem.connect(self.model.load_from) + self.resultWindow = None + self.details_dialog = None self.directories_dialog = DirectoriesDialog(self) - self.resultWindow = self.RESULT_WINDOW_CLASS(self.directories_dialog, self) - self.progress_window = ProgressWindow(self.resultWindow, self.model.progress_window) - self.details_dialog = self.DETAILS_DIALOG_CLASS(self.resultWindow, self) - self.problemDialog = ProblemDialog(parent=self.resultWindow, model=self.model.problem_dialog) - self.ignoreListDialog = IgnoreListDialog(parent=self.resultWindow, model=self.model.ignore_list_dialog) - self.deletionOptions = DeletionOptions(parent=self.resultWindow, model=self.model.deletion_options) - self.preferences_dialog = self.PREFERENCES_DIALOG_CLASS(self.resultWindow, self) - self.about_box = AboutBox(self.resultWindow, 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.about_box = AboutBox(self.directories_dialog, self) self.directories_dialog.show() self.model.load() @@ -94,6 +92,7 @@ class DupeGuru(QObject): 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) #--- Public def add_selected_to_ignore_list(self): @@ -112,14 +111,15 @@ class DupeGuru(QObject): self.model.invoke_custom_command() def show_details(self): - self.details_dialog.show() + if self.details_dialog is not None: + self.details_dialog.show() def showResultsWindow(self): - self.resultWindow.show() + if self.resultWindow is not None: + self.resultWindow.show() #--- Signals willSavePrefs = pyqtSignal() - prefsChanged = pyqtSignal(object) #--- Events def finishedLaunching(self): @@ -143,13 +143,14 @@ class DupeGuru(QObject): desktop.open_path(debugLogPath) def preferencesTriggered(self): - self.preferences_dialog.load() - result = self.preferences_dialog.exec() + preferences_dialog = self.PREFERENCES_DIALOG_CLASS(self.directories_dialog, self) + preferences_dialog.load() + result = preferences_dialog.exec() if result == QDialog.Accepted: - self.preferences_dialog.save() + preferences_dialog.save() self.prefs.save() self._update_options() - self.prefsChanged.emit(self.prefs) + preferences_dialog.setParent(None) def quitTriggered(self): self.directories_dialog.close() @@ -176,6 +177,18 @@ class DupeGuru(QObject): def ask_yes_no(self, prompt): return self.confirm('', prompt) + def create_results_window(self): + """Creates resultWindow and details_dialog depending on the selected ``app_mode``. + """ + if self.details_dialog is not None: + self.details_dialog.close() + self.details_dialog.setParent(None) + if self.resultWindow is not None: + self.resultWindow.close() + self.resultWindow.setParent(None) + self.resultWindow = self.RESULT_WINDOW_CLASS(self.directories_dialog, self) + self.details_dialog = self.DETAILS_DIALOG_CLASS(self.resultWindow, self) + def show_results_window(self): self.showResultsWindow() diff --git a/qt/base/directories_dialog.py b/qt/base/directories_dialog.py index 3a721180..e5a738c4 100644 --- a/qt/base/directories_dialog.py +++ b/qt/base/directories_dialog.py @@ -13,6 +13,8 @@ from PyQt5.QtWidgets import ( from PyQt5.QtGui import QPixmap, QIcon from hscommon.trans import trget +from core.app import AppMode +from qtlib.radio_box import RadioBox from qtlib.recent import Recent from qtlib.util import moveToScreenCenter, createActions @@ -28,9 +30,7 @@ class DirectoriesDialog(QMainWindow): self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.recentFolders = Recent(self.app, 'recentFolders') self._setupUi() - SCAN_TYPE_ORDER = [so.scan_type for so in self.app.model.SCANNER_CLASS.get_scan_options()] - scan_type_index = SCAN_TYPE_ORDER.index(self.app.prefs.scan_type) - self.scanTypeComboBox.setCurrentIndex(scan_type_index) + self._updateScanTypeList() self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView) self.directoriesDelegate = DirectoriesDelegate() self.treeView.setItemDelegate(self.directoriesDelegate) @@ -41,11 +41,12 @@ class DirectoriesDialog(QMainWindow): self._updateAddButton() self._updateRemoveButton() self._updateLoadResultsButton() + self._updateActionsState() self._setupBindings() def _setupBindings(self): + self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected) self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger) - self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged) self.scanButton.clicked.connect(self.scanButtonClicked) self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger) self.addFolderButton.clicked.connect(self.actionAddFolder.trigger) @@ -123,6 +124,13 @@ class DirectoriesDialog(QMainWindow): self.treeView.setUniformRowHeights(True) self.verticalLayout.addWidget(self.treeView) hl = QHBoxLayout() + label = QLabel(tr("Application Mode:"), self) + label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + hl.addWidget(label) + self.appModeRadioBox = RadioBox(self, items=[tr("Standard"), tr("Music")], spread=False) + hl.addWidget(self.appModeRadioBox) + self.verticalLayout.addLayout(hl) + hl = QHBoxLayout() hl.setAlignment(Qt.AlignLeft) label = QLabel(tr("Scan Type:"), self) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) @@ -130,10 +138,8 @@ class DirectoriesDialog(QMainWindow): self.scanTypeComboBox = QComboBox(self) self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.scanTypeComboBox.setMaximumWidth(400) - for scan_option in self.app.model.SCANNER_CLASS.get_scan_options(): - self.scanTypeComboBox.addItem(scan_option.label) hl.addWidget(self.scanTypeComboBox) - self.showPreferencesButton = QPushButton(tr("Options"), self.centralwidget) + self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget) self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) hl.addWidget(self.showPreferencesButton) self.verticalLayout.addLayout(hl) @@ -172,6 +178,9 @@ class DirectoriesDialog(QMainWindow): header.setSectionResizeMode(1, QHeaderView.Fixed) header.resizeSection(1, 100) + def _updateActionsState(self): + self.actionShowResultsWindow.setEnabled(self.app.resultWindow is not None) + def _updateAddButton(self): if self.recentFolders.isEmpty(): self.addFolderButton.setMenu(None) @@ -191,6 +200,23 @@ class DirectoriesDialog(QMainWindow): else: self.loadResultsButton.setMenu(self.menuRecentResults) + def _updateScanTypeList(self): + try: + self.scanTypeComboBox.currentIndexChanged[int].disconnect(self.scanTypeChanged) + except TypeError: + # Not connected, ignore + pass + self.scanTypeComboBox.clear() + scan_options = self.app.model.SCANNER_CLASS.get_scan_options() + for scan_option in scan_options: + self.scanTypeComboBox.addItem(scan_option.label) + SCAN_TYPE_ORDER = [so.scan_type for so in scan_options] + selected_scan_type = self.app.prefs.get_scan_type(self.app.model.app_mode) + scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type) + self.scanTypeComboBox.setCurrentIndex(scan_type_index) + self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged) + self.app._update_options() + #--- QWidget overrides def closeEvent(self, event): event.accept() @@ -213,6 +239,14 @@ class DirectoriesDialog(QMainWindow): self.app.model.add_directory(dirpath) self.recentFolders.insertItem(dirpath) + def appModeButtonSelected(self, index): + if index == 1: + mode = AppMode.Music + else: + mode = AppMode.Standard + self.app.model.app_mode = mode + self._updateScanTypeList() + def appWillSavePrefs(self): self.app.prefs.directoriesWindowRect = self.geometry() @@ -241,7 +275,7 @@ class DirectoriesDialog(QMainWindow): def scanTypeChanged(self, index): scan_options = self.app.model.SCANNER_CLASS.get_scan_options() - self.app.prefs.scan_type = 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): diff --git a/qt/base/preferences.py b/qt/base/preferences.py index 96838f12..cd319a44 100644 --- a/qt/base/preferences.py +++ b/qt/base/preferences.py @@ -7,6 +7,8 @@ from PyQt5.QtWidgets import QApplication from hscommon import trans +from core.app import AppMode +from core.scanner import ScanType from qtlib.preferences import Preferences as PreferencesBase class Preferences(PreferencesBase): @@ -89,10 +91,14 @@ class Preferences(PreferencesBase): self._save_specific(settings) # scan_type is special because we save it immediately when we set it. - @property - def scan_type(self): - return self.get_value('ScanType', self.DEFAULT_SCAN_TYPE) + def get_scan_type(self, app_mode): + if app_mode == AppMode.Music: + return self.get_value('ScanTypeMusic', ScanType.Tag) + else: + return self.get_value('ScanTypeStandard', ScanType.Contents) - @scan_type.setter - def scan_type(self, value): - self.set_value('ScanType', value) + def set_scan_type(self, app_mode, value): + if app_mode == AppMode.Music: + self.set_value('ScanTypeMusic', value) + else: + self.set_value('ScanTypeStandard', value) diff --git a/qt/base/results_model.py b/qt/base/results_model.py index 0b08a415..85841780 100644 --- a/qt/base/results_model.py +++ b/qt/base/results_model.py @@ -1,9 +1,9 @@ # Created By: Virgil Dupras # Created On: 2009-04-23 # 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, pyqtSignal, QModelIndex @@ -17,13 +17,17 @@ class ResultsModel(Table): model = app.model.result_table super().__init__(model, view, **kwargs) view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder) - - app.prefsChanged.connect(self.appPrefsChanged) + font = view.font() + font.setPointSize(app.prefs.tableFontSize) + self.view.setFont(font) + fm = QFontMetrics(font) + view.verticalHeader().setDefaultSectionSize(fm.height()+2) + app.willSavePrefs.connect(self.appWillSavePrefs) - + def _getData(self, row, column, role): if column.name == 'marked': - if role == Qt.CheckStateRole and row.markable: + if role == Qt.CheckStateRole and row.markable: return Qt.Checked if row.marked else Qt.Unchecked return None if role == Qt.DisplayRole: @@ -43,7 +47,7 @@ class ResultsModel(Table): 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': @@ -52,7 +56,7 @@ class ResultsModel(Table): elif column.name == 'name': flags |= Qt.ItemIsEditable return flags - + def _setData(self, row, column, value, role): if role == Qt.CheckStateRole: if column.name == 'marked': @@ -62,46 +66,39 @@ class ResultsModel(Table): if column.name == 'name': return self.model.rename_selected(value) return False - + def sort(self, column, order): column = self.model.COLUMNS[column] self.model.sort(column.name, order == Qt.AscendingOrder) - + #--- Properties @property def power_marker(self): return self.model.power_marker - + @power_marker.setter def power_marker(self, value): self.model.power_marker = value - + @property def delta_values(self): return self.model.delta_values - + @delta_values.setter def delta_values(self, value): self.model.delta_values = value - + #--- Events - def appPrefsChanged(self, prefs): - font = self.view.font() - font.setPointSize(prefs.tableFontSize) - self.view.setFont(font) - fm = QFontMetrics(font) - self.view.verticalHeader().setDefaultSectionSize(fm.height()+2) - def appWillSavePrefs(self): self.model.columns.save_columns() - + #--- model --> view def invalidate_markings(self): # redraw view # HACK. this is the only way I found to update the widget without reseting everything self.view.scroll(0, 1) self.view.scroll(0, -1) - + class ResultsView(QTableView): #--- Override @@ -110,10 +107,10 @@ class ResultsView(QTableView): self.spacePressed.emit() return super().keyPressEvent(event) - + def mouseDoubleClickEvent(self, event): self.doubleClicked.emit(QModelIndex()) # We don't call the superclass' method because the default behavior is to rename the cell. - + #--- Signals spacePressed = pyqtSignal() diff --git a/qt/me/app.py b/qt/me/app.py deleted file mode 100644 index 3158274f..00000000 --- a/qt/me/app.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2016 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 -# http://www.gnu.org/licenses/gpl-3.0.html - -from core_me import __appname__ -from core_me.app import DupeGuru as DupeGuruModel - -from ..base.app import DupeGuru as DupeGuruBase -from .details_dialog import DetailsDialog -from .results_model import ResultsModel -from .preferences import Preferences -from .preferences_dialog import PreferencesDialog - -class DupeGuru(DupeGuruBase): - MODELCLASS = DupeGuruModel - EDITION = 'me' - LOGO_NAME = 'logo_me' - NAME = __appname__ - - DETAILS_DIALOG_CLASS = DetailsDialog - RESULT_MODEL_CLASS = ResultsModel - PREFERENCES_CLASS = Preferences - PREFERENCES_DIALOG_CLASS = PreferencesDialog - - def _update_options(self): - DupeGuruBase._update_options(self) - self.model.options['min_match_percentage'] = self.prefs.filter_hardness - self.model.options['scan_type'] = self.prefs.scan_type - self.model.options['word_weighting'] = self.prefs.word_weighting - self.model.options['match_similar_words'] = self.prefs.match_similar - scanned_tags = set() - if self.prefs.scan_tag_track: - scanned_tags.add('track') - if self.prefs.scan_tag_artist: - scanned_tags.add('artist') - if self.prefs.scan_tag_album: - scanned_tags.add('album') - if self.prefs.scan_tag_title: - scanned_tags.add('title') - if self.prefs.scan_tag_genre: - scanned_tags.add('genre') - if self.prefs.scan_tag_year: - scanned_tags.add('year') - self.model.options['scanned_tags'] = scanned_tags - diff --git a/qt/me/installer.aip b/qt/me/installer.aip deleted file mode 100644 index f3fb76a7..00000000 --- a/qt/me/installer.aip +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/qt/me/preferences.py b/qt/me/preferences.py deleted file mode 100644 index 47e08903..00000000 --- a/qt/me/preferences.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2016 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 -# http://www.gnu.org/licenses/gpl-3.0.html - -from core.scanner import ScanType - -from ..base.preferences import Preferences as PreferencesBase - -class Preferences(PreferencesBase): - DEFAULT_SCAN_TYPE = ScanType.Tag - - def _load_specific(self, settings): - get = self.get_value - self.word_weighting = get('WordWeighting', self.word_weighting) - self.match_similar = get('MatchSimilar', self.match_similar) - 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) - - def _reset_specific(self): - self.filter_hardness = 80 - self.word_weighting = True - self.match_similar = False - self.scan_tag_track = False - self.scan_tag_artist = True - self.scan_tag_album = True - self.scan_tag_title = True - self.scan_tag_genre = False - self.scan_tag_year = False - - def _save_specific(self, settings): - set_ = self.set_value - set_('WordWeighting', self.word_weighting) - set_('MatchSimilar', self.match_similar) - 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) - diff --git a/qt/me/preferences_dialog.py b/qt/me/preferences_dialog.py index 777a2e7d..535b1d12 100644 --- a/qt/me/preferences_dialog.py +++ b/qt/me/preferences_dialog.py @@ -10,10 +10,11 @@ from PyQt5.QtWidgets import ( ) from hscommon.trans import trget +from core.app import AppMode from core.scanner import ScanType from ..base.preferences_dialog import PreferencesDialogBase -from . import preferences +from ..se import preferences tr = trget('ui') @@ -74,7 +75,7 @@ class PreferencesDialog(PreferencesDialogBase): setchecked(self.wordWeightingBox, prefs.word_weighting) # Update UI state based on selected scan type - scan_type = prefs.scan_type + scan_type = prefs.get_scan_type(AppMode.Music) word_based = scan_type in ( ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder, ScanType.Tag diff --git a/qt/se/app.py b/qt/se/app.py index 43005a70..70941433 100644 --- a/qt/se/app.py +++ b/qt/se/app.py @@ -7,12 +7,16 @@ from core_se import __appname__ from core_se.app import DupeGuru as DupeGuruModel from core.directories import Directories as DirectoriesBase, DirectoryState +from core.app import AppMode from ..base.app import DupeGuru as DupeGuruBase -from .details_dialog import DetailsDialog -from .results_model import ResultsModel +from .details_dialog import DetailsDialog as DetailsDialogStandard +from ..me.details_dialog import DetailsDialog as DetailsDialogMusic +from .results_model import ResultsModel as ResultsModelStandard +from ..me.results_model import ResultsModel as ResultsModelMusic from .preferences import Preferences -from .preferences_dialog import PreferencesDialog +from .preferences_dialog import PreferencesDialog as PreferencesDialogStandard +from ..me.preferences_dialog import PreferencesDialog as PreferencesDialogMusic class Directories(DirectoriesBase): ROOT_PATH_TO_EXCLUDE = frozenset(['windows', 'program files']) @@ -30,10 +34,7 @@ class DupeGuru(DupeGuruBase): LOGO_NAME = 'logo_se' NAME = __appname__ - DETAILS_DIALOG_CLASS = DetailsDialog - RESULT_MODEL_CLASS = ResultsModel PREFERENCES_CLASS = Preferences - PREFERENCES_DIALOG_CLASS = PreferencesDialog def _setup(self): self.directories = Directories() @@ -42,9 +43,43 @@ class DupeGuru(DupeGuruBase): def _update_options(self): DupeGuruBase._update_options(self) self.model.options['min_match_percentage'] = self.prefs.filter_hardness - self.model.options['scan_type'] = self.prefs.scan_type 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') + if self.prefs.scan_tag_artist: + scanned_tags.add('artist') + if self.prefs.scan_tag_album: + scanned_tags.add('album') + if self.prefs.scan_tag_title: + scanned_tags.add('title') + if self.prefs.scan_tag_genre: + scanned_tags.add('genre') + if self.prefs.scan_tag_year: + scanned_tags.add('year') + self.model.options['scanned_tags'] = scanned_tags + + @property + def DETAILS_DIALOG_CLASS(self): + if self.model.app_mode == AppMode.Music: + return DetailsDialogMusic + else: + return DetailsDialogStandard + + @property + def RESULT_MODEL_CLASS(self): + if self.model.app_mode == AppMode.Music: + return ResultsModelMusic + else: + return ResultsModelStandard + + @property + def PREFERENCES_DIALOG_CLASS(self): + if self.model.app_mode == AppMode.Music: + return PreferencesDialogMusic + else: + return PreferencesDialogStandard diff --git a/qt/se/preferences.py b/qt/se/preferences.py index 1ae96082..27bb5e0c 100644 --- a/qt/se/preferences.py +++ b/qt/se/preferences.py @@ -4,19 +4,21 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html -from core.scanner import ScanType - from ..base.preferences import Preferences as PreferencesBase class Preferences(PreferencesBase): - DEFAULT_SCAN_TYPE = ScanType.Contents - def _load_specific(self, settings): get = self.get_value 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) def _reset_specific(self): self.filter_hardness = 80 @@ -24,6 +26,12 @@ class Preferences(PreferencesBase): self.match_similar = False self.ignore_small_files = True self.small_file_threshold = 10 # KB + self.scan_tag_track = False + self.scan_tag_artist = True + self.scan_tag_album = True + self.scan_tag_title = True + self.scan_tag_genre = False + self.scan_tag_year = False def _save_specific(self, settings): set_ = self.set_value @@ -31,4 +39,10 @@ class Preferences(PreferencesBase): 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) diff --git a/qt/se/preferences_dialog.py b/qt/se/preferences_dialog.py index 5ee5ca23..01716011 100644 --- a/qt/se/preferences_dialog.py +++ b/qt/se/preferences_dialog.py @@ -13,6 +13,7 @@ from hscommon.plat import ISWINDOWS, ISLINUX from hscommon.trans import trget from hscommon.util import tryint +from core.app import AppMode from core.scanner import ScanType from ..base.preferences_dialog import PreferencesDialogBase @@ -81,7 +82,7 @@ class PreferencesDialog(PreferencesDialogBase): self.sizeThresholdEdit.setText(str(prefs.small_file_threshold)) # Update UI state based on selected scan type - scan_type = prefs.scan_type + scan_type = prefs.get_scan_type(AppMode.Standard) word_based = scan_type == ScanType.Filename self.filterHardnessSlider.setEnabled(word_based) self.matchSimilarBox.setEnabled(word_based)