1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-22 06:37:17 +00:00

Decoupled app in core.app from apps in qt.app and core.app_cocoa. Instead of subclassing it, they now hold a reference to it while fulfilling the role of core.app's "view".

This commit is contained in:
Virgil Dupras
2011-09-20 15:06:29 -04:00
parent 841b249b67
commit f730f4f55a
15 changed files with 162 additions and 170 deletions

View File

@@ -19,7 +19,7 @@ from jobprogress import job
from jobprogress.qt import Progress
from hscommon.trans import tr, trmsg
from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
from core.app import DupeGuru as DupeGuruModel, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
from qtlib.about_box import AboutBox
from qtlib.recent import Recent
@@ -45,11 +45,12 @@ class SysWrapper(io.IOBase):
if s.strip(): # don't log empty stuff
logging.warning(s)
class DupeGuru(DupeGuruBase, QObject):
class DupeGuru(QObject):
LOGO_NAME = '<replace this>'
NAME = '<replace this>'
def __init__(self, data_module):
QObject.__init__(self)
appdata = str(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
if not op.exists(appdata):
os.makedirs(appdata)
@@ -62,8 +63,7 @@ class DupeGuru(DupeGuruBase, QObject):
sys.stdout = SysWrapper()
self.prefs = self._create_preferences()
self.prefs.load()
DupeGuruBase.__init__(self, data_module, appdata)
QObject.__init__(self)
self.model = DupeGuruModel(view=self, data_module=data_module, appdata=appdata)
self._setup()
#--- Private
@@ -71,7 +71,7 @@ class DupeGuru(DupeGuruBase, QObject):
self._setupActions()
self._update_options()
self.recentResults = Recent(self, 'recentResults')
self.recentResults.mustOpenItem.connect(self.load_from)
self.recentResults.mustOpenItem.connect(self.model.load_from)
self.resultWindow = self._create_result_window()
self._progress = Progress(self.resultWindow)
self.directories_dialog = DirectoriesDialog(self.resultWindow, self)
@@ -81,16 +81,16 @@ class DupeGuru(DupeGuruBase, QObject):
self.about_box = AboutBox(self.resultWindow, self)
self.reg = Registration(self)
self.set_registration(self.prefs.registration_code, self.prefs.registration_email)
if self.should_show_fairware_reminder:
self.reg = Registration(self.model)
self.model.set_registration(self.prefs.registration_code, self.prefs.registration_email)
if self.model.should_show_fairware_reminder:
# The timer scheme is because if the nag is not shown before the application is
# completely initialized, the nag will be shown before the app shows up in the task bar
# In some circumstances, the nag is hidden by other window, which may make the user think
# that the application haven't launched.
QTimer.singleShot(0, self.reg.show_nag)
self.directories_dialog.show()
self.load()
self.model.load()
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished)
@@ -120,10 +120,10 @@ class DupeGuru(DupeGuruBase, QObject):
self.about_box.registeredEmailLabel.setText(self.prefs.registration_email)
def _update_options(self):
self.scanner.mix_file_kind = self.prefs.mix_file_kind
self.options['escape_filter_regexp'] = self.prefs.use_regexp
self.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
self.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches
self.model.scanner.mix_file_kind = self.prefs.mix_file_kind
self.model.options['escape_filter_regexp'] = self.prefs.use_regexp
self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches
#--- Virtual
def _create_details_dialog(self, parent):
@@ -138,44 +138,15 @@ class DupeGuru(DupeGuruBase, QObject):
def _create_preferences_dialog(self, parent):
raise NotImplementedError()
#--- Override
@staticmethod
def _open_path(path):
url = QUrl.fromLocalFile(str(path))
QDesktopServices.openUrl(url)
@staticmethod
def _reveal_path(path):
DupeGuru._open_path(path[:-1])
def _start_job(self, jobid, func, *args):
title = JOBID2TITLE[jobid]
try:
j = self._progress.create_job()
args = tuple([j] + list(args))
self._progress.run(jobid, title, func, args=args)
except job.JobInProgressError:
msg = trmsg("TaskHangingMsg")
QMessageBox.information(self.resultWindow, 'Action in progress', msg)
def _get_default(self, key):
return self.prefs.get_value(key)
def _set_default(self, key, value):
self.prefs.set_value(key, value)
def _show_extra_fairware_reminder(self):
dialog = ExtraFairwareReminder(self.directories_dialog, self)
dialog.exec_()
#--- Public
def add_selected_to_ignore_list(self):
dupes = self.without_ref(self.selected_dupes)
dupes = self.model.without_ref(self.model.selected_dupes)
if not dupes:
return
title = tr("Add to Ignore List")
msg = trmsg("IgnoreConfirmMsg").format(len(dupes))
if self.confirm(title, msg):
DupeGuruBase.add_selected_to_ignore_list(self)
self.model.add_selected_to_ignore_list(self)
def copy_or_move_marked(self, copy):
opname = tr("copy") if copy else tr("move")
@@ -185,18 +156,17 @@ class DupeGuru(DupeGuruBase, QObject):
if not destination:
return
recreate_path = self.prefs.destination_type
DupeGuruBase.copy_or_move_marked(self, copy, destination, recreate_path)
self.model.copy_or_move_marked(self, copy, destination, recreate_path)
def remove_selected(self):
dupes = self.without_ref(self.selected_dupes)
dupes = self.model.without_ref(self.model.selected_dupes)
if not dupes:
return
title = tr("Remove duplicates")
msg = trmsg("FileRemovalConfirmMsg").format(len(dupes))
if self.confirm(title, msg):
DupeGuruBase.remove_selected(self)
self.model.remove_selected(self)
#--- Public
def askForRegCode(self):
self.reg.ask_for_code()
@@ -209,7 +179,7 @@ class DupeGuru(DupeGuruBase, QObject):
def invokeCustomCommand(self):
cmd = self.prefs.custom_command
if cmd:
self.invoke_command(cmd)
self.model.invoke_command(cmd)
else:
msg = trmsg("NoCustomCommandMsg")
QMessageBox.warning(self.resultWindow, tr("Custom Command"), msg)
@@ -227,21 +197,21 @@ class DupeGuru(DupeGuruBase, QObject):
def application_will_terminate(self):
self.willSavePrefs.emit()
self.prefs.save()
self.save()
self.model.save()
def checkForUpdateTriggered(self):
QProcess.execute('updater.exe', ['/checknow'])
def job_finished(self, jobid):
self._job_completed(jobid)
self.model._job_completed(jobid)
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE):
if self.results.problems:
if self.model.results.problems:
self.problemDialog.show()
else:
msg = trmsg("OperationSuccessMsg")
QMessageBox.information(self.resultWindow, tr("Operation Complete"), msg)
elif jobid == JOB_SCAN:
if not self.results.groups:
if not self.model.results.groups:
title = tr("Scan complete")
msg = trmsg("NoDuplicateFoundMsg")
QMessageBox.information(self.resultWindow, title, msg)
@@ -251,12 +221,12 @@ class DupeGuru(DupeGuruBase, QObject):
self.showResultsWindow()
def openDebugLogTriggered(self):
debugLogPath = op.join(self.appdata, 'debug.log')
self._open_path(debugLogPath)
debugLogPath = op.join(self.model.appdata, 'debug.log')
self.open_path(debugLogPath)
def preferencesTriggered(self):
self.preferences_dialog.load()
result = self.preferences_dialog.exec_()
result = self.preferences_dialog.exec()
if result == QDialog.Accepted:
self.preferences_dialog.save()
self.prefs.save()
@@ -276,3 +246,33 @@ class DupeGuru(DupeGuruBase, QObject):
url = QUrl.fromLocalFile(op.abspath(op.join(base_path, 'index.html')))
QDesktopServices.openUrl(url)
#--- model --> view
@staticmethod
def open_path(path):
url = QUrl.fromLocalFile(str(path))
QDesktopServices.openUrl(url)
@staticmethod
def reveal_path(path):
DupeGuru.open_path(path[:-1])
def start_job(self, jobid, func, *args):
title = JOBID2TITLE[jobid]
try:
j = self._progress.create_job()
args = tuple([j] + list(args))
self._progress.run(jobid, title, func, args=args)
except job.JobInProgressError:
msg = trmsg("TaskHangingMsg")
QMessageBox.information(self.resultWindow, 'Action in progress', msg)
def get_default(self, key):
return self.prefs.get_value(key)
def set_default(self, key, value):
self.prefs.set_value(key, value)
def show_extra_fairware_reminder(self):
dialog = ExtraFairwareReminder(self.directories_dialog, self)
dialog.exec()

View File

@@ -18,7 +18,7 @@ class DetailsDialog(QDialog):
def __init__(self, parent, app):
QDialog.__init__(self, parent, Qt.Tool)
self.app = app
self.model = DetailsPanel(self, app)
self.model = DetailsPanel(self, app.model)
self._setupUi()
if self.app.prefs.detailsWindowRect is not None:
self.setGeometry(self.app.prefs.detailsWindowRect)

View File

@@ -45,7 +45,7 @@ class DirectoriesDialog(QMainWindow):
self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged)
self.app.recentResults.itemsChanged.connect(self._updateLoadResultsButton)
self.recentFolders.itemsChanged.connect(self._updateAddButton)
self.recentFolders.mustOpenItem.connect(self.app.add_directory)
self.recentFolders.mustOpenItem.connect(self.app.model.add_directory)
self.directoriesModel.foldersAdded.connect(self.directoriesModelAddedFolders)
self.app.willSavePrefs.connect(self.appWillSavePrefs)
@@ -170,7 +170,7 @@ class DirectoriesDialog(QMainWindow):
#--- QWidget overrides
def closeEvent(self, event):
event.accept()
if self.app.results.is_modified:
if self.app.model.results.is_modified:
title = tr("Unsaved results")
msg = trmsg("ReallyWantToQuitMsg")
if not self.app.confirm(title, msg):
@@ -186,7 +186,7 @@ class DirectoriesDialog(QMainWindow):
if not dirpath:
return
self.lastAddedFolder = dirpath
self.app.add_directory(dirpath)
self.app.model.add_directory(dirpath)
self.recentFolders.insertItem(dirpath)
def appWillSavePrefs(self):
@@ -201,7 +201,7 @@ class DirectoriesDialog(QMainWindow):
files = ';;'.join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
destination = QFileDialog.getOpenFileName(self, title, '', files)
if destination:
self.app.load_from(destination)
self.app.model.load_from(destination)
self.app.recentResults.insertItem(destination)
def removeFolderButtonClicked(self):
@@ -212,16 +212,16 @@ class DirectoriesDialog(QMainWindow):
node = index.internalPointer()
if node.parent is None:
row = index.row()
self.app.remove_directory(row)
self.app.model.remove_directory(row)
def scanButtonClicked(self):
title = tr("Start a new scan")
if self.app.results.is_modified:
if self.app.model.results.is_modified:
msg = trmsg("ReallyWantToContinueMsg")
if not self.app.confirm(title, msg):
return
try:
self.app.start_scanning()
self.app.model.start_scanning()
except NoScannableFileError:
msg = trmsg("NoScannableFileMsg")
QMessageBox.warning(self, title, msg)

View File

@@ -60,7 +60,7 @@ class DirectoriesDelegate(QStyledItemDelegate):
class DirectoriesModel(TreeModel):
def __init__(self, app):
TreeModel.__init__(self)
self.model = DirectoryTree(self, app)
self.model = DirectoryTree(self, app.model)
self.model.connect()
def _createNode(self, ref, row):

View File

@@ -55,7 +55,7 @@ class PrioritizeDialog(QDialog):
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.model = PrioritizeDialogModel(view=self, app=app)
self.model = PrioritizeDialogModel(view=self, app=app.model)
self.categoryList = ComboboxModel(model=self.model.category_list, view=self.categoryCombobox)
self.criteriaList = ListviewModel(model=self.model.criteria_list, view=self.criteriaListView)
self.prioritizationList = PrioritizationList(model=self.model.prioritization_list, view=self.prioritizationListView)

View File

@@ -20,7 +20,7 @@ class ProblemDialog(QDialog):
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.model = ProblemDialogModel(view=self, app=app)
self.model = ProblemDialogModel(view=self, app=app.model)
self.table = ProblemTable(problem_dialog=self, view=self.tableView)
self.model.connect()
self.table.model.connect()

View File

@@ -140,7 +140,7 @@ class ResultWindow(QMainWindow):
# Columns menu
menu = self.menuColumns
self._column_actions = []
for index, column in enumerate(self.app.data.COLUMNS):
for index, column in enumerate(self.app.model.data.COLUMNS):
action = menu.addAction(column.display)
action.setCheckable(True)
action.column_index = index
@@ -224,7 +224,7 @@ class ResultWindow(QMainWindow):
self.actionsButton.showMenu()
def addToIgnoreListTriggered(self):
self.app.add_selected_to_ignore_list()
self.app.model.add_selected_to_ignore_list()
def applyFilterTriggered(self):
title = tr("Apply Filter")
@@ -234,34 +234,34 @@ class ResultWindow(QMainWindow):
if not ok:
return
answer = str(answer)
self.app.apply_filter(answer)
self.app.model.apply_filter(answer)
self._last_filter = answer
def cancelFilterTriggered(self):
self.app.apply_filter('')
self.app.model.apply_filter('')
def clearIgnoreListTriggered(self):
title = tr("Clear Ignore List")
count = len(self.app.scanner.ignore_list)
count = len(self.app.model.scanner.ignore_list)
if not count:
QMessageBox.information(self, title, trmsg("NothingToClearMsg"))
return
msg = trmsg("ClearIgnoreListConfirmMsg").format(count)
if self.app.confirm(title, msg, QMessageBox.No):
self.app.scanner.ignore_list.Clear()
self.app.model.scanner.ignore_list.Clear()
QMessageBox.information(self, title, trmsg("IgnoreListClearedMsg"))
def copyTriggered(self):
self.app.copy_or_move_marked(True)
self.app.model.copy_or_move_marked(True)
def deleteTriggered(self):
count = self.app.results.mark_count
count = self.app.model.results.mark_count
if not count:
return
title = tr("Delete duplicates")
msg = trmsg("SendToTrashConfirmMsg").format(count)
if self.app.confirm(title, msg):
self.app.delete_marked()
self.app.model.delete_marked()
def deltaTriggered(self):
self.resultsModel.delta_values = self.actionDelta.isChecked()
@@ -272,42 +272,42 @@ class ResultWindow(QMainWindow):
def exportTriggered(self):
h = self.resultsView.horizontalHeader()
column_ids = []
for i in range(len(self.app.data.COLUMNS)):
for i in range(len(self.app.model.data.COLUMNS)):
if not h.isSectionHidden(i):
column_ids.append(str(i))
exported_path = self.app.export_to_xhtml(column_ids)
exported_path = self.app.model.export_to_xhtml(column_ids)
url = QUrl.fromLocalFile(exported_path)
QDesktopServices.openUrl(url)
def hardlinkTriggered(self):
count = self.app.results.mark_count
count = self.app.model.results.mark_count
if not count:
return
title = tr("Delete and hardlink duplicates")
msg = trmsg("HardlinkConfirmMsg").format(count)
if self.app.confirm(title, msg):
self.app.delete_marked(replace_with_hardlinks=True)
self.app.model.delete_marked(replace_with_hardlinks=True)
def makeReferenceTriggered(self):
self.app.make_selected_reference()
self.app.model.make_selected_reference()
def markAllTriggered(self):
self.app.mark_all()
self.app.model.mark_all()
def markInvertTriggered(self):
self.app.mark_invert()
self.app.model.mark_invert()
def markNoneTriggered(self):
self.app.mark_none()
self.app.model.mark_none()
def markSelectedTriggered(self):
self.app.toggle_selected_mark_state()
self.app.model.toggle_selected_mark_state()
def moveTriggered(self):
self.app.copy_or_move_marked(False)
self.app.model.copy_or_move_marked(False)
def openTriggered(self):
self.app.open_selected()
self.app.model.open_selected()
def powerMarkerTriggered(self):
self.resultsModel.power_marker = self.actionPowerMarker.isChecked()
@@ -316,16 +316,16 @@ class ResultWindow(QMainWindow):
self.app.show_preferences()
def removeMarkedTriggered(self):
count = self.app.results.mark_count
count = self.app.model.results.mark_count
if not count:
return
title = tr("Remove duplicates")
msg = trmsg("FileRemovalConfirmMsg").format(count)
if self.app.confirm(title, msg):
self.app.remove_marked()
self.app.model.remove_marked()
def removeSelectedTriggered(self):
self.app.remove_selected()
self.app.model.remove_selected()
def renameTriggered(self):
self.resultsView.edit(self.resultsView.selectionModel().currentIndex())
@@ -337,7 +337,7 @@ class ResultWindow(QMainWindow):
dlg.model.perform_reprioritization()
def revealTriggered(self):
self.app.reveal_selected()
self.app.model.reveal_selected()
def saveResultsTriggered(self):
title = trmsg("SelectResultToSaveMsg")
@@ -346,7 +346,7 @@ class ResultWindow(QMainWindow):
if destination:
if not destination.endswith('.dupeguru'):
destination = '{}.dupeguru'.format(destination)
self.app.save_as(destination)
self.app.model.save_as(destination)
self.app.recentResults.insertItem(destination)
#--- Events
@@ -355,7 +355,7 @@ class ResultWindow(QMainWindow):
h = self.resultsView.horizontalHeader()
widths = []
visible = []
for i in range(len(self.app.data.COLUMNS)):
for i in range(len(self.app.model.data.COLUMNS)):
widths.append(h.sectionSize(i))
visible.append(not h.isSectionHidden(i))
prefs.columns_width = widths
@@ -377,8 +377,8 @@ class ResultWindow(QMainWindow):
self.actionActions.menu().exec_(event.globalPos())
def resultsDoubleClicked(self):
self.app.open_selected()
self.app.model.open_selected()
def resultsSpacePressed(self):
self.app.toggle_selected_mark_state()
self.app.model.toggle_selected_mark_state()

View File

@@ -15,10 +15,10 @@ from core.gui.result_table import ResultTable as ResultTableModel
class ResultsModel(Table):
def __init__(self, app, view):
model = ResultTableModel(self, app)
model = ResultTableModel(self, app.model)
self._app = app
self._data = app.data
self._delta_columns = app.data.DELTA_COLUMNS
self._data = app.model.data
self._delta_columns = app.model.data.DELTA_COLUMNS
Table.__init__(self, model, view)
self.model.connect()

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-12
# Copyright 2011 Hardcoded Software (http://www.hardcoded.net)
@@ -12,7 +11,7 @@ from core.gui.stats_label import StatsLabel as StatsLabelModel
class StatsLabel(object):
def __init__(self, app, view):
self.view = view
self.model = StatsLabelModel(self, app)
self.model = StatsLabelModel(self, app.model)
self.model.connect()
def refresh(self):

View File

@@ -22,16 +22,16 @@ class DupeGuru(DupeGuruBase):
DupeGuruBase.__init__(self, data)
def _setup(self):
self.scanner = scanner.ScannerME()
self.directories.fileclasses = [fs.MusicFile]
self.model.scanner = scanner.ScannerME()
self.model.directories.fileclasses = [fs.MusicFile]
DupeGuruBase._setup(self)
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.min_match_percentage = self.prefs.filter_hardness
self.scanner.scan_type = self.prefs.scan_type
self.scanner.word_weighting = self.prefs.word_weighting
self.scanner.match_similar_words = self.prefs.match_similar
self.model.scanner.min_match_percentage = self.prefs.filter_hardness
self.model.scanner.scan_type = self.prefs.scan_type
self.model.scanner.word_weighting = self.prefs.word_weighting
self.model.scanner.match_similar_words = self.prefs.match_similar
scanned_tags = set()
if self.prefs.scan_tag_track:
scanned_tags.add('track')
@@ -45,7 +45,7 @@ class DupeGuru(DupeGuruBase):
scanned_tags.add('genre')
if self.prefs.scan_tag_year:
scanned_tags.add('year')
self.scanner.scanned_tags = scanned_tags
self.model.scanner.scanned_tags = scanned_tags
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)

View File

@@ -74,16 +74,16 @@ class DupeGuru(DupeGuruBase):
DupeGuruBase.__init__(self, data_pe)
def _setup(self):
self.scanner = ScannerPE()
self.directories.fileclasses = [File]
self.scanner.cache_path = op.join(self.appdata, 'cached_pictures.db')
self.model.scanner = ScannerPE()
self.model.directories.fileclasses = [File]
self.model.scanner.cache_path = op.join(self.model.appdata, 'cached_pictures.db')
DupeGuruBase._setup(self)
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.scan_type = self.prefs.scan_type
self.scanner.match_scaled = self.prefs.match_scaled
self.scanner.threshold = self.prefs.filter_hardness
self.model.scanner.scan_type = self.prefs.scan_type
self.model.scanner.match_scaled = self.prefs.match_scaled
self.model.scanner.threshold = self.prefs.filter_hardness
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)

View File

@@ -60,10 +60,10 @@ class DetailsDialog(DetailsDialogBase):
self.verticalLayout.addWidget(self.tableView)
def _update(self):
if not self.app.selected_dupes:
if not self.app.model.selected_dupes:
return
dupe = self.app.selected_dupes[0]
group = self.app.results.get_group_of_duplicate(dupe)
dupe = self.app.model.selected_dupes[0]
group = self.app.model.results.get_group_of_duplicate(dupe)
ref = group.ref
self.selectedPixmap = QPixmap(str(dupe.path))

View File

@@ -37,12 +37,12 @@ class DupeGuru(DupeGuruBase):
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.min_match_percentage = self.prefs.filter_hardness
self.scanner.scan_type = self.prefs.scan_type
self.scanner.word_weighting = self.prefs.word_weighting
self.scanner.match_similar_words = self.prefs.match_similar
self.model.scanner.min_match_percentage = self.prefs.filter_hardness
self.model.scanner.scan_type = self.prefs.scan_type
self.model.scanner.word_weighting = self.prefs.word_weighting
self.model.scanner.match_similar_words = self.prefs.match_similar
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
self.scanner.size_threshold = threshold * 1024 # threshold is in KB. the scanner wants bytes
self.model.scanner.size_threshold = threshold * 1024 # threshold is in KB. the scanner wants bytes
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)