1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2024-10-31 22:05:58 +00:00

qt: merge ME edition into SE

(breaks PE temporarily)

Adds a Standard/Music Application Mode button to SE and thus adds the
ability to run ME scan types in SE. When in Music mode, the
Music-specific results window, details panel and preferences panel will
show up.

All preferences except scan_type become shared between app modes
(changing the pref in a mode changes it in the other mode).

Results Window and Details Panel are now re-created at each scan
operation because they could change their type between two runs.

Preferences panel is instantiated on the fly and discarded after close.

This is a very big merge operation and I'm trying to touch as little
code as possible, sometimes at the cost of elegance. I try to minimize
the breakage that this change brings.
This commit is contained in:
Virgil Dupras 2016-05-29 22:37:38 -04:00
parent 8b878b7b13
commit 7d749779f2
18 changed files with 261 additions and 393 deletions

View File

@ -55,6 +55,11 @@ class JobType:
Copy = 'job_copy' Copy = 'job_copy'
Delete = 'job_delete' Delete = 'job_delete'
class AppMode:
Standard = 0
Music = 1
Picture = 2
JOBID2TITLE = { JOBID2TITLE = {
JobType.Scan: tr("Scanning for duplicates"), JobType.Scan: tr("Scanning for duplicates"),
JobType.Load: tr("Loading"), JobType.Load: tr("Loading"),
@ -149,6 +154,7 @@ class DupeGuru(Broadcaster):
# open_path(path) # open_path(path)
# reveal_path(path) # reveal_path(path)
# ask_yes_no(prompt) --> bool # ask_yes_no(prompt) --> bool
# create_results_window()
# show_results_window() # show_results_window()
# show_problem_dialog() # show_problem_dialog()
# select_dest_folder(prompt: str) --> str # 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) self.appdata = desktop.special_folder_path(desktop.SpecialFolder.AppData, appname=self.NAME)
if not op.exists(self.appdata): if not op.exists(self.appdata):
os.makedirs(self.appdata) os.makedirs(self.appdata)
self.app_mode = AppMode.Standard
self.discarded_file_count = 0 self.discarded_file_count = 0
self.fileclasses = [fs.File]
self.folderclass = fs.Folder
self.directories = directories.Directories() self.directories = directories.Directories()
self.results = results.Results(self) self.results = results.Results(self)
self.ignore_list = IgnoreList() self.ignore_list = IgnoreList()
@ -187,10 +192,10 @@ class DupeGuru(Broadcaster):
self.problem_dialog = ProblemDialog(self) self.problem_dialog = ProblemDialog(self)
self.ignore_list_dialog = IgnoreListDialog(self) self.ignore_list_dialog = IgnoreListDialog(self)
self.stats_label = StatsLabel(self) self.stats_label = StatsLabel(self)
self.result_table = self._create_result_table() self.result_table = None
self.deletion_options = DeletionOptions() self.deletion_options = DeletionOptions()
self.progress_window = ProgressWindow(self._job_completed) 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: for child in children:
child.connect() child.connect()
@ -746,6 +751,11 @@ class DupeGuru(Broadcaster):
if hasattr(scanner, k): if hasattr(scanner, k):
setattr(scanner, k, v) setattr(scanner, k, v)
self.results.groups = [] 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() self._results_changed()
def do(j): def do(j):

View File

@ -11,7 +11,7 @@ from .base import DupeGuruGUIObject
class DetailsPanel(GUIObject, DupeGuruGUIObject): class DetailsPanel(GUIObject, DupeGuruGUIObject):
def __init__(self, app): def __init__(self, app):
GUIObject.__init__(self) GUIObject.__init__(self, multibind=True)
DupeGuruGUIObject.__init__(self, app) DupeGuruGUIObject.__init__(self, app)
self._table = [] self._table = []

View File

@ -384,7 +384,7 @@ class TestCaseDupeGuruWithResults:
app.JOB = Job(1, lambda *args, **kw: False) # Cancels the task 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 add_fake_files_to_directories(app.directories, self.objects) # We want the scan to at least start
app.start_scanning() # will be cancelled immediately 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): 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 # Purge the app's `selected_dupes` attribute when removing dupes, or else it might cause a

View File

@ -4,7 +4,7 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html # 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.path import Path
from hscommon.util import get_file_ext, format_size from hscommon.util import get_file_ext, format_size
from hscommon.gui.column import Column from hscommon.gui.column import Column
@ -41,6 +41,8 @@ class DupeGuruView:
def ask_yes_no(self, prompt): def ask_yes_no(self, prompt):
return True # always answer yes return True # always answer yes
def create_results_window(self):
pass
class ResultTable(ResultTableBase): class ResultTable(ResultTableBase):
COLUMNS = [ COLUMNS = [
@ -59,12 +61,16 @@ class DupeGuru(DupeGuruBase):
def __init__(self): def __init__(self):
DupeGuruBase.__init__(self, DupeGuruView()) DupeGuruBase.__init__(self, DupeGuruView())
self.appdata = '/tmp' self.appdata = '/tmp'
self.result_table = self._create_result_table()
self.result_table.connect()
def _prioritization_categories(self): def _prioritization_categories(self):
return prioritize.all_categories() return prioritize.all_categories()
def _create_result_table(self): def _create_result_table(self):
return ResultTable(self) result = ResultTable(self)
result.view = CallLogger()
return result
class NamedObject: class NamedObject:
@ -141,7 +147,6 @@ class TestApp(TestAppBase):
TestAppBase.__init__(self) TestAppBase.__init__(self)
self.app = DupeGuru() self.app = DupeGuru()
self.default_parent = self.app self.default_parent = self.app
self.rtable = link_gui(self.app.result_table)
self.dtree = link_gui(self.app.directory_tree) self.dtree = link_gui(self.app.directory_tree)
self.dpanel = link_gui(self.app.details_panel) self.dpanel = link_gui(self.app.details_panel)
self.slabel = link_gui(self.app.stats_label) 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.jobdesc_textfield)
link_gui(self.app.progress_window.progressdesc_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 #--- Helpers
def select_pri_criterion(self, name): def select_pri_criterion(self, name):
# Select a main prioritize criterion by name instead of by index. Makes tests more # Select a main prioritize criterion by name instead of by index. Makes tests more

View File

@ -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)

View File

@ -4,24 +4,69 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html # 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 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 . import __appname__, fs, scanner
from .result_table import ResultTable from .result_table import ResultTable
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
NAME = __appname__ NAME = __appname__
METADATA_TO_READ = ['size', 'mtime']
SCANNER_CLASS = scanner.ScannerSE
def __init__(self, view): def __init__(self, view):
DupeGuruBase.__init__(self, view) DupeGuruBase.__init__(self, view)
self.fileclasses = [fs.File]
self.folderclass = fs.Folder 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): def _prioritization_categories(self):
if self.app_mode == AppMode.Music:
return prioritize.all_categories()
else:
return prioritize.all_categories() return prioritize.all_categories()
def _create_result_table(self): def _create_result_table(self):
if self.app_mode == AppMode.Music:
return core_me.result_table.ResultTable(self)
else:
return ResultTable(self) 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']

@ -1 +1 @@
Subproject commit ea634cefdf78ae9e4c7470e571fce859760f6f38 Subproject commit 316af1bca53915f99b9bb874064cba6bee881cc1

View File

@ -47,7 +47,6 @@ class DupeGuru(QObject):
self.prefs.load() self.prefs.load()
self.model = self.MODELCLASS(view=self) self.model = self.MODELCLASS(view=self)
self._setup() self._setup()
self.prefsChanged.emit(self.prefs)
#--- Private #--- Private
def _setup(self): def _setup(self):
@ -55,15 +54,14 @@ class DupeGuru(QObject):
self._update_options() self._update_options()
self.recentResults = Recent(self, 'recentResults') self.recentResults = Recent(self, 'recentResults')
self.recentResults.mustOpenItem.connect(self.model.load_from) self.recentResults.mustOpenItem.connect(self.model.load_from)
self.resultWindow = None
self.details_dialog = None
self.directories_dialog = DirectoriesDialog(self) self.directories_dialog = DirectoriesDialog(self)
self.resultWindow = self.RESULT_WINDOW_CLASS(self.directories_dialog, self) self.progress_window = ProgressWindow(self.directories_dialog, self.model.progress_window)
self.progress_window = ProgressWindow(self.resultWindow, self.model.progress_window) self.problemDialog = ProblemDialog(parent=self.directories_dialog, model=self.model.problem_dialog)
self.details_dialog = self.DETAILS_DIALOG_CLASS(self.resultWindow, self) self.ignoreListDialog = IgnoreListDialog(parent=self.directories_dialog, model=self.model.ignore_list_dialog)
self.problemDialog = ProblemDialog(parent=self.resultWindow, model=self.model.problem_dialog) self.deletionOptions = DeletionOptions(parent=self.directories_dialog, model=self.model.deletion_options)
self.ignoreListDialog = IgnoreListDialog(parent=self.resultWindow, model=self.model.ignore_list_dialog) self.about_box = AboutBox(self.directories_dialog, self)
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.directories_dialog.show() self.directories_dialog.show()
self.model.load() self.model.load()
@ -94,6 +92,7 @@ class DupeGuru(QObject):
self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders 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['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches
self.model.options['copymove_dest_type'] = self.prefs.destination_type 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 #--- Public
def add_selected_to_ignore_list(self): def add_selected_to_ignore_list(self):
@ -112,14 +111,15 @@ class DupeGuru(QObject):
self.model.invoke_custom_command() self.model.invoke_custom_command()
def show_details(self): def show_details(self):
if self.details_dialog is not None:
self.details_dialog.show() self.details_dialog.show()
def showResultsWindow(self): def showResultsWindow(self):
if self.resultWindow is not None:
self.resultWindow.show() self.resultWindow.show()
#--- Signals #--- Signals
willSavePrefs = pyqtSignal() willSavePrefs = pyqtSignal()
prefsChanged = pyqtSignal(object)
#--- Events #--- Events
def finishedLaunching(self): def finishedLaunching(self):
@ -143,13 +143,14 @@ class DupeGuru(QObject):
desktop.open_path(debugLogPath) desktop.open_path(debugLogPath)
def preferencesTriggered(self): def preferencesTriggered(self):
self.preferences_dialog.load() preferences_dialog = self.PREFERENCES_DIALOG_CLASS(self.directories_dialog, self)
result = self.preferences_dialog.exec() preferences_dialog.load()
result = preferences_dialog.exec()
if result == QDialog.Accepted: if result == QDialog.Accepted:
self.preferences_dialog.save() preferences_dialog.save()
self.prefs.save() self.prefs.save()
self._update_options() self._update_options()
self.prefsChanged.emit(self.prefs) preferences_dialog.setParent(None)
def quitTriggered(self): def quitTriggered(self):
self.directories_dialog.close() self.directories_dialog.close()
@ -176,6 +177,18 @@ class DupeGuru(QObject):
def ask_yes_no(self, prompt): 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``.
"""
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): def show_results_window(self):
self.showResultsWindow() self.showResultsWindow()

View File

@ -13,6 +13,8 @@ from PyQt5.QtWidgets import (
from PyQt5.QtGui import QPixmap, QIcon from PyQt5.QtGui import QPixmap, QIcon
from hscommon.trans import trget from hscommon.trans import trget
from core.app import AppMode
from qtlib.radio_box import RadioBox
from qtlib.recent import Recent from qtlib.recent import Recent
from qtlib.util import moveToScreenCenter, createActions from qtlib.util import moveToScreenCenter, createActions
@ -28,9 +30,7 @@ class DirectoriesDialog(QMainWindow):
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
self.recentFolders = Recent(self.app, 'recentFolders') self.recentFolders = Recent(self.app, 'recentFolders')
self._setupUi() self._setupUi()
SCAN_TYPE_ORDER = [so.scan_type for so in self.app.model.SCANNER_CLASS.get_scan_options()] self._updateScanTypeList()
scan_type_index = SCAN_TYPE_ORDER.index(self.app.prefs.scan_type)
self.scanTypeComboBox.setCurrentIndex(scan_type_index)
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.directoriesDelegate = DirectoriesDelegate()
self.treeView.setItemDelegate(self.directoriesDelegate) self.treeView.setItemDelegate(self.directoriesDelegate)
@ -41,11 +41,12 @@ class DirectoriesDialog(QMainWindow):
self._updateAddButton() self._updateAddButton()
self._updateRemoveButton() self._updateRemoveButton()
self._updateLoadResultsButton() self._updateLoadResultsButton()
self._updateActionsState()
self._setupBindings() self._setupBindings()
def _setupBindings(self): def _setupBindings(self):
self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected)
self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger) self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger)
self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
self.scanButton.clicked.connect(self.scanButtonClicked) self.scanButton.clicked.connect(self.scanButtonClicked)
self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger) self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
self.addFolderButton.clicked.connect(self.actionAddFolder.trigger) self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
@ -123,6 +124,13 @@ class DirectoriesDialog(QMainWindow):
self.treeView.setUniformRowHeights(True) self.treeView.setUniformRowHeights(True)
self.verticalLayout.addWidget(self.treeView) self.verticalLayout.addWidget(self.treeView)
hl = QHBoxLayout() 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) hl.setAlignment(Qt.AlignLeft)
label = QLabel(tr("Scan Type:"), self) label = QLabel(tr("Scan Type:"), self)
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
@ -130,10 +138,8 @@ class DirectoriesDialog(QMainWindow):
self.scanTypeComboBox = QComboBox(self) self.scanTypeComboBox = QComboBox(self)
self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
self.scanTypeComboBox.setMaximumWidth(400) 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) 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) self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
hl.addWidget(self.showPreferencesButton) hl.addWidget(self.showPreferencesButton)
self.verticalLayout.addLayout(hl) self.verticalLayout.addLayout(hl)
@ -172,6 +178,9 @@ class DirectoriesDialog(QMainWindow):
header.setSectionResizeMode(1, QHeaderView.Fixed) header.setSectionResizeMode(1, QHeaderView.Fixed)
header.resizeSection(1, 100) header.resizeSection(1, 100)
def _updateActionsState(self):
self.actionShowResultsWindow.setEnabled(self.app.resultWindow is not None)
def _updateAddButton(self): def _updateAddButton(self):
if self.recentFolders.isEmpty(): if self.recentFolders.isEmpty():
self.addFolderButton.setMenu(None) self.addFolderButton.setMenu(None)
@ -191,6 +200,23 @@ class DirectoriesDialog(QMainWindow):
else: else:
self.loadResultsButton.setMenu(self.menuRecentResults) 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 #--- QWidget overrides
def closeEvent(self, event): def closeEvent(self, event):
event.accept() event.accept()
@ -213,6 +239,14 @@ class DirectoriesDialog(QMainWindow):
self.app.model.add_directory(dirpath) self.app.model.add_directory(dirpath)
self.recentFolders.insertItem(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): def appWillSavePrefs(self):
self.app.prefs.directoriesWindowRect = self.geometry() self.app.prefs.directoriesWindowRect = self.geometry()
@ -241,7 +275,7 @@ class DirectoriesDialog(QMainWindow):
def scanTypeChanged(self, index): def scanTypeChanged(self, index):
scan_options = self.app.model.SCANNER_CLASS.get_scan_options() 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() self.app._update_options()
def selectionChanged(self, selected, deselected): def selectionChanged(self, selected, deselected):

View File

@ -7,6 +7,8 @@
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from hscommon import trans from hscommon import trans
from core.app import AppMode
from core.scanner import ScanType
from qtlib.preferences import Preferences as PreferencesBase from qtlib.preferences import Preferences as PreferencesBase
class Preferences(PreferencesBase): class Preferences(PreferencesBase):
@ -89,10 +91,14 @@ class Preferences(PreferencesBase):
self._save_specific(settings) self._save_specific(settings)
# scan_type is special because we save it immediately when we set it. # scan_type is special because we save it immediately when we set it.
@property def get_scan_type(self, app_mode):
def scan_type(self): if app_mode == AppMode.Music:
return self.get_value('ScanType', self.DEFAULT_SCAN_TYPE) return self.get_value('ScanTypeMusic', ScanType.Tag)
else:
return self.get_value('ScanTypeStandard', ScanType.Contents)
@scan_type.setter def set_scan_type(self, app_mode, value):
def scan_type(self, value): if app_mode == AppMode.Music:
self.set_value('ScanType', value) self.set_value('ScanTypeMusic', value)
else:
self.set_value('ScanTypeStandard', value)

View File

@ -17,8 +17,12 @@ class ResultsModel(Table):
model = app.model.result_table model = app.model.result_table
super().__init__(model, view, **kwargs) super().__init__(model, view, **kwargs)
view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder) view.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder)
font = view.font()
font.setPointSize(app.prefs.tableFontSize)
self.view.setFont(font)
fm = QFontMetrics(font)
view.verticalHeader().setDefaultSectionSize(fm.height()+2)
app.prefsChanged.connect(self.appPrefsChanged)
app.willSavePrefs.connect(self.appWillSavePrefs) app.willSavePrefs.connect(self.appWillSavePrefs)
def _getData(self, row, column, role): def _getData(self, row, column, role):
@ -85,13 +89,6 @@ class ResultsModel(Table):
self.model.delta_values = value self.model.delta_values = value
#--- Events #--- 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): def appWillSavePrefs(self):
self.model.columns.save_columns() self.model.columns.save_columns()

View File

@ -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

View File

@ -1,164 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT Type="Advanced Installer" CreateVersion="4.7.2" version="8.0.2" Modules="professional" RootPath="." Language="en">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
<ROW Property="ALLUSERS" Value="2"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru_me/"/>
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru_me/"/>
<ROW Property="BannerBitmap" MultiBuildValue="DefaultBuild:banner_image.jpg" Type="1"/>
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" MultiBuildValue="DefaultBuild:dialog_image.jpg" Type="1"/>
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
<ROW Property="ProductCode" Value="1033:{A7037226-389C-4704-81C3-E2CA17D11058} " Type="16"/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="dupeGuru Music Edition" ValueLocId="*"/>
<ROW Property="ProductVersion" Value="5.6.0"/>
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
<ROW Property="UpgradeCode" Value="{E11BFC48-7639-44BD-BB5B-A6AC934BC12D}"/>
<ROW Property="WindowsType9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
<ROW Property="WindowsType9XDisplay" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
<ROW Property="WindowsTypeNT" MultiBuildValue="DefaultBuild:Windows NT/2k/XP/Vista/Windows7 x86" ValueLocId="-"/>
<ROW Property="WindowsTypeNT64" MultiBuildValue="DefaultBuild:Windows XP x64, Windows XP x64 ServicePack 1, Windows XP x64 ServicePack 2, Windows Server 2003 x64, Windows Server 2003 x64 Service Pack 1, Windows Server 2003 x64 Service pack 2" ValueLocId="-"/>
<ROW Property="WindowsTypeNT64Display" MultiBuildValue="DefaultBuild:Windows XP x64, Windows Server 2003 x64" ValueLocId="-"/>
<ROW Property="WindowsTypeNTDisplay" MultiBuildValue="DefaultBuild:Windows NT/2k/XP/Vista/Windows7 x86" ValueLocId="-"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
<ROW Component="AIShRegAnswer" ComponentId="{7DB5C163-2978-436F-A47D-54F1C76F1D64}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer"/>
<ROW Component="CurrentVersion" ComponentId="{1EF88868-DCB1-4F7D-83F6-CC17C9F65740}" Directory_="APPDIR" Attributes="4" KeyPath="CurrentVersion"/>
<ROW Component="SHORTCUTDIR" ComponentId="{B21C7D52-4D02-4470-AD46-9EF2780EEB12}" Directory_="SHORTCUTDIR" Attributes="0"/>
<ROW Component="dupeGuru_ME.exe" ComponentId="{14F7B73A-FA85-4C10-AA92-10BE05FE103B}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru_ME.exe"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="dupeGuru_ME.exe AIShRegAnswer CurrentVersion SHORTCUTDIR"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
<ROW File="dupeGuru_ME.exe" Component_="dupeGuru_ME.exe" FileName="dupeGu~1.exe|dupeGuru ME.exe" Version="65535.65535.65535.65535" Attributes="0" SourcePath="dist\dupeGuru ME.exe" SelfReg="false" Sequence="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageFolder="install" PackageFileName="dupeguru_me_win64_[|ProductVersion]" Languages="en" InstallationType="4" CreateMd5="true" ExtUI="true" MsiPackageType="x64"/>
<ATTRIBUTE name="CurrentBuild" value="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.CacheComponent">
<ATTRIBUTE name="Enable" value="false"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;AI_DICTS&gt;ui.ail"/>
<ROW Path="&lt;AI_DICTS&gt;ui_en.ail"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="CommonUI.aip" Path="&lt;AI_FRAGS&gt;CommonUI.aip"/>
<ROW Fragment="FolderDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\FolderDlg.aip"/>
<ROW Fragment="SequenceDialogs.aip" Path="&lt;AI_THEMES&gt;classic\fragments\SequenceDialogs.aip"/>
<ROW Fragment="Sequences.aip" Path="&lt;AI_FRAGS&gt;Sequences.aip"/>
<ROW Fragment="ShortcutsDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\ShortcutsDlg.aip"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;AI_FRAGS&gt;StaticUIStrings.aip"/>
<ROW Fragment="UI.aip" Path="&lt;AI_THEMES&gt;classic\fragments\UI.aip"/>
<ROW Fragment="Validation.aip" Path="&lt;AI_FRAGS&gt;Validation.aip"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="aicustact.dll" SourcePath="&lt;AI_CUSTACTS&gt;aicustact.dll"/>
<ROW Name="banner_image.jpg" SourcePath="&lt;AI_THEMES&gt;classic\resources\banner-image.jpg"/>
<ROW Name="dialog_image.jpg" SourcePath="&lt;AI_THEMES&gt;classic\resources\dialog-image.jpg"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed) AND (VersionNT&lt;&quot;601&quot;)"/>
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed) AND (VersionNT&lt;&quot;601&quot;)@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="CustomizeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="CustomizeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="ChangeButton" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="301"/>
<ROW Dialog_="ResumeDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_RESUME" Ordering="299"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_MAINT" Ordering="197"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_PATCH" Ordering="198"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="199"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="201"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="SHORTCUTDIR" Component_="SHORTCUTDIR"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru_ME.exe]"/>
<ROW Action="AI_PREPARE_UPGRADE" Type="65" Source="aicustact.dll" Target="PrepareUpgrade"/>
<ROW Action="AI_RESTORE_LOCATION" Type="65" Source="aicustact.dll" Target="RestoreLocation"/>
<ROW Action="AI_ResolveKnownFolders" Type="1" Source="aicustact.dll" Target="AI_ResolveKnownFolders"/>
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[ProgramFiles64Folder][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="SystemFolder_msiexec.exe" SourcePath="&lt;AI_RES&gt;uninstall.ico" Index="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel &lt;&gt; 5)" Sequence="210"/>
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE=&quot;No&quot; AND (Not Installed)" Sequence="1300"/>
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE=&quot;ALL&quot;)" Sequence="1449"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="( Version9X OR ( NOT VersionNT64 ) OR ( VersionNT64 AND ((VersionNT64 &lt;&gt; 502) OR (((VersionNT64 = 502) AND (ServicePackLevel &gt;= 1)) OR (MsiNTProductType &lt;&gt; 1))) AND ((VersionNT64 &lt;&gt; 502) OR (((VersionNT64 = 502) AND (ServicePackLevel &lt;&gt; 1)) OR (MsiNTProductType &lt;&gt; 1))) AND ((VersionNT64 &lt;&gt; 502) OR (((VersionNT64 = 502) AND (ServicePackLevel &lt;&gt; 2)) OR (MsiNTProductType &lt;&gt; 1))) AND ((VersionNT64 &lt;&gt; 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel &gt;= 1)))) AND ((VersionNT64 &lt;&gt; 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel &lt;&gt; 1)))) AND ((VersionNT64 &lt;&gt; 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel &lt;&gt; 2)))) ) )" Description="[ProductName] cannot be installed on the following Windows versions: [WindowsTypeNT64Display]" DescriptionLocId="AI.LaunchCondition.NoSpecificNT64" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="( Version9X OR VersionNT64 )" Description="[ProductName] cannot be installed on [WindowsTypeNTDisplay]" DescriptionLocId="AI.LaunchCondition.NoNT" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]" DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
<ROW Registry="CurrentVersion" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="CurrentVersion" Value="[ProductVersion]" Component_="CurrentVersion"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
<ROW Shortcut="Uninstall_dupeGuru_ME" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru ME" Component_="AIShRegAnswer" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
<ROW Shortcut="dupeGuru_ME" Directory_="SHORTCUTDIR" Name="dupeGu~1|dupeGuru ME" Component_="dupeGuru_ME.exe" Target="[#dupeGuru_ME.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="dupeGuru_ME_1" Directory_="DesktopFolder" Name="dupeGu~1|dupeGuru ME" Component_="dupeGuru_ME.exe" Target="[#dupeGuru_ME.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiThemeComponent">
<ATTRIBUTE name="UsedTheme" value="classic"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
<ROW Directory_="APPDIR" SourcePath="dist" Feature="MainFeature" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store|*.pdb|*.vshost.*" ExcludeFlags="6"/>
</COMPONENT>
</DOCUMENT>

View File

@ -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)

View File

@ -10,10 +10,11 @@ from PyQt5.QtWidgets import (
) )
from hscommon.trans import trget from hscommon.trans import trget
from core.app import AppMode
from core.scanner import ScanType from core.scanner import ScanType
from ..base.preferences_dialog import PreferencesDialogBase from ..base.preferences_dialog import PreferencesDialogBase
from . import preferences from ..se import preferences
tr = trget('ui') tr = trget('ui')
@ -74,7 +75,7 @@ class PreferencesDialog(PreferencesDialogBase):
setchecked(self.wordWeightingBox, prefs.word_weighting) setchecked(self.wordWeightingBox, prefs.word_weighting)
# Update UI state based on selected scan type # 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 ( word_based = scan_type in (
ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder, ScanType.Filename, ScanType.Fields, ScanType.FieldsNoOrder,
ScanType.Tag ScanType.Tag

View File

@ -7,12 +7,16 @@
from core_se import __appname__ from core_se import __appname__
from core_se.app import DupeGuru as DupeGuruModel from core_se.app import DupeGuru as DupeGuruModel
from core.directories import Directories as DirectoriesBase, DirectoryState from core.directories import Directories as DirectoriesBase, DirectoryState
from core.app import AppMode
from ..base.app import DupeGuru as DupeGuruBase from ..base.app import DupeGuru as DupeGuruBase
from .details_dialog import DetailsDialog from .details_dialog import DetailsDialog as DetailsDialogStandard
from .results_model import ResultsModel 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 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): class Directories(DirectoriesBase):
ROOT_PATH_TO_EXCLUDE = frozenset(['windows', 'program files']) ROOT_PATH_TO_EXCLUDE = frozenset(['windows', 'program files'])
@ -30,10 +34,7 @@ class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_se' LOGO_NAME = 'logo_se'
NAME = __appname__ NAME = __appname__
DETAILS_DIALOG_CLASS = DetailsDialog
RESULT_MODEL_CLASS = ResultsModel
PREFERENCES_CLASS = Preferences PREFERENCES_CLASS = Preferences
PREFERENCES_DIALOG_CLASS = PreferencesDialog
def _setup(self): def _setup(self):
self.directories = Directories() self.directories = Directories()
@ -42,9 +43,43 @@ class DupeGuru(DupeGuruBase):
def _update_options(self): def _update_options(self):
DupeGuruBase._update_options(self) DupeGuruBase._update_options(self)
self.model.options['min_match_percentage'] = self.prefs.filter_hardness 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['word_weighting'] = self.prefs.word_weighting
self.model.options['match_similar_words'] = self.prefs.match_similar self.model.options['match_similar_words'] = self.prefs.match_similar
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0 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['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

View File

@ -4,19 +4,21 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html # http://www.gnu.org/licenses/gpl-3.0.html
from core.scanner import ScanType
from ..base.preferences import Preferences as PreferencesBase from ..base.preferences import Preferences as PreferencesBase
class Preferences(PreferencesBase): class Preferences(PreferencesBase):
DEFAULT_SCAN_TYPE = ScanType.Contents
def _load_specific(self, settings): def _load_specific(self, settings):
get = self.get_value get = self.get_value
self.word_weighting = get('WordWeighting', self.word_weighting) self.word_weighting = get('WordWeighting', self.word_weighting)
self.match_similar = get('MatchSimilar', self.match_similar) self.match_similar = get('MatchSimilar', self.match_similar)
self.ignore_small_files = get('IgnoreSmallFiles', self.ignore_small_files) self.ignore_small_files = get('IgnoreSmallFiles', self.ignore_small_files)
self.small_file_threshold = get('SmallFileThreshold', self.small_file_threshold) 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): def _reset_specific(self):
self.filter_hardness = 80 self.filter_hardness = 80
@ -24,6 +26,12 @@ class Preferences(PreferencesBase):
self.match_similar = False self.match_similar = False
self.ignore_small_files = True 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
self.scan_tag_title = True
self.scan_tag_genre = False
self.scan_tag_year = False
def _save_specific(self, settings): def _save_specific(self, settings):
set_ = self.set_value set_ = self.set_value
@ -31,4 +39,10 @@ class Preferences(PreferencesBase):
set_('MatchSimilar', self.match_similar) set_('MatchSimilar', self.match_similar)
set_('IgnoreSmallFiles', self.ignore_small_files) set_('IgnoreSmallFiles', self.ignore_small_files)
set_('SmallFileThreshold', self.small_file_threshold) 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)

View File

@ -13,6 +13,7 @@ from hscommon.plat import ISWINDOWS, ISLINUX
from hscommon.trans import trget from hscommon.trans import trget
from hscommon.util import tryint from hscommon.util import tryint
from core.app import AppMode
from core.scanner import ScanType from core.scanner import ScanType
from ..base.preferences_dialog import PreferencesDialogBase from ..base.preferences_dialog import PreferencesDialogBase
@ -81,7 +82,7 @@ class PreferencesDialog(PreferencesDialogBase):
self.sizeThresholdEdit.setText(str(prefs.small_file_threshold)) self.sizeThresholdEdit.setText(str(prefs.small_file_threshold))
# Update UI state based on selected scan type # 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 word_based = scan_type == ScanType.Filename
self.filterHardnessSlider.setEnabled(word_based) self.filterHardnessSlider.setEnabled(word_based)
self.matchSimilarBox.setEnabled(word_based) self.matchSimilarBox.setEnabled(word_based)