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:
parent
8b878b7b13
commit
7d749779f2
18
core/app.py
18
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):
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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']
|
||||
|
||||
|
2
hscommon
2
hscommon
@ -1 +1 @@
|
||||
Subproject commit ea634cefdf78ae9e4c7470e571fce859760f6f38
|
||||
Subproject commit 316af1bca53915f99b9bb874064cba6bee881cc1
|
@ -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()
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
47
qt/me/app.py
47
qt/me/app.py
@ -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
|
||||
|
@ -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="<AI_DICTS>ui.ail"/>
|
||||
<ROW Path="<AI_DICTS>ui_en.ail"/>
|
||||
</COMPONENT>
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
|
||||
<ROW Fragment="CommonUI.aip" Path="<AI_FRAGS>CommonUI.aip"/>
|
||||
<ROW Fragment="FolderDlg.aip" Path="<AI_THEMES>classic\fragments\FolderDlg.aip"/>
|
||||
<ROW Fragment="SequenceDialogs.aip" Path="<AI_THEMES>classic\fragments\SequenceDialogs.aip"/>
|
||||
<ROW Fragment="Sequences.aip" Path="<AI_FRAGS>Sequences.aip"/>
|
||||
<ROW Fragment="ShortcutsDlg.aip" Path="<AI_THEMES>classic\fragments\ShortcutsDlg.aip"/>
|
||||
<ROW Fragment="StaticUIStrings.aip" Path="<AI_FRAGS>StaticUIStrings.aip"/>
|
||||
<ROW Fragment="UI.aip" Path="<AI_THEMES>classic\fragments\UI.aip"/>
|
||||
<ROW Fragment="Validation.aip" Path="<AI_FRAGS>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="<AI_CUSTACTS>aicustact.dll"/>
|
||||
<ROW Name="banner_image.jpg" SourcePath="<AI_THEMES>classic\resources\banner-image.jpg"/>
|
||||
<ROW Name="dialog_image.jpg" SourcePath="<AI_THEMES>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<"601")"/>
|
||||
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
|
||||
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed) AND (VersionNT<"601")@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="<AI_RES>uninstall.ico" Index="0"/>
|
||||
</COMPONENT>
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
|
||||
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel <> 5)" Sequence="210"/>
|
||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" Sequence="740"/>
|
||||
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
|
||||
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE="No" AND (Not Installed)" Sequence="1300"/>
|
||||
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE="ALL")" Sequence="1449"/>
|
||||
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
|
||||
</COMPONENT>
|
||||
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
|
||||
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=""" 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 <> 502) OR (((VersionNT64 = 502) AND (ServicePackLevel >= 1)) OR (MsiNTProductType <> 1))) AND ((VersionNT64 <> 502) OR (((VersionNT64 = 502) AND (ServicePackLevel <> 1)) OR (MsiNTProductType <> 1))) AND ((VersionNT64 <> 502) OR (((VersionNT64 = 502) AND (ServicePackLevel <> 2)) OR (MsiNTProductType <> 1))) AND ((VersionNT64 <> 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel >= 1)))) AND ((VersionNT64 <> 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel <> 1)))) AND ((VersionNT64 <> 502) OR ((VersionNT64 = 502) AND ((MsiNTProductType = 1) OR (ServicePackLevel <> 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>
|
@ -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)
|
||||
|
@ -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
|
||||
|
49
qt/se/app.py
49
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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user