mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-22 14:41:39 +00:00
Changed the build system (that commit is *huge*)
--HG-- rename : base/cocoa/AppDelegate.h => cocoa/base/AppDelegate.h rename : base/cocoa/AppDelegate.m => cocoa/base/AppDelegate.m rename : base/cocoa/Consts.h => cocoa/base/Consts.h rename : base/cocoa/DetailsPanel.h => cocoa/base/DetailsPanel.h rename : base/cocoa/DetailsPanel.m => cocoa/base/DetailsPanel.m rename : base/cocoa/DirectoryPanel.h => cocoa/base/DirectoryPanel.h rename : base/cocoa/DirectoryPanel.m => cocoa/base/DirectoryPanel.m rename : base/cocoa/PyDupeGuru.h => cocoa/base/PyDupeGuru.h rename : base/cocoa/ResultWindow.h => cocoa/base/ResultWindow.h rename : base/cocoa/ResultWindow.m => cocoa/base/ResultWindow.m rename : base/cocoa/dsa_pub.pem => cocoa/base/dsa_pub.pem rename : base/cocoa/xib/DetailsPanel.xib => cocoa/base/xib/DetailsPanel.xib rename : base/cocoa/xib/DirectoryPanel.xib => cocoa/base/xib/DirectoryPanel.xib rename : base/cocoa/xib/MainMenu.xib => cocoa/base/xib/MainMenu.xib rename : me/cocoa/AppDelegate.h => cocoa/me/AppDelegate.h rename : me/cocoa/AppDelegate.m => cocoa/me/AppDelegate.m rename : me/cocoa/Consts.h => cocoa/me/Consts.h rename : me/cocoa/DetailsPanel.h => cocoa/me/DetailsPanel.h rename : me/cocoa/DetailsPanel.m => cocoa/me/DetailsPanel.m rename : me/cocoa/DirectoryPanel.h => cocoa/me/DirectoryPanel.h rename : me/cocoa/DirectoryPanel.m => cocoa/me/DirectoryPanel.m rename : me/cocoa/Info.plist => cocoa/me/Info.plist rename : me/cocoa/PyDupeGuru.h => cocoa/me/PyDupeGuru.h rename : me/cocoa/ResultWindow.h => cocoa/me/ResultWindow.h rename : me/cocoa/ResultWindow.m => cocoa/me/ResultWindow.m rename : me/cocoa/dupeguru.icns => cocoa/me/dupeguru.icns rename : me/cocoa/dupeguru.xcodeproj/project.pbxproj => cocoa/me/dupeguru.xcodeproj/project.pbxproj rename : me/cocoa/gen.py => cocoa/me/gen.py rename : me/cocoa/main.m => cocoa/me/main.m rename : me/cocoa/py/dg_cocoa.py => cocoa/me/py/dg_cocoa.py rename : me/cocoa/py/setup.py => cocoa/me/py/setup.py rename : me/cocoa/xib/Preferences.xib => cocoa/me/xib/Preferences.xib rename : pe/cocoa/AppDelegate.h => cocoa/pe/AppDelegate.h rename : pe/cocoa/AppDelegate.m => cocoa/pe/AppDelegate.m rename : pe/cocoa/Consts.h => cocoa/pe/Consts.h rename : pe/cocoa/DetailsPanel.h => cocoa/pe/DetailsPanel.h rename : pe/cocoa/DetailsPanel.m => cocoa/pe/DetailsPanel.m rename : pe/cocoa/DirectoryPanel.h => cocoa/pe/DirectoryPanel.h rename : pe/cocoa/DirectoryPanel.m => cocoa/pe/DirectoryPanel.m rename : pe/cocoa/Info.plist => cocoa/pe/Info.plist rename : pe/cocoa/PictureBlocks.h => cocoa/pe/PictureBlocks.h rename : pe/cocoa/PictureBlocks.m => cocoa/pe/PictureBlocks.m rename : pe/cocoa/PyDupeGuru.h => cocoa/pe/PyDupeGuru.h rename : pe/cocoa/ResultWindow.h => cocoa/pe/ResultWindow.h rename : pe/cocoa/ResultWindow.m => cocoa/pe/ResultWindow.m rename : pe/cocoa/dupeguru.icns => cocoa/pe/dupeguru.icns rename : pe/cocoa/dupeguru.xcodeproj/project.pbxproj => cocoa/pe/dupeguru.xcodeproj/project.pbxproj rename : pe/cocoa/gen.py => cocoa/pe/gen.py rename : pe/cocoa/main.m => cocoa/pe/main.m rename : pe/cocoa/py/dg_cocoa.py => cocoa/pe/py/dg_cocoa.py rename : pe/cocoa/py/setup.py => cocoa/pe/py/setup.py rename : pe/cocoa/xib/DetailsPanel.xib => cocoa/pe/xib/DetailsPanel.xib rename : pe/cocoa/xib/Preferences.xib => cocoa/pe/xib/Preferences.xib rename : se/cocoa/AppDelegate.h => cocoa/se/AppDelegate.h rename : se/cocoa/AppDelegate.m => cocoa/se/AppDelegate.m rename : se/cocoa/Consts.h => cocoa/se/Consts.h rename : se/cocoa/DetailsPanel.h => cocoa/se/DetailsPanel.h rename : se/cocoa/DetailsPanel.m => cocoa/se/DetailsPanel.m rename : se/cocoa/DirectoryPanel.h => cocoa/se/DirectoryPanel.h rename : se/cocoa/DirectoryPanel.m => cocoa/se/DirectoryPanel.m rename : se/cocoa/Info.plist => cocoa/se/Info.plist rename : se/cocoa/PyDupeGuru.h => cocoa/se/PyDupeGuru.h rename : se/cocoa/ResultWindow.h => cocoa/se/ResultWindow.h rename : se/cocoa/ResultWindow.m => cocoa/se/ResultWindow.m rename : se/cocoa/dupeguru.icns => cocoa/se/dupeguru.icns rename : se/cocoa/dupeguru.xcodeproj/project.pbxproj => cocoa/se/dupeguru.xcodeproj/project.pbxproj rename : se/cocoa/gen.py => cocoa/se/gen.py rename : se/cocoa/main.m => cocoa/se/main.m rename : se/cocoa/py/dg_cocoa.py => cocoa/se/py/dg_cocoa.py rename : se/cocoa/py/setup.py => cocoa/se/py/setup.py rename : se/cocoa/xib/Preferences.xib => cocoa/se/xib/Preferences.xib rename : base/core/LICENSE => core/LICENSE rename : base/core/__init__.py => core/__init__.py rename : base/core/app.py => core/app.py rename : base/core/app_cocoa.py => core/app_cocoa.py rename : base/core/data.py => core/data.py rename : base/core/directories.py => core/directories.py rename : base/core/engine.py => core/engine.py rename : base/core/export.py => core/export.py rename : base/core/fs.py => core/fs.py rename : base/core/ignore.py => core/ignore.py rename : base/core/results.py => core/results.py rename : base/core/scanner.py => core/scanner.py rename : base/core/tests/__init__.py => core/tests/__init__.py rename : base/core/tests/app_cocoa_test.py => core/tests/app_cocoa_test.py rename : base/core/tests/app_test.py => core/tests/app_test.py rename : base/core/tests/data.py => core/tests/data.py rename : base/core/tests/directories_test.py => core/tests/directories_test.py rename : base/core/tests/engine_test.py => core/tests/engine_test.py rename : base/core/tests/ignore_test.py => core/tests/ignore_test.py rename : base/core/tests/results_test.py => core/tests/results_test.py rename : base/core/tests/scanner_test.py => core/tests/scanner_test.py rename : me/core/__init__.py => core_me/__init__.py rename : me/core/app_cocoa.py => core_me/app_cocoa.py rename : me/core/data.py => core_me/data.py rename : me/core/fs.py => core_me/fs.py rename : me/core/scanner.py => core_me/scanner.py rename : me/core/tests/__init__.py => core_me/tests/__init__.py rename : me/core/tests/scanner_test.py => core_me/tests/scanner_test.py rename : pe/core/LICENSE => core_pe/LICENSE rename : pe/core/__init__.py => core_pe/__init__.py rename : pe/core/app_cocoa.py => core_pe/app_cocoa.py rename : pe/core/block.py => core_pe/block.py rename : pe/core/cache.py => core_pe/cache.py rename : pe/core/data.py => core_pe/data.py rename : pe/core/gen.py => core_pe/gen.py rename : pe/core/matchbase.py => core_pe/matchbase.py rename : pe/core/modules/block/block.pyx => core_pe/modules/block/block.pyx rename : pe/core/modules/block/setup.py => core_pe/modules/block/setup.py rename : pe/core/modules/cache/cache.pyx => core_pe/modules/cache/cache.pyx rename : pe/core/modules/cache/setup.py => core_pe/modules/cache/setup.py rename : pe/core/scanner.py => core_pe/scanner.py rename : pe/core/tests/__init__.py => core_pe/tests/__init__.py rename : pe/core/tests/block_test.py => core_pe/tests/block_test.py rename : pe/core/tests/cache_test.py => core_pe/tests/cache_test.py rename : se/core/LICENSE => core_se/LICENSE rename : se/core/__init__.py => core_se/__init__.py rename : se/core/app_cocoa.py => core_se/app_cocoa.py rename : se/core/data.py => core_se/data.py rename : se/core/fs.py => core_se/fs.py rename : se/core/tests/__init__.py => core_se/tests/__init__.py rename : se/core/tests/fs_test.py => core_se/tests/fs_test.py rename : me/help/LICENSE => help_me/LICENSE rename : me/help/__init__.py => help_me/__init__.py rename : me/help/changelog.yaml => help_me/changelog.yaml rename : me/help/gen.py => help_me/gen.py rename : me/help/skeleton/hardcoded.css => help_me/skeleton/hardcoded.css rename : me/help/skeleton/images/hs_title.png => help_me/skeleton/images/hs_title.png rename : me/help/templates/base_dg.mako => help_me/templates/base_dg.mako rename : me/help/templates/credits.mako => help_me/templates/credits.mako rename : me/help/templates/directories.mako => help_me/templates/directories.mako rename : me/help/templates/faq.mako => help_me/templates/faq.mako rename : me/help/templates/intro.mako => help_me/templates/intro.mako rename : me/help/templates/power_marker.mako => help_me/templates/power_marker.mako rename : me/help/templates/preferences.mako => help_me/templates/preferences.mako rename : me/help/templates/quick_start.mako => help_me/templates/quick_start.mako rename : me/help/templates/results.mako => help_me/templates/results.mako rename : me/help/templates/versions.mako => help_me/templates/versions.mako rename : pe/help/LICENSE => help_pe/LICENSE rename : pe/help/__init__.py => help_pe/__init__.py rename : pe/help/changelog.yaml => help_pe/changelog.yaml rename : pe/help/gen.py => help_pe/gen.py rename : pe/help/skeleton/hardcoded.css => help_pe/skeleton/hardcoded.css rename : pe/help/skeleton/images/hs_title.png => help_pe/skeleton/images/hs_title.png rename : pe/help/templates/base_dg.mako => help_pe/templates/base_dg.mako rename : pe/help/templates/credits.mako => help_pe/templates/credits.mako rename : pe/help/templates/directories.mako => help_pe/templates/directories.mako rename : pe/help/templates/faq.mako => help_pe/templates/faq.mako rename : pe/help/templates/intro.mako => help_pe/templates/intro.mako rename : pe/help/templates/power_marker.mako => help_pe/templates/power_marker.mako rename : pe/help/templates/preferences.mako => help_pe/templates/preferences.mako rename : pe/help/templates/quick_start.mako => help_pe/templates/quick_start.mako rename : pe/help/templates/results.mako => help_pe/templates/results.mako rename : pe/help/templates/versions.mako => help_pe/templates/versions.mako rename : se/help/LICENSE => help_se/LICENSE rename : se/help/changelog.yaml => help_se/changelog.yaml rename : se/help/gen.py => help_se/gen.py rename : se/help/skeleton/hardcoded.css => help_se/skeleton/hardcoded.css rename : se/help/skeleton/images/hs_title.png => help_se/skeleton/images/hs_title.png rename : se/help/templates/base_dg.mako => help_se/templates/base_dg.mako rename : se/help/templates/credits.mako => help_se/templates/credits.mako rename : se/help/templates/directories.mako => help_se/templates/directories.mako rename : se/help/templates/faq.mako => help_se/templates/faq.mako rename : se/help/templates/intro.mako => help_se/templates/intro.mako rename : se/help/templates/power_marker.mako => help_se/templates/power_marker.mako rename : se/help/templates/preferences.mako => help_se/templates/preferences.mako rename : se/help/templates/quick_start.mako => help_se/templates/quick_start.mako rename : se/help/templates/results.mako => help_se/templates/results.mako rename : se/help/templates/versions.mako => help_se/templates/versions.mako rename : base/qt/WARNING => qt/WARNING rename : base/qt/__init__.py => qt/base/__init__.py rename : base/qt/app.py => qt/base/app.py rename : base/qt/details_table.py => qt/base/details_table.py rename : base/qt/dg.qrc => qt/base/dg.qrc rename : base/qt/directories_dialog.py => qt/base/directories_dialog.py rename : base/qt/directories_dialog.ui => qt/base/directories_dialog.ui rename : base/qt/directories_model.py => qt/base/directories_model.py rename : base/qt/main_window.py => qt/base/main_window.py rename : base/qt/main_window.ui => qt/base/main_window.ui rename : base/qt/platform.py => qt/base/platform.py rename : base/qt/platform_osx.py => qt/base/platform_osx.py rename : base/qt/platform_win.py => qt/base/platform_win.py rename : base/qt/preferences.py => qt/base/preferences.py rename : base/qt/results_model.py => qt/base/results_model.py rename : me/qt/app.py => qt/me/app.py rename : me/qt/build.py => qt/me/build.py rename : me/qt/details_dialog.py => qt/me/details_dialog.py rename : me/qt/details_dialog.ui => qt/me/details_dialog.ui rename : me/qt/dgme.spec => qt/me/dgme.spec rename : me/qt/gen.py => qt/me/gen.py rename : me/qt/installer.aip => qt/me/installer.aip rename : me/qt/preferences.py => qt/me/preferences.py rename : me/qt/preferences_dialog.py => qt/me/preferences_dialog.py rename : me/qt/preferences_dialog.ui => qt/me/preferences_dialog.ui rename : me/qt/profile.py => qt/me/profile.py rename : me/qt/start.py => qt/me/start.py rename : me/qt/verinfo => qt/me/verinfo rename : pe/qt/app.py => qt/pe/app.py rename : pe/qt/block.py => qt/pe/block.py rename : pe/qt/build.py => qt/pe/build.py rename : pe/qt/details_dialog.py => qt/pe/details_dialog.py rename : pe/qt/details_dialog.ui => qt/pe/details_dialog.ui rename : pe/qt/dgpe.spec => qt/pe/dgpe.spec rename : pe/qt/gen.py => qt/pe/gen.py rename : pe/qt/installer.aip => qt/pe/installer.aip rename : pe/qt/main_window.py => qt/pe/main_window.py rename : pe/qt/modules/block/block.pyx => qt/pe/modules/block/block.pyx rename : pe/qt/modules/block/setup.py => qt/pe/modules/block/setup.py rename : pe/qt/preferences.py => qt/pe/preferences.py rename : pe/qt/preferences_dialog.py => qt/pe/preferences_dialog.py rename : pe/qt/preferences_dialog.ui => qt/pe/preferences_dialog.ui rename : pe/qt/profile.py => qt/pe/profile.py rename : pe/qt/start.py => qt/pe/start.py rename : pe/qt/verinfo => qt/pe/verinfo rename : se/qt/app.py => qt/se/app.py rename : se/qt/build.py => qt/se/build.py rename : se/qt/details_dialog.py => qt/se/details_dialog.py rename : se/qt/details_dialog.ui => qt/se/details_dialog.ui rename : se/qt/dgse.spec => qt/se/dgse.spec rename : se/qt/gen.py => qt/se/gen.py rename : se/qt/installer.aip => qt/se/installer.aip rename : se/qt/preferences.py => qt/se/preferences.py rename : se/qt/preferences_dialog.py => qt/se/preferences_dialog.py rename : se/qt/preferences_dialog.ui => qt/se/preferences_dialog.ui rename : se/qt/profile.py => qt/se/profile.py rename : se/qt/start.py => qt/se/start.py rename : se/qt/verinfo => qt/se/verinfo extra : convert_revision : svn%3Ac306627e-7827-47d3-bdf0-9a457c9553a1/trunk%40285
This commit is contained in:
0
qt/base/__init__.py
Normal file
0
qt/base/__init__.py
Normal file
257
qt/base/app.py
Normal file
257
qt/base/app.py
Normal file
@@ -0,0 +1,257 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-04-25
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import os.path as op
|
||||
|
||||
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
|
||||
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
|
||||
|
||||
from hsutil import job
|
||||
from hsutil.reg import RegistrationRequired
|
||||
|
||||
from core import fs
|
||||
from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
|
||||
|
||||
from qtlib.about_box import AboutBox
|
||||
from qtlib.progress import Progress
|
||||
from qtlib.reg import Registration
|
||||
|
||||
from . import platform
|
||||
|
||||
from .main_window import MainWindow
|
||||
from .directories_dialog import DirectoriesDialog
|
||||
|
||||
JOBID2TITLE = {
|
||||
JOB_SCAN: "Scanning for duplicates",
|
||||
JOB_LOAD: "Loading",
|
||||
JOB_MOVE: "Moving",
|
||||
JOB_COPY: "Copying",
|
||||
JOB_DELETE: "Sending files to the recycle bin",
|
||||
}
|
||||
|
||||
def demo_method(method):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return method(self, *args, **kwargs)
|
||||
except RegistrationRequired:
|
||||
msg = "The demo version of dupeGuru only allows 10 actions (delete/move/copy) per session."
|
||||
QMessageBox.information(self.main_window, 'Demo', msg)
|
||||
|
||||
return wrapper
|
||||
|
||||
class DupeGuru(DupeGuruBase, QObject):
|
||||
LOGO_NAME = '<replace this>'
|
||||
NAME = '<replace this>'
|
||||
DELTA_COLUMNS = frozenset()
|
||||
DEMO_LIMIT_DESC = "In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied."
|
||||
|
||||
def __init__(self, data_module, appid):
|
||||
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
||||
if not op.exists(appdata):
|
||||
os.makedirs(appdata)
|
||||
# For basicConfig() to work, we have to be sure that no logging has taken place before this call.
|
||||
logging.basicConfig(filename=op.join(appdata, 'debug.log'), level=logging.WARNING)
|
||||
DupeGuruBase.__init__(self, data_module, appdata, appid)
|
||||
QObject.__init__(self)
|
||||
self._setup()
|
||||
|
||||
#--- Private
|
||||
def _setup(self):
|
||||
self.selected_dupe = None
|
||||
self.prefs = self._create_preferences()
|
||||
self.prefs.load()
|
||||
self._update_options()
|
||||
self.main_window = self._create_main_window()
|
||||
self._progress = Progress(self.main_window)
|
||||
self.directories_dialog = DirectoriesDialog(self.main_window, self)
|
||||
self.details_dialog = self._create_details_dialog(self.main_window)
|
||||
self.preferences_dialog = self._create_preferences_dialog(self.main_window)
|
||||
self.about_box = AboutBox(self.main_window, self)
|
||||
|
||||
self.reg = Registration(self)
|
||||
self.set_registration(self.prefs.registration_code, self.prefs.registration_email)
|
||||
if not self.registered:
|
||||
# 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.
|
||||
self._nagTimer = QTimer()
|
||||
self.connect(self._nagTimer, SIGNAL('timeout()'), self.mustShowNag)
|
||||
self._nagTimer.start(0)
|
||||
self.main_window.show()
|
||||
self.load()
|
||||
|
||||
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
||||
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished)
|
||||
|
||||
def _setup_as_registered(self):
|
||||
self.prefs.registration_code = self.registration_code
|
||||
self.prefs.registration_email = self.registration_email
|
||||
self.main_window.actionRegister.setVisible(False)
|
||||
self.about_box.registerButton.hide()
|
||||
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
|
||||
|
||||
#--- Virtual
|
||||
def _create_details_dialog(self, parent):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _create_main_window(self):
|
||||
return MainWindow(app=self)
|
||||
|
||||
def _create_preferences(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _create_preferences_dialog(self, parent):
|
||||
raise NotImplementedError()
|
||||
|
||||
#--- Override
|
||||
@staticmethod
|
||||
def _recycle_dupe(dupe):
|
||||
platform.recycle_file(dupe.path)
|
||||
|
||||
def _start_job(self, jobid, func):
|
||||
title = JOBID2TITLE[jobid]
|
||||
try:
|
||||
j = self._progress.create_job()
|
||||
self._progress.run(jobid, title, func, args=(j, ))
|
||||
except job.JobInProgressError:
|
||||
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
|
||||
QMessageBox.information(self.main_window, 'Action in progress', msg)
|
||||
|
||||
#--- Public
|
||||
def add_dupes_to_ignore_list(self, duplicates):
|
||||
for dupe in duplicates:
|
||||
self.add_to_ignore_list(dupe)
|
||||
self.remove_duplicates(duplicates)
|
||||
|
||||
def apply_filter(self, filter):
|
||||
DupeGuruBase.apply_filter(self, filter)
|
||||
self.emit(SIGNAL('resultsChanged()'))
|
||||
|
||||
def askForRegCode(self):
|
||||
self.reg.ask_for_code()
|
||||
|
||||
@demo_method
|
||||
def copy_or_move_marked(self, copy):
|
||||
opname = 'copy' if copy else 'move'
|
||||
title = "Select a directory to {0} marked files to".format(opname)
|
||||
flags = QFileDialog.ShowDirsOnly
|
||||
destination = unicode(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
|
||||
if not destination:
|
||||
return
|
||||
recreate_path = self.prefs.destination_type
|
||||
DupeGuruBase.copy_or_move_marked(self, copy, destination, recreate_path)
|
||||
|
||||
delete_marked = demo_method(DupeGuruBase.delete_marked)
|
||||
|
||||
def make_reference(self, duplicates):
|
||||
DupeGuruBase.make_reference(self, duplicates)
|
||||
self.emit(SIGNAL('resultsChanged()'))
|
||||
|
||||
def mark_all(self):
|
||||
self.results.mark_all()
|
||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
||||
|
||||
def mark_invert(self):
|
||||
self.results.mark_invert()
|
||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
||||
|
||||
def mark_none(self):
|
||||
self.results.mark_none()
|
||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
||||
|
||||
def openDebugLog(self):
|
||||
debugLogPath = op.join(self.appdata, 'debug.log')
|
||||
url = QUrl.fromLocalFile(debugLogPath)
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def open_selected(self):
|
||||
if self.selected_dupe is None:
|
||||
return
|
||||
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path))
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def remove_duplicates(self, duplicates):
|
||||
self.results.remove_duplicates(duplicates)
|
||||
self.emit(SIGNAL('resultsChanged()'))
|
||||
|
||||
def remove_marked_duplicates(self):
|
||||
marked = [d for d in self.results.dupes if self.results.is_marked(d)]
|
||||
self.remove_duplicates(marked)
|
||||
|
||||
def rename_dupe(self, dupe, newname):
|
||||
try:
|
||||
dupe.rename(newname)
|
||||
return True
|
||||
except (IndexError, fs.FSError) as e:
|
||||
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
||||
return False
|
||||
|
||||
def reveal_selected(self):
|
||||
if self.selected_dupe is None:
|
||||
return
|
||||
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path[:-1]))
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def select_duplicate(self, dupe):
|
||||
self.selected_dupe = dupe
|
||||
self.emit(SIGNAL('duplicateSelected()'))
|
||||
|
||||
def show_about_box(self):
|
||||
self.about_box.show()
|
||||
|
||||
def show_details(self):
|
||||
self.details_dialog.show()
|
||||
|
||||
def show_directories(self):
|
||||
self.directories_dialog.show()
|
||||
|
||||
def show_help(self):
|
||||
url = QUrl.fromLocalFile(op.abspath('help/intro.htm'))
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def show_preferences(self):
|
||||
self.preferences_dialog.load()
|
||||
result = self.preferences_dialog.exec_()
|
||||
if result == QDialog.Accepted:
|
||||
self.preferences_dialog.save()
|
||||
self.prefs.save()
|
||||
self._update_options()
|
||||
|
||||
def toggle_marking_for_dupes(self, dupes):
|
||||
for dupe in dupes:
|
||||
self.results.mark_toggle(dupe)
|
||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
||||
|
||||
#--- Events
|
||||
def application_will_terminate(self):
|
||||
self.save()
|
||||
self.save_ignore_list()
|
||||
|
||||
def mustShowNag(self):
|
||||
self._nagTimer.stop() # must be shown only once
|
||||
self.reg.show_nag()
|
||||
|
||||
def job_finished(self, jobid):
|
||||
self.emit(SIGNAL('resultsChanged()'))
|
||||
if jobid == JOB_LOAD:
|
||||
self.emit(SIGNAL('directoriesChanged()'))
|
||||
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE) and self.last_op_error_count > 0:
|
||||
msg = "{0} files could not be processed.".format(self.results.mark_count)
|
||||
QMessageBox.warning(self.main_window, 'Warning', msg)
|
||||
|
||||
83
qt/base/details_table.py
Normal file
83
qt/base/details_table.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-05-17
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from PyQt4.QtCore import Qt, SIGNAL, QAbstractTableModel, QVariant
|
||||
from PyQt4.QtGui import QHeaderView, QTableView
|
||||
|
||||
HEADER = ['Attribute', 'Selected', 'Reference']
|
||||
|
||||
class DetailsModel(QAbstractTableModel):
|
||||
def __init__(self, app):
|
||||
QAbstractTableModel.__init__(self)
|
||||
self._app = app
|
||||
self._dupe_data = None
|
||||
self._ref_data = None
|
||||
self.connect(app, SIGNAL('duplicateSelected()'), self.duplicateSelected)
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(HEADER)
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return QVariant()
|
||||
if role != Qt.DisplayRole:
|
||||
return QVariant()
|
||||
column = index.column()
|
||||
row = index.row()
|
||||
if column == 0:
|
||||
return QVariant(self._app.data.COLUMNS[row]['display'])
|
||||
elif column == 1 and self._dupe_data:
|
||||
return QVariant(self._dupe_data[row])
|
||||
elif column == 2 and self._ref_data:
|
||||
return QVariant(self._ref_data[row])
|
||||
return QVariant()
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADER):
|
||||
return QVariant(HEADER[section])
|
||||
return QVariant()
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self._app.data.COLUMNS)
|
||||
|
||||
#--- Events
|
||||
def duplicateSelected(self):
|
||||
dupe = self._app.selected_dupe
|
||||
if dupe is None:
|
||||
group = None
|
||||
ref = None
|
||||
else:
|
||||
group = self._app.results.get_group_of_duplicate(dupe)
|
||||
ref = group.ref if group.ref is not dupe else None
|
||||
self._dupe_data = self._app._get_display_info(dupe, group)
|
||||
self._ref_data = self._app._get_display_info(ref, group)
|
||||
self.reset()
|
||||
|
||||
|
||||
class DetailsTable(QTableView):
|
||||
def __init__(self, *args):
|
||||
QTableView.__init__(self, *args)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setSelectionBehavior(QTableView.SelectRows)
|
||||
self.setShowGrid(False)
|
||||
|
||||
def setModel(self, model):
|
||||
QTableView.setModel(self, model)
|
||||
# The model needs to be set to set header stuff
|
||||
hheader = self.horizontalHeader()
|
||||
hheader.setHighlightSections(False)
|
||||
hheader.setStretchLastSection(False)
|
||||
hheader.resizeSection(0, 100)
|
||||
hheader.setResizeMode(0, QHeaderView.Fixed)
|
||||
hheader.setResizeMode(1, QHeaderView.Stretch)
|
||||
hheader.setResizeMode(2, QHeaderView.Stretch)
|
||||
vheader = self.verticalHeader()
|
||||
vheader.setVisible(False)
|
||||
vheader.setDefaultSectionSize(18)
|
||||
|
||||
16
qt/base/dg.qrc
Normal file
16
qt/base/dg.qrc
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="details">../../images/details32.png</file>
|
||||
<file alias="logo_pe">../../images/dgpe_logo_32.png</file>
|
||||
<file alias="logo_pe_big">../../images/dgpe_logo_128.png</file>
|
||||
<file alias="logo_me">../../images/dgme_logo_32.png</file>
|
||||
<file alias="logo_me_big">../../images/dgme_logo_128.png</file>
|
||||
<file alias="logo_se">../../images/dgse_logo_32.png</file>
|
||||
<file alias="logo_se_big">../../images/dgse_logo_128.png</file>
|
||||
<file alias="folder">../../images/folderwin32.png</file>
|
||||
<file alias="preferences">../../images/preferences32.png</file>
|
||||
<file alias="actions">../../images/actions32.png</file>
|
||||
<file alias="delta">../../images/delta32.png</file>
|
||||
<file alias="power_marker">../../images/power_marker32.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
85
qt/base/directories_dialog.py
Normal file
85
qt/base/directories_dialog.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-04-25
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from PyQt4.QtCore import SIGNAL, Qt
|
||||
from PyQt4.QtGui import QDialog, QFileDialog, QHeaderView
|
||||
|
||||
from . import platform
|
||||
from .directories_dialog_ui import Ui_DirectoriesDialog
|
||||
from .directories_model import DirectoriesModel, DirectoriesDelegate
|
||||
|
||||
class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
|
||||
def __init__(self, parent, app):
|
||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
||||
QDialog.__init__(self, parent, flags)
|
||||
self.app = app
|
||||
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
|
||||
self._setupUi()
|
||||
self._updateRemoveButton()
|
||||
|
||||
self.connect(self.doneButton, SIGNAL('clicked()'), self.doneButtonClicked)
|
||||
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked)
|
||||
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked)
|
||||
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
|
||||
self.connect(self.app, SIGNAL('directoriesChanged()'), self.directoriesChanged)
|
||||
|
||||
def _setupUi(self):
|
||||
self.setupUi(self)
|
||||
# Stuff that can't be done in the Designer
|
||||
self.directoriesModel = DirectoriesModel(self.app)
|
||||
self.directoriesDelegate = DirectoriesDelegate()
|
||||
self.treeView.setItemDelegate(self.directoriesDelegate)
|
||||
self.treeView.setModel(self.directoriesModel)
|
||||
|
||||
header = self.treeView.header()
|
||||
header.setStretchLastSection(False)
|
||||
header.setResizeMode(0, QHeaderView.Stretch)
|
||||
header.setResizeMode(1, QHeaderView.Fixed)
|
||||
header.resizeSection(1, 100)
|
||||
|
||||
def _updateRemoveButton(self):
|
||||
indexes = self.treeView.selectedIndexes()
|
||||
if not indexes:
|
||||
self.removeButton.setEnabled(False)
|
||||
return
|
||||
self.removeButton.setEnabled(True)
|
||||
index = indexes[0]
|
||||
node = index.internalPointer()
|
||||
# label = 'Remove' if node.parent is None else 'Exclude'
|
||||
|
||||
def addButtonClicked(self):
|
||||
title = u"Select a directory to add to the scanning list"
|
||||
flags = QFileDialog.ShowDirsOnly
|
||||
dirpath = unicode(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
|
||||
if not dirpath:
|
||||
return
|
||||
self.lastAddedFolder = dirpath
|
||||
self.app.add_directory(dirpath)
|
||||
self.directoriesModel.reset()
|
||||
|
||||
def directoriesChanged(self):
|
||||
self.directoriesModel.reset()
|
||||
|
||||
def doneButtonClicked(self):
|
||||
self.hide()
|
||||
|
||||
def removeButtonClicked(self):
|
||||
indexes = self.treeView.selectedIndexes()
|
||||
if not indexes:
|
||||
return
|
||||
index = indexes[0]
|
||||
node = index.internalPointer()
|
||||
if node.parent is None:
|
||||
row = index.row()
|
||||
del self.app.directories[row]
|
||||
self.directoriesModel.reset()
|
||||
|
||||
def selectionChanged(self, selected, deselected):
|
||||
self._updateRemoveButton()
|
||||
|
||||
145
qt/base/directories_dialog.ui
Normal file
145
qt/base/directories_dialog.ui
Normal file
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DirectoriesDialog</class>
|
||||
<widget class="QDialog" name="DirectoriesDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>420</width>
|
||||
<height>338</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Directories</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeView">
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||
</property>
|
||||
<property name="dragDropOverwriteMode">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DropOnly</enum>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>91</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Del</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>91</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="doneButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>91</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Done</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
157
qt/base/directories_model.py
Normal file
157
qt/base/directories_model.py
Normal file
@@ -0,0 +1,157 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-04-25
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import urllib
|
||||
|
||||
from PyQt4.QtCore import QModelIndex, Qt, QRect, QEvent, QPoint, QUrl
|
||||
from PyQt4.QtGui import (QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush, QStyle,
|
||||
QStyleOptionComboBox, QStyleOptionViewItemV4)
|
||||
|
||||
from qtlib.tree_model import TreeNode, TreeModel
|
||||
|
||||
HEADERS = ['Name', 'State']
|
||||
STATES = ['Normal', 'Reference', 'Excluded']
|
||||
|
||||
class DirectoriesDelegate(QStyledItemDelegate):
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = QComboBox(parent);
|
||||
editor.addItems(STATES)
|
||||
return editor
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
self.initStyleOption(option, index)
|
||||
# No idea why, but this cast is required if we want to have access to the V4 valuess
|
||||
option = QStyleOptionViewItemV4(option)
|
||||
if (index.column() == 1) and (option.state & QStyle.State_Selected):
|
||||
cboption = QStyleOptionComboBox()
|
||||
cboption.rect = option.rect
|
||||
# On OS X (with Qt4.6.0), adding State_Enabled to the flags causes the whole drawing to
|
||||
# fail (draw nothing), but it's an OS X only glitch. On Windows, it works alright.
|
||||
cboption.state |= QStyle.State_Enabled
|
||||
QApplication.style().drawComplexControl(QStyle.CC_ComboBox, cboption, painter)
|
||||
painter.setBrush(option.palette.text())
|
||||
rect = QRect(option.rect)
|
||||
rect.setLeft(rect.left()+4)
|
||||
painter.drawText(rect, Qt.AlignLeft, option.text)
|
||||
else:
|
||||
QStyledItemDelegate.paint(self, painter, option, index)
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.model().data(index, Qt.EditRole)
|
||||
editor.setCurrentIndex(value);
|
||||
press = QMouseEvent(QEvent.MouseButtonPress, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
|
||||
release = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
|
||||
QApplication.sendEvent(editor, press)
|
||||
QApplication.sendEvent(editor, release)
|
||||
# editor.showPopup() # this causes a weird glitch. the ugly workaround is above.
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
value = editor.currentIndex()
|
||||
model.setData(index, value, Qt.EditRole)
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
editor.setGeometry(option.rect)
|
||||
|
||||
|
||||
class DirectoryNode(TreeNode):
|
||||
def __init__(self, model, parent, ref, row):
|
||||
TreeNode.__init__(self, model, parent, row)
|
||||
self.ref = ref
|
||||
|
||||
def _createNode(self, ref, row):
|
||||
return DirectoryNode(self.model, self, ref, row)
|
||||
|
||||
def _getChildren(self):
|
||||
return self.model.dirs.get_subfolders(self.ref)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self.parent is not None:
|
||||
return self.ref[-1]
|
||||
else:
|
||||
return unicode(self.ref)
|
||||
|
||||
|
||||
class DirectoriesModel(TreeModel):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.dirs = app.directories
|
||||
TreeModel.__init__(self)
|
||||
|
||||
def _createNode(self, ref, row):
|
||||
return DirectoryNode(self, None, ref, row)
|
||||
|
||||
def _getChildren(self):
|
||||
return self.dirs
|
||||
|
||||
def columnCount(self, parent):
|
||||
return 2
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
node = index.internalPointer()
|
||||
if role == Qt.DisplayRole:
|
||||
if index.column() == 0:
|
||||
return node.name
|
||||
else:
|
||||
return STATES[self.dirs.get_state(node.ref)]
|
||||
elif role == Qt.EditRole and index.column() == 1:
|
||||
return self.dirs.get_state(node.ref)
|
||||
elif role == Qt.ForegroundRole:
|
||||
state = self.dirs.get_state(node.ref)
|
||||
if state == 1:
|
||||
return QBrush(Qt.blue)
|
||||
elif state == 2:
|
||||
return QBrush(Qt.red)
|
||||
return None
|
||||
|
||||
def dropMimeData(self, mimeData, action, row, column, parentIndex):
|
||||
# the data in mimeData is urlencoded **in utf-8**!!! which means that urllib.unquote has
|
||||
# to be called on the utf-8 encoded string, and *only then*, decoded to unicode.
|
||||
if not mimeData.hasFormat('text/uri-list'):
|
||||
return False
|
||||
data = str(mimeData.data('text/uri-list'))
|
||||
unquoted = urllib.unquote(data)
|
||||
urls = unicode(unquoted, 'utf-8').split('\r\n')
|
||||
paths = [unicode(QUrl(url).toLocalFile()) for url in urls if url]
|
||||
for path in paths:
|
||||
self.app.add_directory(path)
|
||||
self.reset()
|
||||
return True
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return Qt.ItemIsEnabled | Qt.ItemIsDropEnabled
|
||||
result = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDropEnabled
|
||||
if index.column() == 1:
|
||||
result |= Qt.ItemIsEditable
|
||||
return result
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == Qt.Horizontal:
|
||||
if role == Qt.DisplayRole and section < len(HEADERS):
|
||||
return HEADERS[section]
|
||||
return None
|
||||
|
||||
def mimeTypes(self):
|
||||
return ['text/uri-list']
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
|
||||
return False
|
||||
node = index.internalPointer()
|
||||
self.dirs.set_state(node.ref, value)
|
||||
return True
|
||||
|
||||
def supportedDropActions(self):
|
||||
# Normally, the correct action should be ActionLink, but the drop doesn't work. It doesn't
|
||||
# work with ActionMove either. So screw that, and accept anything.
|
||||
return Qt.ActionMask
|
||||
|
||||
332
qt/base/main_window.py
Normal file
332
qt/base/main_window.py
Normal file
@@ -0,0 +1,332 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-04-25
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl
|
||||
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
|
||||
QMessageBox, QInputDialog, QLineEdit, QItemSelectionModel, QDesktopServices)
|
||||
|
||||
from hsutil.misc import nonone
|
||||
|
||||
from core.app import NoScannableFileError, AllFilesAreRefError
|
||||
|
||||
import dg_rc
|
||||
from main_window_ui import Ui_MainWindow
|
||||
from results_model import ResultsDelegate, ResultsModel
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self, app):
|
||||
QMainWindow.__init__(self, None)
|
||||
self.app = app
|
||||
self._last_filter = None
|
||||
self._setupUi()
|
||||
self.resultsDelegate = ResultsDelegate()
|
||||
self.resultsModel = ResultsModel(self.app)
|
||||
self.resultsView.setModel(self.resultsModel)
|
||||
self.resultsView.setItemDelegate(self.resultsDelegate)
|
||||
self._load_columns()
|
||||
self._update_column_actions_status()
|
||||
self.resultsView.expandAll()
|
||||
self._update_status_line()
|
||||
|
||||
self.connect(self.app, SIGNAL('resultsChanged()'), self.resultsChanged)
|
||||
self.connect(self.app, SIGNAL('dupeMarkingChanged()'), self.dupeMarkingChanged)
|
||||
self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit)
|
||||
self.connect(self.resultsView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
|
||||
self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
|
||||
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
||||
self.connect(self.resultsModel, SIGNAL('modelReset()'), self.resultsReset)
|
||||
self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked)
|
||||
|
||||
def _setupUi(self):
|
||||
self.setupUi(self)
|
||||
# Stuff that can't be setup in the Designer
|
||||
h = self.resultsView.header()
|
||||
h.setHighlightSections(False)
|
||||
h.setMovable(True)
|
||||
h.setStretchLastSection(False)
|
||||
h.setDefaultAlignment(Qt.AlignLeft)
|
||||
|
||||
self.setWindowTitle(QCoreApplication.instance().applicationName())
|
||||
self.actionScan.setIcon(QIcon(QPixmap(':/%s' % self.app.LOGO_NAME)))
|
||||
|
||||
# Columns menu
|
||||
menu = self.menuColumns
|
||||
self._column_actions = []
|
||||
for index, column in enumerate(self.app.data.COLUMNS):
|
||||
action = menu.addAction(column['display'])
|
||||
action.setCheckable(True)
|
||||
action.column_index = index
|
||||
self._column_actions.append(action)
|
||||
menu.addSeparator()
|
||||
action = menu.addAction("Reset to Defaults")
|
||||
action.column_index = -1
|
||||
|
||||
# Action menu
|
||||
actionMenu = QMenu('Actions', self.toolBar)
|
||||
actionMenu.setIcon(QIcon(QPixmap(":/actions")))
|
||||
actionMenu.addAction(self.actionDeleteMarked)
|
||||
actionMenu.addAction(self.actionMoveMarked)
|
||||
actionMenu.addAction(self.actionCopyMarked)
|
||||
actionMenu.addAction(self.actionRemoveMarked)
|
||||
actionMenu.addSeparator()
|
||||
actionMenu.addAction(self.actionRemoveSelected)
|
||||
actionMenu.addAction(self.actionIgnoreSelected)
|
||||
actionMenu.addAction(self.actionMakeSelectedReference)
|
||||
actionMenu.addSeparator()
|
||||
actionMenu.addAction(self.actionOpenSelected)
|
||||
actionMenu.addAction(self.actionRevealSelected)
|
||||
actionMenu.addAction(self.actionRenameSelected)
|
||||
self.actionActions.setMenu(actionMenu)
|
||||
button = QToolButton(self.toolBar)
|
||||
button.setDefaultAction(actionMenu.menuAction())
|
||||
button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
self.actionsButton = button
|
||||
self.toolBar.insertWidget(self.actionActions, button) # the action is a placeholder
|
||||
self.toolBar.removeAction(self.actionActions)
|
||||
|
||||
self.statusLabel = QLabel(self)
|
||||
self.statusbar.addPermanentWidget(self.statusLabel, 1)
|
||||
|
||||
#--- Private
|
||||
def _confirm(self, title, msg, default_button=QMessageBox.Yes):
|
||||
buttons = QMessageBox.Yes | QMessageBox.No
|
||||
answer = QMessageBox.question(self, title, msg, buttons, default_button)
|
||||
return answer == QMessageBox.Yes
|
||||
|
||||
def _load_columns(self):
|
||||
h = self.resultsView.header()
|
||||
h.setResizeMode(QHeaderView.Interactive)
|
||||
prefs = self.app.prefs
|
||||
attrs = zip(prefs.columns_width, prefs.columns_visible)
|
||||
for index, (width, visible) in enumerate(attrs):
|
||||
h.resizeSection(index, width)
|
||||
h.setSectionHidden(index, not visible)
|
||||
h.setResizeMode(0, QHeaderView.Stretch)
|
||||
|
||||
def _redraw_results(self):
|
||||
# HACK. this is the only way I found to update the widget without reseting everything
|
||||
self.resultsView.scroll(0, 1)
|
||||
self.resultsView.scroll(0, -1)
|
||||
|
||||
def _save_columns(self):
|
||||
h = self.resultsView.header()
|
||||
widths = []
|
||||
visible = []
|
||||
for i in range(len(self.app.data.COLUMNS)):
|
||||
widths.append(h.sectionSize(i))
|
||||
visible.append(not h.isSectionHidden(i))
|
||||
prefs = self.app.prefs
|
||||
prefs.columns_width = widths
|
||||
prefs.columns_visible = visible
|
||||
prefs.save()
|
||||
|
||||
def _update_column_actions_status(self):
|
||||
h = self.resultsView.header()
|
||||
for action in self._column_actions:
|
||||
colid = action.column_index
|
||||
action.setChecked(not h.isSectionHidden(colid))
|
||||
|
||||
def _update_status_line(self):
|
||||
self.statusLabel.setText(self.app.stat_line)
|
||||
|
||||
#--- Actions
|
||||
def aboutTriggered(self):
|
||||
self.app.show_about_box()
|
||||
|
||||
def actionsTriggered(self):
|
||||
self.actionsButton.showMenu()
|
||||
|
||||
def addToIgnoreListTriggered(self):
|
||||
dupes = self.resultsView.selectedDupes()
|
||||
if not dupes:
|
||||
return
|
||||
title = "Add to Ignore List"
|
||||
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes))
|
||||
if self._confirm(title, msg):
|
||||
self.app.add_dupes_to_ignore_list(dupes)
|
||||
|
||||
def applyFilterTriggered(self):
|
||||
title = "Apply Filter"
|
||||
msg = "Type the filter you want to apply on your results. See help for details."
|
||||
text = nonone(self._last_filter, '[*]')
|
||||
answer, ok = QInputDialog.getText(self, title, msg, QLineEdit.Normal, text)
|
||||
if not ok:
|
||||
return
|
||||
answer = unicode(answer)
|
||||
self.app.apply_filter(answer)
|
||||
self._last_filter = answer
|
||||
|
||||
def cancelFilterTriggered(self):
|
||||
self.app.apply_filter('')
|
||||
|
||||
def checkForUpdateTriggered(self):
|
||||
QProcess.execute('updater.exe', ['/checknow'])
|
||||
|
||||
def clearIgnoreListTriggered(self):
|
||||
title = "Clear Ignore List"
|
||||
count = len(self.app.scanner.ignore_list)
|
||||
if not count:
|
||||
QMessageBox.information(self, title, "Nothing to clear.")
|
||||
return
|
||||
msg = "Do you really want to remove all {0} items from the ignore list?".format(count)
|
||||
if self._confirm(title, msg, QMessageBox.No):
|
||||
self.app.scanner.ignore_list.Clear()
|
||||
QMessageBox.information(self, title, "Ignore list cleared.")
|
||||
|
||||
def copyTriggered(self):
|
||||
self.app.copy_or_move_marked(True)
|
||||
|
||||
def deleteTriggered(self):
|
||||
count = self.app.results.mark_count
|
||||
if not count:
|
||||
return
|
||||
title = "Delete duplicates"
|
||||
msg = "You are about to send {0} files to the recycle bin. Continue?".format(count)
|
||||
if self._confirm(title, msg):
|
||||
self.app.delete_marked()
|
||||
|
||||
def deltaTriggered(self):
|
||||
self.resultsModel.delta = self.actionDelta.isChecked()
|
||||
self._redraw_results()
|
||||
|
||||
def detailsTriggered(self):
|
||||
self.app.show_details()
|
||||
|
||||
def directoriesTriggered(self):
|
||||
self.app.show_directories()
|
||||
|
||||
def exportTriggered(self):
|
||||
h = self.resultsView.header()
|
||||
column_ids = []
|
||||
for i in range(len(self.app.data.COLUMNS)):
|
||||
if not h.isSectionHidden(i):
|
||||
column_ids.append(str(i))
|
||||
exported_path = self.app.export_to_xhtml(column_ids)
|
||||
url = QUrl.fromLocalFile(exported_path)
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def makeReferenceTriggered(self):
|
||||
self.app.make_reference(self.resultsView.selectedDupes())
|
||||
|
||||
def markAllTriggered(self):
|
||||
self.app.mark_all()
|
||||
|
||||
def markInvertTriggered(self):
|
||||
self.app.mark_invert()
|
||||
|
||||
def markNoneTriggered(self):
|
||||
self.app.mark_none()
|
||||
|
||||
def markSelectedTriggered(self):
|
||||
dupes = self.resultsView.selectedDupes()
|
||||
self.app.toggle_marking_for_dupes(dupes)
|
||||
|
||||
def moveTriggered(self):
|
||||
self.app.copy_or_move_marked(False)
|
||||
|
||||
def openDebugLogTriggered(self):
|
||||
self.app.openDebugLog()
|
||||
|
||||
def openTriggered(self):
|
||||
self.app.open_selected()
|
||||
|
||||
def powerMarkerTriggered(self):
|
||||
self.resultsModel.power_marker = self.actionPowerMarker.isChecked()
|
||||
|
||||
def preferencesTriggered(self):
|
||||
self.app.show_preferences()
|
||||
|
||||
def registerTrigerred(self):
|
||||
self.app.ask_for_reg_code()
|
||||
|
||||
def removeMarkedTriggered(self):
|
||||
count = self.app.results.mark_count
|
||||
if not count:
|
||||
return
|
||||
title = "Remove duplicates"
|
||||
msg = "You are about to remove {0} files from results. Continue?".format(count)
|
||||
if self._confirm(title, msg):
|
||||
self.app.remove_marked_duplicates()
|
||||
|
||||
def removeSelectedTriggered(self):
|
||||
dupes = self.resultsView.selectedDupes()
|
||||
if not dupes:
|
||||
return
|
||||
title = "Remove duplicates"
|
||||
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes))
|
||||
if self._confirm(title, msg):
|
||||
self.app.remove_duplicates(dupes)
|
||||
|
||||
def renameTriggered(self):
|
||||
self.resultsView.edit(self.resultsView.selectionModel().currentIndex())
|
||||
|
||||
def revealTriggered(self):
|
||||
self.app.reveal_selected()
|
||||
|
||||
def scanTriggered(self):
|
||||
title = "Start a new scan"
|
||||
if len(self.app.results.groups) > 0:
|
||||
msg = "Are you sure you want to start a new duplicate scan?"
|
||||
if not self._confirm(title, msg):
|
||||
return
|
||||
try:
|
||||
self.app.start_scanning()
|
||||
except NoScannableFileError:
|
||||
msg = "The selected directories contain no scannable file."
|
||||
QMessageBox.warning(self, title, msg)
|
||||
self.app.show_directories()
|
||||
except AllFilesAreRefError:
|
||||
msg = "You cannot make a duplicate scan with only reference directories."
|
||||
QMessageBox.warning(self, title, msg)
|
||||
|
||||
def showHelpTriggered(self):
|
||||
self.app.show_help()
|
||||
|
||||
#--- Events
|
||||
def application_will_terminate(self):
|
||||
self._save_columns()
|
||||
|
||||
def columnToggled(self, action):
|
||||
colid = action.column_index
|
||||
if colid == -1:
|
||||
self.app.prefs.reset_columns()
|
||||
self._load_columns()
|
||||
else:
|
||||
h = self.resultsView.header()
|
||||
h.setSectionHidden(colid, not h.isSectionHidden(colid))
|
||||
self._update_column_actions_status()
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self.actionActions.menu().exec_(event.globalPos())
|
||||
|
||||
def dupeMarkingChanged(self):
|
||||
self._redraw_results()
|
||||
self._update_status_line()
|
||||
|
||||
def resultsChanged(self):
|
||||
self.resultsView.model().reset()
|
||||
|
||||
def resultsDoubleClicked(self):
|
||||
self.app.open_selected()
|
||||
|
||||
def resultsReset(self):
|
||||
self.resultsView.expandAll()
|
||||
dupe = self.app.selected_dupe
|
||||
if dupe is not None:
|
||||
[modelIndex] = self.resultsModel.indexesForDupes([dupe])
|
||||
if modelIndex.isValid():
|
||||
flags = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
|
||||
self.resultsView.selectionModel().setCurrentIndex(modelIndex, flags)
|
||||
self._update_status_line()
|
||||
|
||||
def selectionChanged(self, selected, deselected):
|
||||
index = self.resultsView.selectionModel().currentIndex()
|
||||
dupe = index.internalPointer().dupe if index.isValid() else None
|
||||
self.app.select_duplicate(dupe)
|
||||
|
||||
957
qt/base/main_window.ui
Normal file
957
qt/base/main_window.ui
Normal file
@@ -0,0 +1,957 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>630</width>
|
||||
<height>514</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>dupeGuru</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="ResultsView" name="resultsView">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="expandsOnDoubleClick">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>630</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuColumns">
|
||||
<property name="title">
|
||||
<string>Columns</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuActions">
|
||||
<property name="title">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
<addaction name="actionDeleteMarked"/>
|
||||
<addaction name="actionMoveMarked"/>
|
||||
<addaction name="actionCopyMarked"/>
|
||||
<addaction name="actionRemoveMarked"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionRemoveSelected"/>
|
||||
<addaction name="actionIgnoreSelected"/>
|
||||
<addaction name="actionMakeSelectedReference"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionOpenSelected"/>
|
||||
<addaction name="actionRevealSelected"/>
|
||||
<addaction name="actionRenameSelected"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionApplyFilter"/>
|
||||
<addaction name="actionCancelFilter"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuMark">
|
||||
<property name="title">
|
||||
<string>Mark</string>
|
||||
</property>
|
||||
<addaction name="actionMarkAll"/>
|
||||
<addaction name="actionMarkNone"/>
|
||||
<addaction name="actionInvertMarking"/>
|
||||
<addaction name="actionMarkSelected"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuModes">
|
||||
<property name="title">
|
||||
<string>Modes</string>
|
||||
</property>
|
||||
<addaction name="actionPowerMarker"/>
|
||||
<addaction name="actionDelta"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuWindow">
|
||||
<property name="title">
|
||||
<string>Windows</string>
|
||||
</property>
|
||||
<addaction name="actionDetails"/>
|
||||
<addaction name="actionDirectories"/>
|
||||
<addaction name="actionPreferences"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionShowHelp"/>
|
||||
<addaction name="actionRegister"/>
|
||||
<addaction name="actionCheckForUpdate"/>
|
||||
<addaction name="actionOpenDebugLog"/>
|
||||
<addaction name="actionAbout"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionScan"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionExport"/>
|
||||
<addaction name="actionClearIgnoreList"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuMark"/>
|
||||
<addaction name="menuActions"/>
|
||||
<addaction name="menuColumns"/>
|
||||
<addaction name="menuModes"/>
|
||||
<addaction name="menuWindow"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar</string>
|
||||
</property>
|
||||
<property name="movable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionScan"/>
|
||||
<addaction name="actionActions"/>
|
||||
<addaction name="actionDirectories"/>
|
||||
<addaction name="actionDetails"/>
|
||||
<addaction name="actionPreferences"/>
|
||||
<addaction name="actionDelta"/>
|
||||
<addaction name="actionPowerMarker"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar">
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<action name="actionScan">
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/logo_pe</normaloff>:/logo_pe</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start Scan</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Start scanning for duplicates</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDirectories">
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/folder</normaloff>:/folder</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Directories</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+4</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDetails">
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/details</normaloff>:/details</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Details</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+3</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionActions">
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/actions</normaloff>:/actions</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPreferences">
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/preferences</normaloff>:/preferences</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+5</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDelta">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/delta</normaloff>:/delta</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delta Values</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+2</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPowerMarker">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="dg.qrc">
|
||||
<normaloff>:/power_marker</normaloff>:/power_marker</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Power Marker</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+1</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDeleteMarked">
|
||||
<property name="text">
|
||||
<string>Send Marked to Recycle Bin</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+D</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMoveMarked">
|
||||
<property name="text">
|
||||
<string>Move Marked to...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+M</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCopyMarked">
|
||||
<property name="text">
|
||||
<string>Copy Marked to...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+M</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemoveMarked">
|
||||
<property name="text">
|
||||
<string>Remove Marked from Results</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+R</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemoveSelected">
|
||||
<property name="text">
|
||||
<string>Remove Selected from Results</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Del</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionIgnoreSelected">
|
||||
<property name="text">
|
||||
<string>Add Selected to Ignore List</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+Del</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMakeSelectedReference">
|
||||
<property name="text">
|
||||
<string>Make Selected Reference</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Space</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpenSelected">
|
||||
<property name="text">
|
||||
<string>Open Selected with Default Application</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+O</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRevealSelected">
|
||||
<property name="text">
|
||||
<string>Open Containing Folder of Selected</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+O</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRenameSelected">
|
||||
<property name="text">
|
||||
<string>Rename Selected</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F2</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMarkAll">
|
||||
<property name="text">
|
||||
<string>Mark All</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMarkNone">
|
||||
<property name="text">
|
||||
<string>Mark None</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInvertMarking">
|
||||
<property name="text">
|
||||
<string>Invert Marking</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Alt+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMarkSelected">
|
||||
<property name="text">
|
||||
<string>Mark Selected</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClearIgnoreList">
|
||||
<property name="text">
|
||||
<string>Clear Ignore List</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionQuit">
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionApplyFilter">
|
||||
<property name="text">
|
||||
<string>Apply Filter</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+F</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCancelFilter">
|
||||
<property name="text">
|
||||
<string>Cancel Filter</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+F</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionShowHelp">
|
||||
<property name="text">
|
||||
<string>dupeGuru Help</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F1</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAbout">
|
||||
<property name="text">
|
||||
<string>About dupeGuru</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRegister">
|
||||
<property name="text">
|
||||
<string>Register dupeGuru</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCheckForUpdate">
|
||||
<property name="text">
|
||||
<string>Check for Update</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExport">
|
||||
<property name="text">
|
||||
<string>Export To XHTML</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpenDebugLog">
|
||||
<property name="text">
|
||||
<string>Open Debug Log</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ResultsView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>results_model</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="dg.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>actionDirectories</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>directoriesTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionActions</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>actionsTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionCopyMarked</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>copyTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionDeleteMarked</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>deleteTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionDelta</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>deltaTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionDetails</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>detailsTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionIgnoreSelected</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>addToIgnoreListTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionMakeSelectedReference</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>makeReferenceTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionMoveMarked</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>moveTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionOpenSelected</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>openTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionPowerMarker</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>powerMarkerTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionPreferences</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>preferencesTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionRemoveMarked</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>removeMarkedTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionRemoveSelected</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>removeSelectedTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionRevealSelected</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>revealTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionRenameSelected</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>renameTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionScan</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>scanTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionClearIgnoreList</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>clearIgnoreListTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionMarkAll</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>markAllTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionMarkNone</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>markNoneTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionMarkSelected</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>markSelectedTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionInvertMarking</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>markInvertTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionApplyFilter</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>applyFilterTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionCancelFilter</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>cancelFilterTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionShowHelp</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>showHelpTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionAbout</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>aboutTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionRegister</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>registerTrigerred()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionCheckForUpdate</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>checkForUpdateTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionExport</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>exportTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>actionOpenDebugLog</sender>
|
||||
<signal>triggered()</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>openDebugLogTriggered()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>-1</x>
|
||||
<y>-1</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>314</x>
|
||||
<y>256</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>directoriesTriggered()</slot>
|
||||
<slot>scanTriggered()</slot>
|
||||
<slot>actionsTriggered()</slot>
|
||||
<slot>detailsTriggered()</slot>
|
||||
<slot>preferencesTriggered()</slot>
|
||||
<slot>deltaTriggered()</slot>
|
||||
<slot>powerMarkerTriggered()</slot>
|
||||
<slot>deleteTriggered()</slot>
|
||||
<slot>moveTriggered()</slot>
|
||||
<slot>copyTriggered()</slot>
|
||||
<slot>removeMarkedTriggered()</slot>
|
||||
<slot>removeSelectedTriggered()</slot>
|
||||
<slot>addToIgnoreListTriggered()</slot>
|
||||
<slot>makeReferenceTriggered()</slot>
|
||||
<slot>openTriggered()</slot>
|
||||
<slot>revealTriggered()</slot>
|
||||
<slot>renameTriggered()</slot>
|
||||
<slot>clearIgnoreListTriggered()</slot>
|
||||
<slot>clearPictureCacheTriggered()</slot>
|
||||
<slot>markAllTriggered()</slot>
|
||||
<slot>markNoneTriggered()</slot>
|
||||
<slot>markInvertTriggered()</slot>
|
||||
<slot>markSelectedTriggered()</slot>
|
||||
<slot>applyFilterTriggered()</slot>
|
||||
<slot>cancelFilterTriggered()</slot>
|
||||
<slot>showHelpTriggered()</slot>
|
||||
<slot>aboutTriggered()</slot>
|
||||
<slot>registerTrigerred()</slot>
|
||||
<slot>checkForUpdateTriggered()</slot>
|
||||
<slot>exportTriggered()</slot>
|
||||
<slot>openDebugLogTriggered()</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
19
qt/base/platform.py
Normal file
19
qt/base/platform.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-09-27
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from platform_win import *
|
||||
elif sys.platform == 'darwin':
|
||||
from platform_osx import *
|
||||
else:
|
||||
pass # unsupported platform
|
||||
16
qt/base/platform_osx.py
Normal file
16
qt/base/platform_osx.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-10-14
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
# dummy unit to allow the app to run under OSX during development
|
||||
|
||||
INITIAL_FOLDER_IN_DIALOGS = '/'
|
||||
|
||||
def recycle_file(path):
|
||||
pass
|
||||
23
qt/base/platform_win.py
Normal file
23
qt/base/platform_win.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-08-31
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
import winshell
|
||||
|
||||
INITIAL_FOLDER_IN_DIALOGS = 'C:\\'
|
||||
|
||||
def recycle_file(path):
|
||||
try:
|
||||
winshell.delete_file(unicode(path), no_confirm=True, silent=True)
|
||||
except winshell.x_winshell as e:
|
||||
logging.warning("winshell error: %s", e)
|
||||
116
qt/base/preferences.py
Normal file
116
qt/base/preferences.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-05-03
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from PyQt4.QtCore import QSettings, QVariant
|
||||
|
||||
from hsutil.misc import tryint
|
||||
|
||||
def variant_to_py(v):
|
||||
value = None
|
||||
ok = False
|
||||
t = v.type()
|
||||
if t == QVariant.String:
|
||||
value = unicode(v.toString())
|
||||
ok = True # anyway
|
||||
# might be bool or int, try them
|
||||
if v == 'true':
|
||||
value = True
|
||||
elif value == 'false':
|
||||
value = False
|
||||
else:
|
||||
value = tryint(value, value)
|
||||
elif t == QVariant.Int:
|
||||
value, ok = v.toInt()
|
||||
elif t == QVariant.Bool:
|
||||
value, ok = v.toBool(), True
|
||||
elif t in (QVariant.List, QVariant.StringList):
|
||||
value, ok = map(variant_to_py, v.toList()), True
|
||||
if not ok:
|
||||
raise TypeError(u"Can't convert {0} of type {1}".format(repr(v), v.type()))
|
||||
return value
|
||||
|
||||
def py_to_variant(v):
|
||||
if isinstance(v, (list, tuple)):
|
||||
return QVariant(map(py_to_variant, v))
|
||||
return QVariant(v)
|
||||
|
||||
class Preferences(object):
|
||||
# (width, is_visible)
|
||||
COLUMNS_DEFAULT_ATTRS = []
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
self.reset_columns()
|
||||
|
||||
def _load_specific(self, settings, get):
|
||||
# load prefs specific to the dg edition
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
self.reset()
|
||||
settings = QSettings()
|
||||
def get(name, default):
|
||||
if settings.contains(name):
|
||||
return variant_to_py(settings.value(name))
|
||||
else:
|
||||
return default
|
||||
|
||||
self.filter_hardness = get('FilterHardness', self.filter_hardness)
|
||||
self.mix_file_kind = get('MixFileKind', self.mix_file_kind)
|
||||
self.use_regexp = get('UseRegexp', self.use_regexp)
|
||||
self.remove_empty_folders = get('RemoveEmptyFolders', self.remove_empty_folders)
|
||||
self.destination_type = get('DestinationType', self.destination_type)
|
||||
widths = get('ColumnsWidth', self.columns_width)
|
||||
# only set nonzero values
|
||||
for index, width in enumerate(widths[:len(self.columns_width)]):
|
||||
if width > 0:
|
||||
self.columns_width[index] = width
|
||||
self.columns_visible = get('ColumnsVisible', self.columns_visible)
|
||||
self.registration_code = get('RegistrationCode', self.registration_code)
|
||||
self.registration_email = get('RegistrationEmail', self.registration_email)
|
||||
self._load_specific(settings, get)
|
||||
|
||||
def _reset_specific(self):
|
||||
# reset prefs specific to the dg edition
|
||||
pass
|
||||
|
||||
def reset(self):
|
||||
self.filter_hardness = 95
|
||||
self.mix_file_kind = True
|
||||
self.use_regexp = False
|
||||
self.remove_empty_folders = False
|
||||
self.destination_type = 1
|
||||
self.registration_code = ''
|
||||
self.registration_email = ''
|
||||
self._reset_specific()
|
||||
|
||||
def reset_columns(self):
|
||||
self.columns_width = [width for width, _ in self.COLUMNS_DEFAULT_ATTRS]
|
||||
self.columns_visible = [visible for _, visible in self.COLUMNS_DEFAULT_ATTRS]
|
||||
|
||||
def _save_specific(self, settings, set_):
|
||||
# save prefs specific to the dg edition
|
||||
pass
|
||||
|
||||
def save(self):
|
||||
settings = QSettings()
|
||||
def set_(name, value):
|
||||
settings.setValue(name, py_to_variant(value))
|
||||
|
||||
set_('FilterHardness', self.filter_hardness)
|
||||
set_('MixFileKind', self.mix_file_kind)
|
||||
set_('UseRegexp', self.use_regexp)
|
||||
set_('RemoveEmptyFolders', self.remove_empty_folders)
|
||||
set_('DestinationType', self.destination_type)
|
||||
set_('ColumnsWidth', self.columns_width)
|
||||
set_('ColumnsVisible', self.columns_visible)
|
||||
set_('RegistrationCode', self.registration_code)
|
||||
set_('RegistrationEmail', self.registration_email)
|
||||
self._save_specific(settings, set_)
|
||||
|
||||
204
qt/base/results_model.py
Normal file
204
qt/base/results_model.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-04-23
|
||||
# $Id$
|
||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.hardcoded.net/licenses/hs_license
|
||||
|
||||
from PyQt4.QtCore import SIGNAL, Qt, QAbstractItemModel, QModelIndex, QRect
|
||||
from PyQt4.QtGui import QBrush, QStyledItemDelegate, QFont, QTreeView, QColor
|
||||
|
||||
from qtlib.tree_model import TreeNode, TreeModel
|
||||
|
||||
class ResultNode(TreeNode):
|
||||
def __init__(self, model, parent, row, dupe, group):
|
||||
TreeNode.__init__(self, model, parent, row)
|
||||
self.dupe = dupe
|
||||
self.group = group
|
||||
self._normalData = None
|
||||
self._deltaData = None
|
||||
|
||||
def _createNode(self, ref, row):
|
||||
return ResultNode(self.model, self, row, ref, self.group)
|
||||
|
||||
def _getChildren(self):
|
||||
return self.group.dupes if self.dupe is self.group.ref else []
|
||||
|
||||
def invalidate(self):
|
||||
self._normalData = None
|
||||
self._deltaData = None
|
||||
TreeNode.invalidate(self)
|
||||
|
||||
@property
|
||||
def normalData(self):
|
||||
if self._normalData is None:
|
||||
self._normalData = self.model._app._get_display_info(self.dupe, self.group, delta=False)
|
||||
return self._normalData
|
||||
|
||||
@property
|
||||
def deltaData(self):
|
||||
if self._deltaData is None:
|
||||
self._deltaData = self.model._app._get_display_info(self.dupe, self.group, delta=True)
|
||||
return self._deltaData
|
||||
|
||||
|
||||
class ResultsDelegate(QStyledItemDelegate):
|
||||
def initStyleOption(self, option, index):
|
||||
QStyledItemDelegate.initStyleOption(self, option, index)
|
||||
node = index.internalPointer()
|
||||
if node.group.ref is node.dupe:
|
||||
newfont = QFont(option.font)
|
||||
newfont.setBold(True)
|
||||
option.font = newfont
|
||||
|
||||
|
||||
class ResultsModel(TreeModel):
|
||||
def __init__(self, app):
|
||||
self._app = app
|
||||
self._results = app.results
|
||||
self._data = app.data
|
||||
self._delta_columns = app.DELTA_COLUMNS
|
||||
self.delta = False
|
||||
self._power_marker = False
|
||||
TreeModel.__init__(self)
|
||||
|
||||
def _createNode(self, ref, row):
|
||||
if self.power_marker:
|
||||
# ref is a dupe
|
||||
group = self._results.get_group_of_duplicate(ref)
|
||||
return ResultNode(self, None, row, ref, group)
|
||||
else:
|
||||
# ref is a group
|
||||
return ResultNode(self, None, row, ref.ref, ref)
|
||||
|
||||
def _getChildren(self):
|
||||
return self._results.dupes if self.power_marker else self._results.groups
|
||||
|
||||
def columnCount(self, parent):
|
||||
return len(self._data.COLUMNS)
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
node = index.internalPointer()
|
||||
if role == Qt.DisplayRole:
|
||||
data = node.deltaData if self.delta else node.normalData
|
||||
return data[index.column()]
|
||||
elif role == Qt.CheckStateRole:
|
||||
if index.column() == 0 and node.dupe is not node.group.ref:
|
||||
state = Qt.Checked if self._results.is_marked(node.dupe) else Qt.Unchecked
|
||||
return state
|
||||
elif role == Qt.ForegroundRole:
|
||||
if node.dupe is node.group.ref or node.dupe.is_ref:
|
||||
return QBrush(Qt.blue)
|
||||
elif self.delta and index.column() in self._delta_columns:
|
||||
return QBrush(QColor(255, 142, 40)) # orange
|
||||
elif role == Qt.EditRole:
|
||||
if index.column() == 0:
|
||||
return node.normalData[index.column()]
|
||||
return None
|
||||
|
||||
def dupesForIndexes(self, indexes):
|
||||
nodes = [index.internalPointer() for index in indexes]
|
||||
return [node.dupe for node in nodes]
|
||||
|
||||
def indexesForDupes(self, dupes):
|
||||
def index(dupe):
|
||||
try:
|
||||
if self.power_marker:
|
||||
row = self._results.dupes.index(dupe)
|
||||
node = self.subnodes[row]
|
||||
assert node.dupe is dupe
|
||||
return self.createIndex(row, 0, node)
|
||||
else:
|
||||
group = self._results.get_group_of_duplicate(dupe)
|
||||
row = self._results.groups.index(group)
|
||||
node = self.subnodes[row]
|
||||
if dupe is group.ref:
|
||||
assert node.dupe is dupe
|
||||
return self.createIndex(row, 0, node)
|
||||
subrow = group.dupes.index(dupe)
|
||||
subnode = node.subnodes[subrow]
|
||||
assert subnode.dupe is dupe
|
||||
return self.createIndex(subrow, 0, subnode)
|
||||
except ValueError: # the dupe is not there anymore
|
||||
return QModelIndex()
|
||||
|
||||
return map(index, dupes)
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return Qt.ItemIsEnabled
|
||||
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
if index.column() == 0:
|
||||
flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(self._data.COLUMNS):
|
||||
return self._data.COLUMNS[section]['display']
|
||||
return None
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if not index.isValid():
|
||||
return False
|
||||
node = index.internalPointer()
|
||||
if role == Qt.CheckStateRole:
|
||||
if index.column() == 0:
|
||||
self._app.toggle_marking_for_dupes([node.dupe])
|
||||
return True
|
||||
if role == Qt.EditRole:
|
||||
if index.column() == 0:
|
||||
value = unicode(value.toString())
|
||||
if self._app.rename_dupe(node.dupe, value):
|
||||
node.invalidate()
|
||||
return True
|
||||
return False
|
||||
|
||||
def sort(self, column, order):
|
||||
if self.power_marker:
|
||||
self._results.sort_dupes(column, order == Qt.AscendingOrder, self.delta)
|
||||
else:
|
||||
self._results.sort_groups(column, order == Qt.AscendingOrder)
|
||||
self.reset()
|
||||
|
||||
def toggleMarked(self, indexes):
|
||||
assert indexes
|
||||
dupes = self.dupesForIndexes(indexes)
|
||||
self._app.toggle_marking_for_dupes(dupes)
|
||||
|
||||
#--- Properties
|
||||
@property
|
||||
def power_marker(self):
|
||||
return self._power_marker
|
||||
|
||||
@power_marker.setter
|
||||
def power_marker(self, value):
|
||||
if value == self._power_marker:
|
||||
return
|
||||
self._power_marker = value
|
||||
self.reset()
|
||||
|
||||
|
||||
class ResultsView(QTreeView):
|
||||
#--- Override
|
||||
def keyPressEvent(self, event):
|
||||
if event.text() == ' ':
|
||||
self.model().toggleMarked(self.selectionModel().selectedRows())
|
||||
return
|
||||
QTreeView.keyPressEvent(self, event)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
self.emit(SIGNAL('doubleClicked()'))
|
||||
# We don't call the superclass' method because the default behavior is to rename the cell.
|
||||
|
||||
def setModel(self, model):
|
||||
assert isinstance(model, ResultsModel)
|
||||
QTreeView.setModel(self, model)
|
||||
|
||||
#--- Public
|
||||
def selectedDupes(self):
|
||||
return self.model().dupesForIndexes(self.selectionModel().selectedRows())
|
||||
|
||||
Reference in New Issue
Block a user