1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-22 14:41:39 +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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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