1
0
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:
hsoft
2009-12-30 16:34:41 +00:00
parent 5645515d90
commit 838f8ae352
251 changed files with 602 additions and 500 deletions

11
qt/WARNING Normal file
View File

@@ -0,0 +1,11 @@
WARNING ABOUT THE HS LICENSE AND PyQt
Although Qt is now LGPL licensed, PyQt still is dual licensed. Until Nokia buys Riverbank and
releases PyQt as LGPL, users of this part of the code (The PyQt-based GUI code) have to use the
GPL version of PyQt, unless they possess a commercial license to it.
There is no problem to this AS LONG AS YOU DON'T REDISTRIBUTE HS LICENSED CODE. The GPL license, from the point of view of the user, is very permissive. You can do WHATEVER you want with the GPLed version of PyQt, as long as you don't redistribute any of the code, or code dependent on it. When you do, the code you distribute has to be GPL compliant. The HS license is NOT, I repeat, NOT compliant with the GPL.
So, what does it all mean? You have no restriction on the usage of the PyQt-dependent-HS-licensed code, but unless you possess a commercial PyQt license, Hardcoded Software (or anyone) cannot accept any contribution from you for this part of the code.
Note that this only affects the PyQt dependent code, and not any other part of HS licensed code (if it has "import PyQt4" in it, it's PyQt dependent code). For the rest of the code, the only restrictions that apply are the ones from the HS license.

0
qt/base/__init__.py Normal file
View File

257
qt/base/app.py Normal file
View 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
View 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
View 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>

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

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

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

60
qt/me/app.py Normal file
View File

@@ -0,0 +1,60 @@
# Created By: Virgil Dupras
# Created On: 2009-05-21
# $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 core_me import data, scanner, fs
from base.app import DupeGuru as DupeGuruBase
from details_dialog import DetailsDialog
from preferences import Preferences
from preferences_dialog import PreferencesDialog
class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_me'
NAME = 'dupeGuru Music Edition'
VERSION = '5.7.0'
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8])
def __init__(self):
DupeGuruBase.__init__(self, data, appid=1)
def _setup(self):
self.scanner = scanner.ScannerME()
self.directories.fileclasses = [fs.Mp3File, fs.Mp4File, fs.WmaFile, fs.OggFile, fs.FlacFile, fs.AiffFile]
DupeGuruBase._setup(self)
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.min_match_percentage = self.prefs.filter_hardness
self.scanner.scan_type = self.prefs.scan_type
self.scanner.word_weighting = self.prefs.word_weighting
self.scanner.match_similar_words = self.prefs.match_similar
scanned_tags = set()
if self.prefs.scan_tag_track:
scanned_tags.add('track')
if self.prefs.scan_tag_artist:
scanned_tags.add('artist')
if self.prefs.scan_tag_album:
scanned_tags.add('album')
if self.prefs.scan_tag_title:
scanned_tags.add('title')
if self.prefs.scan_tag_genre:
scanned_tags.add('genre')
if self.prefs.scan_tag_year:
scanned_tags.add('year')
self.scanner.scanned_tags = scanned_tags
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)
def _create_preferences(self):
return Preferences()
def _create_preferences_dialog(self, parent):
return PreferencesDialog(parent, self)

44
qt/me/build.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $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
# On Windows, PyInstaller is used to build an exe (py2exe creates a very bad looking icon)
# The release version is outdated. Use at least r672 on http://svn.pyinstaller.org/trunk
import os
import os.path as op
import shutil
from hsutil.build import print_and_do
from app import DupeGuru
# Removing build and dist
if op.exists('build'):
shutil.rmtree('build')
if op.exists('dist'):
shutil.rmtree('dist')
version = DupeGuru.VERSION
versioncomma = version.replace('.', ', ') + ', 0'
verinfo = open('verinfo').read()
verinfo = verinfo.replace('$versioncomma', versioncomma).replace('$version', version)
fp = open('verinfo_tmp', 'w')
fp.write(verinfo)
fp.close()
print_and_do("python C:\\Python26\\pyinstaller\\Build.py dgme.spec")
os.remove('verinfo_tmp')
print_and_do("del dist\\*90.dll") # They're in vcredist, no need to include them
print_and_do("xcopy /Y /S /I help\\dupeguru_me_help dist\\help")
aicom = '"\\Program Files\\Caphyon\\Advanced Installer\\AdvancedInstaller.com"'
shutil.copy('installer.aip', 'installer_tmp.aip') # this is so we don'a have to re-commit installer.aip at every version change
print_and_do('%s /edit installer_tmp.aip /SetVersion %s' % (aicom, version))
print_and_do('%s /build installer_tmp.aip -force' % aicom)
os.remove('installer_tmp.aip')

22
qt/me/details_dialog.py Normal file
View File

@@ -0,0 +1,22 @@
# Created By: Virgil Dupras
# Created On: 2009-04-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
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QDialog
from base.details_table import DetailsModel
from details_dialog_ui import Ui_DetailsDialog
class DetailsDialog(QDialog, Ui_DetailsDialog):
def __init__(self, parent, app):
QDialog.__init__(self, parent, Qt.Tool)
self.app = app
self.setupUi(self)
self.model = DetailsModel(app)
self.tableView.setModel(self.model)

53
qt/me/details_dialog.ui Normal file
View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DetailsDialog</class>
<widget class="QDialog" name="DetailsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>502</width>
<height>295</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>250</height>
</size>
</property>
<property name="windowTitle">
<string>Details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="DetailsTable" name="tableView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DetailsTable</class>
<extends>QTableView</extends>
<header>base.details_table</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

19
qt/me/dgme.spec Normal file
View File

@@ -0,0 +1,19 @@
# -*- mode: python -*-
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'start.py'],
pathex=[])
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
exclude_binaries=1,
name=os.path.join('build\\pyi.win32\\dupeGuru ME', 'dupeGuru ME.exe'),
debug=False,
strip=False,
upx=True,
console=False , icon='..\\base\\images\\dgme_logo.ico', version='verinfo_tmp')
coll = COLLECT( exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name=os.path.join('dist'))

19
qt/me/gen.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $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 os
import os.path as op
from hsutil.build import print_and_do, build_all_qt_ui
build_all_qt_ui(op.join('..', '..', 'qtlib', 'ui'))
build_all_qt_ui(op.join('..', 'base'))
build_all_qt_ui('.')
print_and_do("pyrcc4 {0} > {1}".format(op.join('..', 'base', 'dg.qrc'), op.join('..', 'base', 'dg_rc.py')))

245
qt/me/installer.aip Normal file
View File

@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT type="Advanced Installer" CreateVersion="4.7.2" version="4.9.2" modules="professional" RootPath="." Language="en">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_DESKTOP_SH" Value="1" Type="4"/>
<ROW Property="AI_QUICKLAUNCH_SH" Value="1" Type="4"/>
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
<ROW Property="AI_STARTMENU_SH" Value="1" Type="4"/>
<ROW Property="AI_STARTUP_SH" Value="1" Type="4"/>
<ROW Property="ALLUSERS" Value="2"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru_me/"/>
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru_me/"/>
<ROW Property="BannerBitmap" Value="default_banner.bmp" Type="1"/>
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="default_dialog.bmp" Type="1"/>
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
<ROW Property="ProductCode" Value="1033:{A7037226-389C-4704-81C3-E2CA17D11058} "/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="dupeGuru Music Edition" ValueLocId="*"/>
<ROW Property="ProductVersion" Value="5.6.0"/>
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
<ROW Property="UpgradeCode" Value="{E11BFC48-7639-44BD-BB5B-A6AC934BC12D}"/>
<ROW Property="WindowsFamily9X" Value="Windows 9x/ME"/>
<ROW Property="WindowsTypeNT" Value="Windows 2000"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
<ROW Directory="accessible_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="access~1|accessible"/>
<ROW Directory="codecs_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="codecs"/>
<ROW Directory="iconengines_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="iconen~1|iconengines"/>
<ROW Directory="imageformats_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="imagef~1|imageformats"/>
<ROW Directory="qt4_plugins_DIR" Directory_Parent="APPDIR" DefaultDir="qt4_pl~1|qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
<ROW Component="AIShRegAnswer" ComponentId="{7DB5C163-2978-436F-A47D-54F1C76F1D64}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer" FullKeyPath="HK_UM\Software\Caphyon\Advanced Installer\Installs\[ProductCode]\AIShRegAnswer"/>
<ROW Component="MSVCP90.dll" ComponentId="{9CD64E9F-44D0-4EB9-9FB2-B2F1AB1551CE}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCP90.dll" FullKeyPath="APPDIR\MSVCP90.dll"/>
<ROW Component="MSVCR90.dll" ComponentId="{2D77BB4F-0B6B-452A-A7D1-2DB8CDC3BC0B}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCR90.dll" FullKeyPath="APPDIR\MSVCR90.dll"/>
<ROW Component="POWRPROF.dll" ComponentId="{12B8FFB0-6FEB-49A9-8A09-5B4B26379034}" Directory_="APPDIR" Attributes="0" KeyPath="POWRPROF.dll" FullKeyPath="APPDIR\POWRPROF.dll"/>
<ROW Component="PyWinTypes26.dll" ComponentId="{E7DC87D7-F396-46F0-8132-D4F582709AA2}" Directory_="APPDIR" Attributes="0" KeyPath="PyWinTypes26.dll" FullKeyPath="APPDIR\PyWinTypes26.dll"/>
<ROW Component="QtCore4.dll" ComponentId="{731F12A4-0DB0-4484-8BA1-399560578D68}" Directory_="APPDIR" Attributes="0" KeyPath="QtCore4.dll" FullKeyPath="APPDIR\QtCore4.dll"/>
<ROW Component="QtGui4.dll" ComponentId="{3D4FDAFA-EE5E-43C5-A4F5-125F7FA6A550}" Directory_="APPDIR" Attributes="0" KeyPath="QtGui4.dll" FullKeyPath="APPDIR\QtGui4.dll"/>
<ROW Component="SHLWAPI.dll" ComponentId="{FF064632-83EE-43F6-8AE2-7982EFF81216}" Directory_="APPDIR" Attributes="0" KeyPath="SHLWAPI.dll" FullKeyPath="APPDIR\SHLWAPI.dll"/>
<ROW Component="bz2.pyd_1" ComponentId="{E34B2912-4B50-4EEA-B1EF-B56FA625FCC7}" Directory_="APPDIR" Attributes="0" KeyPath="bz2.pyd" FullKeyPath="APPDIR"/>
<ROW Component="dupeGuru_ME.exe" ComponentId="{14F7B73A-FA85-4C10-AA92-10BE05FE103B}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru_ME.exe" FullKeyPath="APPDIR\dupeGuru ME.exe"/>
<ROW Component="iertutil.dll" ComponentId="{C4907490-FE25-4CD9-B31A-583DFF8A4359}" Directory_="APPDIR" Attributes="0" KeyPath="iertutil.dll" FullKeyPath="APPDIR\iertutil.dll"/>
<ROW Component="mfc90.dll" ComponentId="{B206C255-03A4-4A40-92F2-5565830FEB14}" Directory_="APPDIR" Attributes="0" KeyPath="mfc90.dll" FullKeyPath="APPDIR\mfc90.dll"/>
<ROW Component="msvcm90.dll" ComponentId="{B86025F0-23B8-45B6-B870-459DF292E17D}" Directory_="APPDIR" Attributes="0" KeyPath="msvcm90.dll" FullKeyPath="APPDIR\msvcm90.dll"/>
<ROW Component="python26.dll" ComponentId="{20E0EA31-C5CF-4278-B7C6-5FCA0C691B7F}" Directory_="APPDIR" Attributes="0" KeyPath="python26.dll" FullKeyPath="APPDIR\python26.dll"/>
<ROW Component="pythoncom26.dll" ComponentId="{C0489561-2362-48C1-86E8-D5F1A08DE4DE}" Directory_="APPDIR" Attributes="0" KeyPath="pythoncom26.dll" FullKeyPath="APPDIR\pythoncom26.dll"/>
<ROW Component="qcncodecs4.dll" ComponentId="{03C97279-4453-48E0-A40D-D28E6F3E701F}" Directory_="codecs_DIR" Attributes="0" KeyPath="qcncodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qcncodecs4.dll"/>
<ROW Component="qgif4.dll" ComponentId="{5D6E676F-18BC-49C8-BBD2-E6BA1F3CA043}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qgif4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qgif4.dll"/>
<ROW Component="qico4.dll" ComponentId="{C23E13E3-0E14-46E8-AA73-5D592CDBB725}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qico4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qico4.dll"/>
<ROW Component="qjpcodecs4.dll" ComponentId="{E6649370-16BB-449A-8668-9995B5C1CEB5}" Directory_="codecs_DIR" Attributes="0" KeyPath="qjpcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qjpcodecs4.dll"/>
<ROW Component="qjpeg4.dll" ComponentId="{1D084E38-4A03-4C3C-87F7-3C68FEB41B4C}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qjpeg4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qjpeg4.dll"/>
<ROW Component="qkrcodecs4.dll" ComponentId="{18F32B38-7D3F-4F3C-AB80-3778634C8676}" Directory_="codecs_DIR" Attributes="0" KeyPath="qkrcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qkrcodecs4.dll"/>
<ROW Component="qmng4.dll" ComponentId="{D7E0BF84-EB34-4E4B-81CD-93B47B31503C}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qmng4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qmng4.dll"/>
<ROW Component="qsvg4.dll" ComponentId="{3920BD4C-9A56-4CE4-A321-E649652D3D8A}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qsvg4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qsvg4.dll"/>
<ROW Component="qsvgicon4.dll" ComponentId="{F5D7A7C3-6320-4274-90DF-9F42C94FCDED}" Directory_="iconengines_DIR" Attributes="0" KeyPath="qsvgicon4.dll" FullKeyPath="APPDIR\qt4_plugins\iconengines\qsvgicon4.dll"/>
<ROW Component="qt4_plugins" ComponentId="{5711E103-B078-4868-94BC-A50DC4CA2EE6}" Directory_="qt4_plugins_DIR" Attributes="0"/>
<ROW Component="qtaccessiblecompatwidgets4.dll" ComponentId="{990608A3-AD85-439A-B46A-764DF560FA3A}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblecompatwidgets4.dll" FullKeyPath="APPDIR\qt4_plugins\accessible\qtaccessiblecompatwidgets4.dll"/>
<ROW Component="qtaccessiblewidgets4.dll" ComponentId="{1F04BD2B-42BD-412D-B101-806B130CD1B8}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblewidgets4.dll" FullKeyPath="APPDIR\qt4_plugins\accessible\qtaccessiblewidgets4.dll"/>
<ROW Component="qtiff4.dll" ComponentId="{D5B4A7B8-7D9B-4A5C-9DD5-2048690EEE11}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qtiff4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qtiff4.dll"/>
<ROW Component="qtwcodecs4.dll" ComponentId="{E6C5E92F-D193-4A75-88E2-5058F069C224}" Directory_="codecs_DIR" Attributes="0" KeyPath="qtwcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qtwcodecs4.dll"/>
<ROW Component="updater.exe" ComponentId="{D1DDB6CB-B336-4112-BC40-1ABD36C3ABDA}" Directory_="APPDIR" Attributes="0" KeyPath="updater.exe" FullKeyPath="APPDIR\updater.exe"/>
<ROW Component="urlmon.dll" ComponentId="{BAD794CE-427A-4BB0-9C23-E4BBCDE988C3}" Directory_="APPDIR" Attributes="0" KeyPath="urlmon.dll" FullKeyPath="APPDIR\urlmon.dll"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="updater.exe dupeGuru_ME.exe AIShRegAnswer bz2.pyd_1 iertutil.dll mfc90.dll MSVCP90.dll MSVCR90.dll POWRPROF.dll python26.dll pythoncom26.dll PyWinTypes26.dll qtaccessiblecompatwidgets4.dll qtaccessiblewidgets4.dll qcncodecs4.dll qjpcodecs4.dll qkrcodecs4.dll qtwcodecs4.dll qsvgicon4.dll qgif4.dll qico4.dll qjpeg4.dll qmng4.dll qsvg4.dll qtiff4.dll qt4_plugins QtCore4.dll QtGui4.dll SHLWAPI.dll urlmon.dll msvcm90.dll"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
<ROW File="MSVCP90.dll" Component_="MSVCP90.dll" FileName="MSVCP90.dll" Attributes="0" SourcePath="dist\MSVCP90.dll" SelfReg="false" Sequence="6"/>
<ROW File="MSVCR90.dll" Component_="MSVCR90.dll" FileName="MSVCR90.dll" Attributes="0" SourcePath="dist\MSVCR90.dll" SelfReg="false" Sequence="7"/>
<ROW File="Microsoft.VC90.CRT.manifest" Component_="bz2.pyd_1" FileName="Micros~1.man|Microsoft.VC90.CRT.manifest" Attributes="0" SourcePath="dist\Microsoft.VC90.CRT.manifest" SelfReg="false" Sequence="45"/>
<ROW File="POWRPROF.dll" Component_="POWRPROF.dll" FileName="POWRPROF.dll" Attributes="0" SourcePath="dist\POWRPROF.dll" SelfReg="false" Sequence="8"/>
<ROW File="PyQt4.QtCore.pyd" Component_="bz2.pyd_1" FileName="PyQt4Q~1.pyd|PyQt4.QtCore.pyd" Attributes="0" SourcePath="dist\PyQt4.QtCore.pyd" SelfReg="false" Sequence="10"/>
<ROW File="PyQt4.QtGui.pyd" Component_="bz2.pyd_1" FileName="PyQt4Q~2.pyd|PyQt4.QtGui.pyd" Attributes="0" SourcePath="dist\PyQt4.QtGui.pyd" SelfReg="false" Sequence="11"/>
<ROW File="PyWinTypes26.dll" Component_="PyWinTypes26.dll" FileName="PyWinT~1.dll|PyWinTypes26.dll" Attributes="0" SourcePath="dist\PyWinTypes26.dll" SelfReg="false" Sequence="14"/>
<ROW File="QtCore4.dll" Component_="QtCore4.dll" FileName="QtCore4.dll" Attributes="0" SourcePath="dist\QtCore4.dll" SelfReg="false" Sequence="28"/>
<ROW File="QtGui4.dll" Component_="QtGui4.dll" FileName="QtGui4.dll" Attributes="0" SourcePath="dist\QtGui4.dll" SelfReg="false" Sequence="29"/>
<ROW File="SHLWAPI.dll" Component_="SHLWAPI.dll" FileName="SHLWAPI.dll" Attributes="0" SourcePath="dist\SHLWAPI.dll" SelfReg="false" Sequence="31"/>
<ROW File="bz2.pyd" Component_="bz2.pyd_1" FileName="bz2.pyd" Attributes="0" SourcePath="dist\bz2.pyd" SelfReg="false" Sequence="3"/>
<ROW File="ctypes.pyd" Component_="bz2.pyd_1" FileName="_ctypes.pyd" Attributes="0" SourcePath="dist\_ctypes.pyd" SelfReg="false" Sequence="39"/>
<ROW File="dupeGuru_ME.exe" Component_="dupeGuru_ME.exe" FileName="dupeGu~1.exe|dupeGuru ME.exe" Attributes="0" SourcePath="dist\dupeGuru ME.exe" SelfReg="false" Sequence="2"/>
<ROW File="hashlib.pyd" Component_="bz2.pyd_1" FileName="_hashlib.pyd" Attributes="0" SourcePath="dist\_hashlib.pyd" SelfReg="false" Sequence="40"/>
<ROW File="iertutil.dll" Component_="iertutil.dll" FileName="iertutil.dll" Attributes="0" SourcePath="dist\iertutil.dll" SelfReg="false" Sequence="4"/>
<ROW File="mfc90.dll" Component_="mfc90.dll" FileName="mfc90.dll" Attributes="0" SourcePath="dist\mfc90.dll" SelfReg="false" Sequence="5"/>
<ROW File="msvcm90.dll" Component_="msvcm90.dll" FileName="msvcm90.dll" Attributes="0" SourcePath="dist\msvcm90.dll" SelfReg="false" Sequence="46"/>
<ROW File="multiprocessing.pyd" Component_="bz2.pyd_1" FileName="_multi~1.pyd|_multiprocessing.pyd" Attributes="0" SourcePath="dist\_multiprocessing.pyd" SelfReg="false" Sequence="41"/>
<ROW File="pyexpat.pyd" Component_="bz2.pyd_1" FileName="pyexpat.pyd" Attributes="0" SourcePath="dist\pyexpat.pyd" SelfReg="false" Sequence="9"/>
<ROW File="python26.dll" Component_="python26.dll" FileName="python26.dll" Attributes="0" SourcePath="dist\python26.dll" SelfReg="false" Sequence="12"/>
<ROW File="pythoncom26.dll" Component_="pythoncom26.dll" FileName="python~1.dll|pythoncom26.dll" Attributes="0" SourcePath="dist\pythoncom26.dll" SelfReg="false" Sequence="13"/>
<ROW File="qcncodecs4.dll" Component_="qcncodecs4.dll" FileName="qcncod~1.dll|qcncodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qcncodecs4.dll" SelfReg="false" Sequence="17"/>
<ROW File="qgif4.dll" Component_="qgif4.dll" FileName="qgif4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qgif4.dll" SelfReg="false" Sequence="22"/>
<ROW File="qico4.dll" Component_="qico4.dll" FileName="qico4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qico4.dll" SelfReg="false" Sequence="23"/>
<ROW File="qjpcodecs4.dll" Component_="qjpcodecs4.dll" FileName="qjpcod~1.dll|qjpcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qjpcodecs4.dll" SelfReg="false" Sequence="18"/>
<ROW File="qjpeg4.dll" Component_="qjpeg4.dll" FileName="qjpeg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qjpeg4.dll" SelfReg="false" Sequence="24"/>
<ROW File="qkrcodecs4.dll" Component_="qkrcodecs4.dll" FileName="qkrcod~1.dll|qkrcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qkrcodecs4.dll" SelfReg="false" Sequence="19"/>
<ROW File="qmng4.dll" Component_="qmng4.dll" FileName="qmng4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qmng4.dll" SelfReg="false" Sequence="25"/>
<ROW File="qsvg4.dll" Component_="qsvg4.dll" FileName="qsvg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qsvg4.dll" SelfReg="false" Sequence="26"/>
<ROW File="qsvgicon4.dll" Component_="qsvgicon4.dll" FileName="qsvgic~1.dll|qsvgicon4.dll" Attributes="0" SourcePath="dist\qt4_plugins\iconengines\qsvgicon4.dll" SelfReg="false" Sequence="21"/>
<ROW File="qtaccessiblecompatwidgets4.dll" Component_="qtaccessiblecompatwidgets4.dll" FileName="qtacce~1.dll|qtaccessiblecompatwidgets4.dll" Attributes="0" SourcePath="dist\qt4_plugins\accessible\qtaccessiblecompatwidgets4.dll" SelfReg="false" Sequence="15"/>
<ROW File="qtaccessiblewidgets4.dll" Component_="qtaccessiblewidgets4.dll" FileName="qtacce~2.dll|qtaccessiblewidgets4.dll" Attributes="0" SourcePath="dist\qt4_plugins\accessible\qtaccessiblewidgets4.dll" SelfReg="false" Sequence="16"/>
<ROW File="qtiff4.dll" Component_="qtiff4.dll" FileName="qtiff4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qtiff4.dll" SelfReg="false" Sequence="27"/>
<ROW File="qtwcodecs4.dll" Component_="qtwcodecs4.dll" FileName="qtwcod~1.dll|qtwcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qtwcodecs4.dll" SelfReg="false" Sequence="20"/>
<ROW File="select.pyd" Component_="bz2.pyd_1" FileName="select.pyd" Attributes="0" SourcePath="dist\select.pyd" SelfReg="false" Sequence="30"/>
<ROW File="sip.pyd" Component_="bz2.pyd_1" FileName="sip.pyd" Attributes="0" SourcePath="dist\sip.pyd" SelfReg="false" Sequence="32"/>
<ROW File="socket.pyd" Component_="bz2.pyd_1" FileName="_socket.pyd" Attributes="0" SourcePath="dist\_socket.pyd" SelfReg="false" Sequence="42"/>
<ROW File="ssl.pyd" Component_="bz2.pyd_1" FileName="_ssl.pyd" Attributes="0" SourcePath="dist\_ssl.pyd" SelfReg="false" Sequence="43"/>
<ROW File="unicodedata.pyd" Component_="bz2.pyd_1" FileName="unicod~1.pyd|unicodedata.pyd" Attributes="0" SourcePath="dist\unicodedata.pyd" SelfReg="false" Sequence="33"/>
<ROW File="updater.exe" Component_="updater.exe" FileName="updater.exe" Attributes="0" SourcePath="&lt;updater.exe&gt;" SelfReg="false" Sequence="1" DigSign="true"/>
<ROW File="urlmon.dll" Component_="urlmon.dll" FileName="urlmon.dll" Attributes="0" SourcePath="dist\urlmon.dll" SelfReg="false" Sequence="34"/>
<ROW File="win32api.pyd" Component_="bz2.pyd_1" FileName="win32api.pyd" Attributes="0" SourcePath="dist\win32api.pyd" SelfReg="false" Sequence="35"/>
<ROW File="win32com.shell.shell.pyd" Component_="bz2.pyd_1" FileName="win32c~1.pyd|win32com.shell.shell.pyd" Attributes="0" SourcePath="dist\win32com.shell.shell.pyd" SelfReg="false" Sequence="36"/>
<ROW File="win32sysloader.pyd" Component_="bz2.pyd_1" FileName="_win32~1.pyd|_win32sysloader.pyd" Attributes="0" SourcePath="dist\_win32sysloader.pyd" SelfReg="false" Sequence="44"/>
<ROW File="win32trace.pyd" Component_="bz2.pyd_1" FileName="win32t~1.pyd|win32trace.pyd" Attributes="0" SourcePath="dist\win32trace.pyd" SelfReg="false" Sequence="37"/>
<ROW File="win32ui.pyd" Component_="bz2.pyd_1" FileName="win32ui.pyd" Attributes="0" SourcePath="dist\win32ui.pyd" SelfReg="false" Sequence="38"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;ui.ail&gt;"/>
<ROW Path="&lt;ui_en.ail&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="FolderDlg.aip" Path="&lt;FolderDlg.aip&gt;"/>
<ROW Fragment="ShortcutsDlg.aip" Path="&lt;ShortcutsDlg.aip&gt;"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;StaticUIStrings.aip&gt;"/>
<ROW Fragment="UI.aip" Path="&lt;UI.aip&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="aicustact.dll" SourcePath="&lt;aicustact.dll&gt;"/>
<ROW Name="default_banner.bmp" SourcePath="&lt;default-banner.bmp&gt;"/>
<ROW Name="default_dialog.bmp" SourcePath="&lt;default-dialog.bmp&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="qt4_plugins_DIR" Component_="qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru_ME.exe]"/>
<ROW Action="AI_PREPARE_UPGRADE" Type="1" Source="aicustact.dll" Target="PrepareUpgrade"/>
<ROW Action="AI_RESTORE_LOCATION" Type="1" Source="aicustact.dll" Target="RestoreLocation"/>
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
<ROW Action="AI_UPDATER_UNINSTALL" Type="18" Source="updater.exe" Target="/clean silent"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="SystemFolder_msiexec.exe" SourcePath="&lt;uninstall.ico&gt;" Index="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIniFileComponent">
<ROW IniFile="AppDir" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="AppDir" Value="[APPDIR]" Action="0" Component_="updater.exe"/>
<ROW IniFile="ApplicationName" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="ApplicationName" Value="[ProductName]" Action="0" Component_="updater.exe"/>
<ROW IniFile="CheckFrequency" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="CheckFrequency" Value="2" Action="0" Component_="updater.exe"/>
<ROW IniFile="CompanyName" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="CompanyName" Value="[Manufacturer]" Action="0" Component_="updater.exe"/>
<ROW IniFile="DownloadsFolder" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="DownloadsFolder" Value="[AppDataFolder][Manufacturer]\[ProductName]\updates\" Action="0" Component_="updater.exe"/>
<ROW IniFile="ID" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="ID" Value="[UpgradeCode]" Action="0" Component_="updater.exe"/>
<ROW IniFile="URL" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="URL" Value="http://www.hardcoded.net/updates/dupeguru_me.aiu" Action="0" Component_="updater.exe"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel &lt;&gt; 5)" Sequence="210"/>
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE=&quot;No&quot; AND (Not Installed)" Sequence="1300"/>
<ROW Action="AI_UPDATER_UNINSTALL" Condition="($updater.exe = 2) AND (?updater.exe = 3) AND NOT (UPGRADINGPRODUCTCODE)" Sequence="1549"/>
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE=&quot;ALL&quot;)" Sequence="1449"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="Version9X OR VersionNT64 OR (VersionNT &gt;= 500 )" Description="[ProductName] can not be installed on systems earlier than [WindowsTypeNT]"/>
<ROW Condition="VersionNT" Description="[ProductName] can not be installed on [WindowsFamily9X]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiMediaComponent">
<ATTRIBUTE name="CabsLocation" value="1"/>
<ATTRIBUTE name="Compress" value="1"/>
<ATTRIBUTE name="CreateMd5" value="true"/>
<ATTRIBUTE name="InstallationType" value="4"/>
<ATTRIBUTE name="Package" value="6"/>
<ATTRIBUTE name="PackageName" value="install\dupeguru_me_win_[|ProductVersion]"/>
<ATTRIBUTE name="UseLargeSchema" value="true"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
<ROW Shortcut="Check_for_update" Directory_="SHORTCUTDIR" Name="Checkf~2|Check for update" Component_="updater.exe" Target="[#updater.exe]" Arguments="/checknow" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="Uninstall_dupeGuru_ME" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru ME" Component_="qtwcodecs4.dll" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
<ROW Shortcut="dupeGuru_ME" Directory_="SHORTCUTDIR" Name="dupeGu~1|dupeGuru ME" Component_="dupeGuru_ME.exe" Target="[#dupeGuru_ME.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="dupeGuru_ME_1" Directory_="DesktopFolder" Name="dupeGu~1|dupeGuru ME" Component_="dupeGuru_ME.exe" Target="[#dupeGuru_ME.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqComponent">
<ROW PrereqKey="0" DisplayName="Visual C++ 2008 SP1 Redistributable" SetupFileUrl="http://download.hardcoded.net/vcredist_90sp1_x86.exe" Location="1" ExactSize="4216840" MinWin9xVer="37" MinWinNTVer="17" Operator="1" ComLine="/q" Sequence="1" MD5="5689d43c3b201dd3810fa3bba4a6476a"/>
<ATTRIBUTE name="ExtractionFolder" value="[AppDataFolder][|Manufacturer]\[|ProductName]\install"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqSearchComponent">
<ROW Prereq="0" SearchType="9" SearchString="HKLM\SOFTWARE\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033\SP" RefContent="M1" Order="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
<ROW Directory_="APPDIR" SourcePath="dist" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store|*.pdb|*.vshost.*" ExcludeFlags="6"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.UpdaterComponent">
<ROW Updater="updater.exe" URL="URL" SearchFreq="CheckFrequency" DownloadsFolder="DownloadsFolder" ID="ID" TargetDir="AppDir" AppName="ApplicationName" CompanyName="CompanyName" UnistallCASeq="AI_UPDATER_UNINSTALL"/>
</COMPONENT>
</DOCUMENT>

72
qt/me/preferences.py Normal file
View File

@@ -0,0 +1,72 @@
# 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 core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
from base.preferences import Preferences as PreferencesBase
class Preferences(PreferencesBase):
# (width, is_visible)
COLUMNS_DEFAULT_ATTRS = [
(200, True), # name
(180, True), # path
(60, True), # size
(60, True), # Time
(50, True), # Bitrate
(60, False), # Sample Rate
(40, False), # Kind
(120, False), # creation
(120, False), # modification
(120, False), # Title
(120, False), # Artist
(120, False), # Album
(80, False), # Genre
(40, False), # Year
(40, False), # Track Number
(120, False), # Comment
(60, True), # match %
(120, False), # Words Used
(80, False), # dupe count
]
def _load_specific(self, settings, get):
self.scan_type = get('ScanType', self.scan_type)
self.word_weighting = get('WordWeighting', self.word_weighting)
self.match_similar = get('MatchSimilar', self.match_similar)
self.scan_tag_track = get('ScanTagTrack', self.scan_tag_track)
self.scan_tag_artist = get('ScanTagArtist', self.scan_tag_artist)
self.scan_tag_album = get('ScanTagAlbum', self.scan_tag_album)
self.scan_tag_title = get('ScanTagTitle', self.scan_tag_title)
self.scan_tag_genre = get('ScanTagGenre', self.scan_tag_genre)
self.scan_tag_year = get('ScanTagYear', self.scan_tag_year)
def _reset_specific(self):
self.filter_hardness = 80
self.scan_type = SCAN_TYPE_TAG
self.word_weighting = True
self.match_similar = False
self.scan_tag_track = False
self.scan_tag_artist = True
self.scan_tag_album = True
self.scan_tag_title = True
self.scan_tag_genre = False
self.scan_tag_year = False
def _save_specific(self, settings, set_):
set_('ScanType', self.scan_type)
set_('WordWeighting', self.word_weighting)
set_('MatchSimilar', self.match_similar)
set_('ScanTagTrack', self.scan_tag_track)
set_('ScanTagArtist', self.scan_tag_artist)
set_('ScanTagAlbum', self.scan_tag_album)
set_('ScanTagTitle', self.scan_tag_title)
set_('ScanTagGenre', self.scan_tag_genre)
set_('ScanTagYear', self.scan_tag_year)

103
qt/me/preferences_dialog.py Normal file
View File

@@ -0,0 +1,103 @@
# Created By: Virgil Dupras
# Created On: 2009-04-29
# $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, QDialogButtonBox
from core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
from preferences_dialog_ui import Ui_PreferencesDialog
import preferences
SCAN_TYPE_ORDER = [
SCAN_TYPE_FILENAME,
SCAN_TYPE_FIELDS,
SCAN_TYPE_FIELDS_NO_ORDER,
SCAN_TYPE_TAG,
SCAN_TYPE_CONTENT,
SCAN_TYPE_CONTENT_AUDIO,
]
class PreferencesDialog(QDialog, Ui_PreferencesDialog):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
self.connect(self.scanTypeComboBox, SIGNAL('currentIndexChanged(int)'), self.scanTypeChanged)
def _setupUi(self):
self.setupUi(self)
def load(self, prefs=None):
if prefs is None:
prefs = self.app.prefs
self.filterHardnessSlider.setValue(prefs.filter_hardness)
self.filterHardnessLabel.setNum(prefs.filter_hardness)
scan_type_index = SCAN_TYPE_ORDER.index(prefs.scan_type)
self.scanTypeComboBox.setCurrentIndex(scan_type_index)
setchecked = lambda cb, b: cb.setCheckState(Qt.Checked if b else Qt.Unchecked)
setchecked(self.tagTrackBox, prefs.scan_tag_track)
setchecked(self.tagArtistBox, prefs.scan_tag_artist)
setchecked(self.tagAlbumBox, prefs.scan_tag_album)
setchecked(self.tagTitleBox, prefs.scan_tag_title)
setchecked(self.tagGenreBox, prefs.scan_tag_genre)
setchecked(self.tagYearBox, prefs.scan_tag_year)
setchecked(self.matchSimilarBox, prefs.match_similar)
setchecked(self.wordWeightingBox, prefs.word_weighting)
setchecked(self.mixFileKindBox, prefs.mix_file_kind)
setchecked(self.useRegexpBox, prefs.use_regexp)
setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type)
def save(self):
prefs = self.app.prefs
prefs.filter_hardness = self.filterHardnessSlider.value()
prefs.scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()]
ischecked = lambda cb: cb.checkState() == Qt.Checked
prefs.scan_tag_track = ischecked(self.tagTrackBox)
prefs.scan_tag_artist = ischecked(self.tagArtistBox)
prefs.scan_tag_album = ischecked(self.tagAlbumBox)
prefs.scan_tag_title = ischecked(self.tagTitleBox)
prefs.scan_tag_genre = ischecked(self.tagGenreBox)
prefs.scan_tag_year = ischecked(self.tagYearBox)
prefs.match_similar = ischecked(self.matchSimilarBox)
prefs.word_weighting = ischecked(self.wordWeightingBox)
prefs.mix_file_kind = ischecked(self.mixFileKindBox)
prefs.use_regexp = ischecked(self.useRegexpBox)
prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
def resetToDefaults(self):
self.load(preferences.Preferences())
#--- Events
def buttonClicked(self, button):
role = self.buttonBox.buttonRole(button)
if role == QDialogButtonBox.ResetRole:
self.resetToDefaults()
def scanTypeChanged(self, index):
scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()]
word_based = scan_type in [SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
SCAN_TYPE_TAG]
tag_based = scan_type == SCAN_TYPE_TAG
self.filterHardnessSlider.setEnabled(word_based)
self.matchSimilarBox.setEnabled(word_based)
self.wordWeightingBox.setEnabled(word_based)
self.tagTrackBox.setEnabled(tag_based)
self.tagArtistBox.setEnabled(tag_based)
self.tagAlbumBox.setEnabled(tag_based)
self.tagTitleBox.setEnabled(tag_based)
self.tagGenreBox.setEnabled(tag_based)
self.tagYearBox.setEnabled(tag_based)

434
qt/me/preferences_dialog.ui Normal file
View File

@@ -0,0 +1,434 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PreferencesDialog</class>
<widget class="QDialog" name="PreferencesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>322</height>
</rect>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,0,0,0,0,1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Scan Type:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="scanTypeComboBox">
<item>
<property name="text">
<string>Filename</string>
</property>
</item>
<item>
<property name="text">
<string>Filename - Fields</string>
</property>
</item>
<item>
<property name="text">
<string>Filename - Fields (No Order)</string>
</property>
</item>
<item>
<property name="text">
<string>Tags</string>
</property>
</item>
<item>
<property name="text">
<string>Contents</string>
</property>
</item>
<item>
<property name="text">
<string>Audio Contents</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Filter Hardness:</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QSlider" name="filterHardnessSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="tracking">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterHardnessLabel">
<property name="minimumSize">
<size>
<width>21</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>100</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>More Results</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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="QLabel" name="label_3">
<property name="text">
<string>Less Results</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>91</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Tags to scan:</string>
</property>
</widget>
<widget class="QCheckBox" name="tagTrackBox">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Track</string>
</property>
</widget>
<widget class="QCheckBox" name="tagArtistBox">
<property name="geometry">
<rect>
<x>60</x>
<y>20</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Artist</string>
</property>
</widget>
<widget class="QCheckBox" name="tagAlbumBox">
<property name="geometry">
<rect>
<x>110</x>
<y>20</y>
<width>61</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Album</string>
</property>
</widget>
<widget class="QCheckBox" name="tagTitleBox">
<property name="geometry">
<rect>
<x>170</x>
<y>20</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Title</string>
</property>
</widget>
<widget class="QCheckBox" name="tagGenreBox">
<property name="geometry">
<rect>
<x>220</x>
<y>20</y>
<width>61</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Genre</string>
</property>
</widget>
<widget class="QCheckBox" name="tagYearBox">
<property name="geometry">
<rect>
<x>280</x>
<y>20</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Year</string>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QCheckBox" name="wordWeightingBox">
<property name="text">
<string>Word weighting</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="matchSimilarBox">
<property name="text">
<string>Match similar words</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mixFileKindBox">
<property name="text">
<string>Can mix file kind</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useRegexpBox">
<property name="text">
<string>Use regular expressions when filtering</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="removeEmptyFoldersBox">
<property name="text">
<string>Remove empty folders on delete or move</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Copy and Move:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="copyMoveDestinationComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Right in destination</string>
</property>
</item>
<item>
<property name="text">
<string>Recreate relative path</string>
</property>
</item>
<item>
<property name="text">
<string>Recreate absolute path</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>filterHardnessSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>filterHardnessLabel</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>26</y>
</hint>
<hint type="destinationlabel">
<x>271</x>
<y>26</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PreferencesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>124</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PreferencesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>124</y>
</hint>
</hints>
</connection>
</connections>
</ui>

27
qt/me/profile.py Normal file
View File

@@ -0,0 +1,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 sys
import cProfile
import pstats
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication
if sys.platform == 'win32':
from app_win import DupeGuru
else:
from app import DupeGuru
if __name__ == "__main__":
app = QApplication(sys.argv)
QCoreApplication.setOrganizationName('Hardcoded Software')
QCoreApplication.setApplicationName('dupeGuru Music Edition')
dgapp = DupeGuru()
cProfile.run('app.exec_()', '/tmp/prof')
p = pstats.Stats('/tmp/prof')
p.sort_stats('time').print_stats()

24
qt/me/start.py Normal file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
# 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 sys
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication, QIcon, QPixmap
import base.dg_rc
from app import DupeGuru
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(QPixmap(":/logo_me")))
QCoreApplication.setOrganizationName('Hardcoded Software')
QCoreApplication.setApplicationName(DupeGuru.NAME)
QCoreApplication.setApplicationVersion(DupeGuru.VERSION)
dgapp = DupeGuru()
sys.exit(app.exec_())

28
qt/me/verinfo Normal file
View File

@@ -0,0 +1,28 @@
VSVersionInfo(
ffi=FixedFileInfo(
filevers=($versioncomma),
prodvers=($versioncomma),
mask=0x17,
flags=0x0,
OS=0x4,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
'040904b0',
[StringStruct('CompanyName', 'Hardcoded Software'),
StringStruct('FileDescription', 'dupeGuru Music Edition'),
StringStruct('FileVersion', '$version'),
StringStruct('InternalName', 'dupeGuru ME.exe'),
StringStruct('LegalCopyright', '(c) Hardcoded Software. All rights reserved.'),
StringStruct('OriginalFilename', 'dupeGuru ME.exe'),
StringStruct('ProductName', 'dupeGuru Music Edition'),
StringStruct('ProductVersion', '$versioncomma')])
]),
VarFileInfo([VarStruct('Translation', [1033])])
]
)

88
qt/pe/app.py Normal file
View File

@@ -0,0 +1,88 @@
# 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 os.path as op
import logging
from PyQt4.QtGui import QImage
import PIL.Image
from hsutil.str import get_file_ext
from core import fs
from core_pe import data as data_pe
from core_pe.cache import Cache
from core_pe.scanner import ScannerPE
from block import getblocks
from base.app import DupeGuru as DupeGuruBase
from details_dialog import DetailsDialog
from main_window import MainWindow
from preferences import Preferences
from preferences_dialog import PreferencesDialog
class File(fs.File):
INITIAL_INFO = fs.File.INITIAL_INFO.copy()
INITIAL_INFO.update({
'dimensions': (0,0),
})
HANDLED_EXTS = set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'tif'])
@classmethod
def can_handle(cls, path):
return fs.File.can_handle(path) and get_file_ext(path[-1]) in cls.HANDLED_EXTS
def _read_info(self, field):
fs.File._read_info(self, field)
if field == 'dimensions':
try:
im = PIL.Image.open(unicode(self.path))
self.dimensions = im.size
except IOError:
self.dimensions = (0, 0)
logging.warning(u"Could not read image '%s'", unicode(self.path))
def get_blocks(self, block_count_per_side):
image = QImage(unicode(self.path))
image = image.convertToFormat(QImage.Format_RGB888)
return getblocks(image, block_count_per_side)
class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_pe'
NAME = 'dupeGuru Picture Edition'
VERSION = '1.8.0'
DELTA_COLUMNS = frozenset([2, 5, 6])
def __init__(self):
DupeGuruBase.__init__(self, data_pe, appid=5)
def _setup(self):
self.scanner = ScannerPE()
self.directories.fileclasses = [File]
self.scanner.cached_blocks = Cache(op.join(self.appdata, 'cached_pictures.db'))
DupeGuruBase._setup(self)
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.match_scaled = self.prefs.match_scaled
self.scanner.threshold = self.prefs.filter_hardness
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)
def _create_main_window(self):
return MainWindow(app=self)
def _create_preferences(self):
return Preferences()
def _create_preferences_dialog(self, parent):
return PreferencesDialog(parent, self)

43
qt/pe/block.py Normal file
View File

@@ -0,0 +1,43 @@
# Created By: Virgil Dupras
# Created On: 2009-05-10
# $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 _block import getblocks
# Converted to Cython
# def getblock(image):
# width = image.width()
# height = image.height()
# if width:
# pixel_count = width * height
# red = green = blue = 0
# s = image.bits().asstring(image.numBytes())
# for i in xrange(pixel_count):
# offset = i * 3
# red += ord(s[offset])
# green += ord(s[offset + 1])
# blue += ord(s[offset + 2])
# return (red // pixel_count, green // pixel_count, blue // pixel_count)
# else:
# return (0, 0, 0)
#
# def getblocks(image, block_count_per_side):
# width = image.width()
# height = image.height()
# if not width:
# return []
# block_width = max(width // block_count_per_side, 1)
# block_height = max(height // block_count_per_side, 1)
# result = []
# for ih in xrange(block_count_per_side):
# top = min(ih * block_height, height - block_height)
# for iw in range(block_count_per_side):
# left = min(iw * block_width, width - block_width)
# crop = image.copy(left, top, block_width, block_height)
# result.append(getblock(crop))
# return result

46
qt/pe/build.py Normal file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $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
# On Windows, PyInstaller is used to build an exe (py2exe creates a very bad looking icon
# The release version is outdated. Use at least r672 on http://svn.pyinstaller.org/trunk
import os
import os.path as op
import shutil
from app import DupeGuru
def print_and_do(cmd):
print cmd
os.system(cmd)
# Removing build and dist
if op.exists('build'):
shutil.rmtree('build')
if op.exists('dist'):
shutil.rmtree('dist')
version = DupeGuru.VERSION
versioncomma = version.replace('.', ', ') + ', 0'
verinfo = open('verinfo').read()
verinfo = verinfo.replace('$versioncomma', versioncomma).replace('$version', version)
fp = open('verinfo_tmp', 'w')
fp.write(verinfo)
fp.close()
print_and_do("python C:\\Python26\\pyinstaller\\Build.py dgpe.spec")
os.remove('verinfo_tmp')
print_and_do("del dist\\*90.dll") # They're in vcredist, no need to include them
print_and_do("xcopy /Y /S /I help\\dupeguru_pe_help dist\\help")
aicom = '"\\Program Files\\Caphyon\\Advanced Installer\\AdvancedInstaller.com"'
shutil.copy('installer.aip', 'installer_tmp.aip') # this is so we don'a have to re-commit installer.aip at every version change
print_and_do('%s /edit installer_tmp.aip /SetVersion %s' % (aicom, version))
print_and_do('%s /build installer_tmp.aip -force' % aicom)
os.remove('installer_tmp.aip')

67
qt/pe/details_dialog.py Normal file
View File

@@ -0,0 +1,67 @@
# Created By: Virgil Dupras
# Created On: 2009-04-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
from PyQt4.QtCore import Qt, SIGNAL, QAbstractTableModel, QVariant
from PyQt4.QtGui import QDialog, QHeaderView, QPixmap
from base.details_table import DetailsModel
from details_dialog_ui import Ui_DetailsDialog
class DetailsDialog(QDialog, Ui_DetailsDialog):
def __init__(self, parent, app):
QDialog.__init__(self, parent, Qt.Tool)
self.app = app
self.selectedPixmap = None
self.referencePixmap = None
self.setupUi(self)
self.model = DetailsModel(app)
self.tableView.setModel(self.model)
self.connect(app, SIGNAL('duplicateSelected()'), self.duplicateSelected)
def _update(self):
dupe = self.app.selected_dupe
if dupe is None:
return
group = self.app.results.get_group_of_duplicate(dupe)
ref = group.ref
self.selectedPixmap = QPixmap(unicode(dupe.path))
if ref is dupe:
self.referencePixmap = None
else:
self.referencePixmap = QPixmap(unicode(ref.path))
self._updateImages()
def _updateImages(self):
if self.selectedPixmap is not None:
target_size = self.selectedImage.size()
scaledPixmap = self.selectedPixmap.scaled(target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.selectedImage.setPixmap(scaledPixmap)
else:
self.selectedImage.setPixmap(QPixmap())
if self.referencePixmap is not None:
target_size = self.referenceImage.size()
scaledPixmap = self.referencePixmap.scaled(target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.referenceImage.setPixmap(scaledPixmap)
else:
self.referenceImage.setPixmap(QPixmap())
#--- Override
def resizeEvent(self, event):
self._updateImages()
def show(self):
QDialog.show(self)
self._update()
#--- Events
def duplicateSelected(self):
if self.isVisible():
self._update()

113
qt/pe/details_dialog.ui Normal file
View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DetailsDialog</class>
<widget class="QDialog" name="DetailsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>502</width>
<height>295</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>250</height>
</size>
</property>
<property name="windowTitle">
<string>Details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="selectedImage">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="referenceImage">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="DetailsTable" name="tableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>188</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>188</height>
</size>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DetailsTable</class>
<extends>QTableView</extends>
<header>base.details_table</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

19
qt/pe/dgpe.spec Normal file
View File

@@ -0,0 +1,19 @@
# -*- mode: python -*-
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'start.py'],
pathex=[])
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
exclude_binaries=1,
name=os.path.join('build\\pyi.win32\\dupeGuru PE', 'dupeGuru PE.exe'),
debug=False,
strip=False,
upx=True,
console=False , icon='..\\base\\images\\dgpe_logo.ico', version='verinfo_tmp')
coll = COLLECT( exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name=os.path.join('dist'))

35
qt/pe/gen.py Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $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 os
import os.path as op
from hsutil.build import print_and_do, build_all_qt_ui
build_all_qt_ui(op.join('..', '..', 'qtlib', 'ui'))
build_all_qt_ui(op.join('..', 'base'))
build_all_qt_ui('.')
print_and_do("pyrcc4 {0} > {1}".format(op.join('..', 'base', 'dg.qrc'), op.join('..', 'base', 'dg_rc.py')))
def move(src, dst):
if not op.exists(src):
return
if op.exists(dst):
os.remove(dst)
print 'Moving %s --> %s' % (src, dst)
os.rename(src, dst)
# The CC=gcc-4.0 thing is because, in Snow Leopard, gcc-4.2 can't compile these units.
os.environ['CC'] = 'gcc-4.0'
os.chdir(op.join('modules', 'block'))
os.system('python setup.py build_ext --inplace')
os.chdir(op.join('..', '..'))
move(op.join('modules', 'block', '_block.so'), op.join('.', '_block.so'))
move(op.join('modules', 'block', '_block.pyd'), op.join('.', '_block.pyd'))

253
qt/pe/installer.aip Normal file
View File

@@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT type="Advanced Installer" CreateVersion="4.7.2" version="4.9.2" modules="professional" RootPath="." Language="en">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_DESKTOP_SH" Value="1" Type="4"/>
<ROW Property="AI_QUICKLAUNCH_SH" Value="1" Type="4"/>
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
<ROW Property="AI_STARTMENU_SH" Value="1" Type="4"/>
<ROW Property="AI_STARTUP_SH" Value="1" Type="4"/>
<ROW Property="ALLUSERS" Value="2"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru_pe/"/>
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru_pe/"/>
<ROW Property="BannerBitmap" Value="default_banner.bmp" Type="1"/>
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="default_dialog.bmp" Type="1"/>
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
<ROW Property="ProductCode" Value="1033:{189C7FAD-CA63-4A56-B592-B68C34889265} "/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="dupeGuru Picture Edition" ValueLocId="*"/>
<ROW Property="ProductVersion" Value="1.7.0"/>
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
<ROW Property="UpgradeCode" Value="{B1E28F97-9CE2-45E2-B19D-C4137F4DEB85}"/>
<ROW Property="WindowsFamily9X" Value="Windows 9x/ME"/>
<ROW Property="WindowsTypeNT" Value="Windows 2000"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
<ROW Directory="accessible_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="access~1|accessible"/>
<ROW Directory="codecs_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="codecs"/>
<ROW Directory="iconengines_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="iconen~1|iconengines"/>
<ROW Directory="imageformats_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="imagef~1|imageformats"/>
<ROW Directory="qt4_plugins_DIR" Directory_Parent="APPDIR" DefaultDir="qt4_pl~1|qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
<ROW Component="AIShRegAnswer" ComponentId="{F315B3C7-7C86-41EB-BE7D-2A6A8E3073B4}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer" FullKeyPath="HK_UM\Software\Caphyon\Advanced Installer\Installs\[ProductCode]\AIShRegAnswer"/>
<ROW Component="MSVCP90.dll" ComponentId="{6AF180E0-48A2-4EF9-B97E-D556D9AF58F6}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCP90.dll" FullKeyPath="APPDIR\MSVCP90.dll"/>
<ROW Component="MSVCR90.dll" ComponentId="{259F168B-9F1C-41BC-90D2-A607E5BCDFE9}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCR90.dll" FullKeyPath="APPDIR\MSVCR90.dll"/>
<ROW Component="POWRPROF.dll" ComponentId="{65828A9D-101E-433C-82E5-11290FDB7054}" Directory_="APPDIR" Attributes="0" KeyPath="POWRPROF.dll" FullKeyPath="APPDIR\POWRPROF.dll"/>
<ROW Component="PyWinTypes26.dll" ComponentId="{51E80B3A-C753-48CB-B7A4-65610CC49E1F}" Directory_="APPDIR" Attributes="0" KeyPath="PyWinTypes26.dll" FullKeyPath="APPDIR\PyWinTypes26.dll"/>
<ROW Component="QtCore4.dll" ComponentId="{5BB6B87D-BE40-4240-B529-23A303942E18}" Directory_="APPDIR" Attributes="0" KeyPath="QtCore4.dll" FullKeyPath="APPDIR\QtCore4.dll"/>
<ROW Component="QtGui4.dll" ComponentId="{EF519048-F252-4FA0-9875-A6B45C7F3020}" Directory_="APPDIR" Attributes="0" KeyPath="QtGui4.dll" FullKeyPath="APPDIR\QtGui4.dll"/>
<ROW Component="SHLWAPI.dll" ComponentId="{11CA10DE-F6AF-4EC7-B5B9-EE1E9D08A7B0}" Directory_="APPDIR" Attributes="0" KeyPath="SHLWAPI.dll" FullKeyPath="APPDIR\SHLWAPI.dll"/>
<ROW Component="SHORTCUTDIR" ComponentId="{29E7E841-7820-418B-8542-7F8CCC9777A8}" Directory_="SHORTCUTDIR" Attributes="0"/>
<ROW Component="bz2.pyd" ComponentId="{F7FDAE87-233A-45B0-8976-021B8264E176}" Directory_="APPDIR" Attributes="0" KeyPath="bz2.pyd" FullKeyPath="APPDIR"/>
<ROW Component="dupeGuru_PE.exe" ComponentId="{4A31F2AE-F42E-4B0F-BC4D-A09F312D469B}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru_PE.exe" FullKeyPath="APPDIR\dupeGuru PE.exe"/>
<ROW Component="iertutil.dll" ComponentId="{4FFBD285-2236-4934-B36C-17392E58B705}" Directory_="APPDIR" Attributes="0" KeyPath="iertutil.dll" FullKeyPath="APPDIR\iertutil.dll"/>
<ROW Component="mfc90.dll" ComponentId="{FC178BE2-B309-4E92-A78C-FE7F5DC8705B}" Directory_="APPDIR" Attributes="0" KeyPath="mfc90.dll" FullKeyPath="APPDIR\mfc90.dll"/>
<ROW Component="msvcm90.dll" ComponentId="{443BA602-0F58-439A-A092-4314423AEE4B}" Directory_="APPDIR" Attributes="0" KeyPath="msvcm90.dll" FullKeyPath="APPDIR\msvcm90.dll"/>
<ROW Component="python26.dll" ComponentId="{20529423-B717-4C53-AE3A-B82E53207A62}" Directory_="APPDIR" Attributes="0" KeyPath="python26.dll" FullKeyPath="APPDIR\python26.dll"/>
<ROW Component="pythoncom26.dll" ComponentId="{46CE0516-582F-44F0-86D7-7F2F5F7458C3}" Directory_="APPDIR" Attributes="0" KeyPath="pythoncom26.dll" FullKeyPath="APPDIR\pythoncom26.dll"/>
<ROW Component="qcncodecs4.dll" ComponentId="{629EB310-99C7-4304-91DB-9134E521A83F}" Directory_="codecs_DIR" Attributes="0" KeyPath="qcncodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qcncodecs4.dll"/>
<ROW Component="qgif4.dll" ComponentId="{788DB84F-6216-4334-B92E-9992FD02A31A}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qgif4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qgif4.dll"/>
<ROW Component="qico4.dll" ComponentId="{CED777C8-E8F6-421F-B533-91A7B5310BCF}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qico4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qico4.dll"/>
<ROW Component="qjpcodecs4.dll" ComponentId="{9A112855-BA4A-4C41-BCA0-20D564A2A7C2}" Directory_="codecs_DIR" Attributes="0" KeyPath="qjpcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qjpcodecs4.dll"/>
<ROW Component="qjpeg4.dll" ComponentId="{0FC6A53E-6DDC-4833-944B-00944A45FB62}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qjpeg4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qjpeg4.dll"/>
<ROW Component="qkrcodecs4.dll" ComponentId="{5EE53027-96D1-4ABA-A2F6-507A7D85EA44}" Directory_="codecs_DIR" Attributes="0" KeyPath="qkrcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qkrcodecs4.dll"/>
<ROW Component="qmng4.dll" ComponentId="{5E3A9C7B-F545-4EF5-B083-2A29E54784BE}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qmng4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qmng4.dll"/>
<ROW Component="qsvg4.dll" ComponentId="{590998F6-2A55-48DC-A83A-AABC75EF7F99}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qsvg4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qsvg4.dll"/>
<ROW Component="qsvgicon4.dll" ComponentId="{0440300E-1BAE-49C5-8575-AF43C2557271}" Directory_="iconengines_DIR" Attributes="0" KeyPath="qsvgicon4.dll" FullKeyPath="APPDIR\qt4_plugins\iconengines\qsvgicon4.dll"/>
<ROW Component="qt4_plugins" ComponentId="{8A139B39-B8E2-4B26-87A7-13F472D0F851}" Directory_="qt4_plugins_DIR" Attributes="0"/>
<ROW Component="qtaccessiblecompatwidgets4.dll" ComponentId="{FD6B5926-8D27-4308-BED4-1520AAE7D65E}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblecompatwidgets4.dll" FullKeyPath="APPDIR\qt4_plugins\accessible\qtaccessiblecompatwidgets4.dll"/>
<ROW Component="qtaccessiblewidgets4.dll" ComponentId="{74F17E1E-D2BC-4C85-A5B9-A093D4AFC840}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblewidgets4.dll" FullKeyPath="APPDIR\qt4_plugins\accessible\qtaccessiblewidgets4.dll"/>
<ROW Component="qtiff4.dll" ComponentId="{545FB567-9E3A-4E44-85A0-74446ABB8FC8}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qtiff4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qtiff4.dll"/>
<ROW Component="qtwcodecs4.dll" ComponentId="{59FB6978-93A0-4DD6-A617-FE6460CDE4DB}" Directory_="codecs_DIR" Attributes="0" KeyPath="qtwcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qtwcodecs4.dll"/>
<ROW Component="sqlite3.dll" ComponentId="{B5D4B746-A65D-4F4A-B31A-D1C3C7F59952}" Directory_="APPDIR" Attributes="0" KeyPath="sqlite3.dll" FullKeyPath="APPDIR\sqlite3.dll"/>
<ROW Component="updater.exe" ComponentId="{D1DDB6CB-B336-4112-BC40-1ABD36C3ABDA}" Directory_="APPDIR" Attributes="0" KeyPath="updater.exe" FullKeyPath="APPDIR\updater.exe"/>
<ROW Component="urlmon.dll" ComponentId="{53BDE31D-455B-42B7-A544-079DF4468BEC}" Directory_="APPDIR" Attributes="0" KeyPath="urlmon.dll" FullKeyPath="APPDIR\urlmon.dll"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="updater.exe dupeGuru_PE.exe AIShRegAnswer SHORTCUTDIR bz2.pyd iertutil.dll mfc90.dll msvcm90.dll MSVCP90.dll MSVCR90.dll POWRPROF.dll python26.dll pythoncom26.dll PyWinTypes26.dll qtaccessiblecompatwidgets4.dll qtaccessiblewidgets4.dll qcncodecs4.dll qjpcodecs4.dll qkrcodecs4.dll qtwcodecs4.dll qsvgicon4.dll qgif4.dll qico4.dll qjpeg4.dll qmng4.dll qsvg4.dll qtiff4.dll qt4_plugins QtCore4.dll QtGui4.dll SHLWAPI.dll sqlite3.dll urlmon.dll"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
<ROW File="MSVCP90.dll" Component_="MSVCP90.dll" FileName="MSVCP90.dll" Attributes="0" SourcePath="dist\MSVCP90.dll" SelfReg="false" Sequence="10"/>
<ROW File="MSVCR90.dll" Component_="MSVCR90.dll" FileName="MSVCR90.dll" Attributes="0" SourcePath="dist\MSVCR90.dll" SelfReg="false" Sequence="11"/>
<ROW File="Microsoft.VC90.CRT.manifest" Component_="bz2.pyd" FileName="Micros~1.man|Microsoft.VC90.CRT.manifest" Attributes="0" SourcePath="dist\Microsoft.VC90.CRT.manifest" SelfReg="false" Sequence="8"/>
<ROW File="PIL._imaging.pyd" Component_="bz2.pyd" FileName="PIL_im~1.pyd|PIL._imaging.pyd" Attributes="0" SourcePath="dist\PIL._imaging.pyd" SelfReg="false" Sequence="12"/>
<ROW File="POWRPROF.dll" Component_="POWRPROF.dll" FileName="POWRPROF.dll" Attributes="0" SourcePath="dist\POWRPROF.dll" SelfReg="false" Sequence="13"/>
<ROW File="PyQt4.QtCore.pyd" Component_="bz2.pyd" FileName="PyQt4Q~1.pyd|PyQt4.QtCore.pyd" Attributes="0" SourcePath="dist\PyQt4.QtCore.pyd" SelfReg="false" Sequence="15"/>
<ROW File="PyQt4.QtGui.pyd" Component_="bz2.pyd" FileName="PyQt4Q~2.pyd|PyQt4.QtGui.pyd" Attributes="0" SourcePath="dist\PyQt4.QtGui.pyd" SelfReg="false" Sequence="16"/>
<ROW File="PyWinTypes26.dll" Component_="PyWinTypes26.dll" FileName="PyWinT~1.dll|PyWinTypes26.dll" Attributes="0" SourcePath="dist\PyWinTypes26.dll" SelfReg="false" Sequence="19"/>
<ROW File="QtCore4.dll" Component_="QtCore4.dll" FileName="QtCore4.dll" Attributes="0" SourcePath="dist\QtCore4.dll" SelfReg="false" Sequence="33"/>
<ROW File="QtGui4.dll" Component_="QtGui4.dll" FileName="QtGui4.dll" Attributes="0" SourcePath="dist\QtGui4.dll" SelfReg="false" Sequence="34"/>
<ROW File="SHLWAPI.dll" Component_="SHLWAPI.dll" FileName="SHLWAPI.dll" Attributes="0" SourcePath="dist\SHLWAPI.dll" SelfReg="false" Sequence="36"/>
<ROW File="block.pyd" Component_="bz2.pyd" FileName="_block.pyd" Attributes="0" SourcePath="dist\_block.pyd" SelfReg="false" Sequence="45"/>
<ROW File="bz2.pyd" Component_="bz2.pyd" FileName="bz2.pyd" Attributes="0" SourcePath="dist\bz2.pyd" SelfReg="false" Sequence="3"/>
<ROW File="ctypes.pyd" Component_="bz2.pyd" FileName="_ctypes.pyd" Attributes="0" SourcePath="dist\_ctypes.pyd" SelfReg="false" Sequence="46"/>
<ROW File="dupeGuru_PE.exe" Component_="dupeGuru_PE.exe" FileName="dupeGu~2.exe|dupeGuru PE.exe" Attributes="0" SourcePath="dist\dupeGuru PE.exe" SelfReg="false" Sequence="2"/>
<ROW File="dupeguru.picture._block.pyd" Component_="bz2.pyd" FileName="dupegu~1.pyd|dupeguru.picture._block.pyd" Attributes="0" SourcePath="dist\dupeguru.picture._block.pyd" SelfReg="false" Sequence="4"/>
<ROW File="dupeguru.picture._cache.pyd" Component_="bz2.pyd" FileName="dupegu~2.pyd|dupeguru.picture._cache.pyd" Attributes="0" SourcePath="dist\dupeguru.picture._cache.pyd" SelfReg="false" Sequence="5"/>
<ROW File="hashlib.pyd" Component_="bz2.pyd" FileName="_hashlib.pyd" Attributes="0" SourcePath="dist\_hashlib.pyd" SelfReg="false" Sequence="47"/>
<ROW File="iertutil.dll" Component_="iertutil.dll" FileName="iertutil.dll" Attributes="0" SourcePath="dist\iertutil.dll" SelfReg="false" Sequence="6"/>
<ROW File="mfc90.dll" Component_="mfc90.dll" FileName="mfc90.dll" Attributes="0" SourcePath="dist\mfc90.dll" SelfReg="false" Sequence="7"/>
<ROW File="msvcm90.dll" Component_="msvcm90.dll" FileName="msvcm90.dll" Attributes="0" SourcePath="dist\msvcm90.dll" SelfReg="false" Sequence="9"/>
<ROW File="multiprocessing.pyd" Component_="bz2.pyd" FileName="_multi~1.pyd|_multiprocessing.pyd" Attributes="0" SourcePath="dist\_multiprocessing.pyd" SelfReg="false" Sequence="48"/>
<ROW File="pyexpat.pyd" Component_="bz2.pyd" FileName="pyexpat.pyd" Attributes="0" SourcePath="dist\pyexpat.pyd" SelfReg="false" Sequence="14"/>
<ROW File="python26.dll" Component_="python26.dll" FileName="python26.dll" Attributes="0" SourcePath="dist\python26.dll" SelfReg="false" Sequence="17"/>
<ROW File="pythoncom26.dll" Component_="pythoncom26.dll" FileName="python~1.dll|pythoncom26.dll" Attributes="0" SourcePath="dist\pythoncom26.dll" SelfReg="false" Sequence="18"/>
<ROW File="qcncodecs4.dll" Component_="qcncodecs4.dll" FileName="qcncod~1.dll|qcncodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qcncodecs4.dll" SelfReg="false" Sequence="22"/>
<ROW File="qgif4.dll" Component_="qgif4.dll" FileName="qgif4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qgif4.dll" SelfReg="false" Sequence="27"/>
<ROW File="qico4.dll" Component_="qico4.dll" FileName="qico4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qico4.dll" SelfReg="false" Sequence="28"/>
<ROW File="qjpcodecs4.dll" Component_="qjpcodecs4.dll" FileName="qjpcod~1.dll|qjpcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qjpcodecs4.dll" SelfReg="false" Sequence="23"/>
<ROW File="qjpeg4.dll" Component_="qjpeg4.dll" FileName="qjpeg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qjpeg4.dll" SelfReg="false" Sequence="29"/>
<ROW File="qkrcodecs4.dll" Component_="qkrcodecs4.dll" FileName="qkrcod~1.dll|qkrcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qkrcodecs4.dll" SelfReg="false" Sequence="24"/>
<ROW File="qmng4.dll" Component_="qmng4.dll" FileName="qmng4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qmng4.dll" SelfReg="false" Sequence="30"/>
<ROW File="qsvg4.dll" Component_="qsvg4.dll" FileName="qsvg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qsvg4.dll" SelfReg="false" Sequence="31"/>
<ROW File="qsvgicon4.dll" Component_="qsvgicon4.dll" FileName="qsvgic~1.dll|qsvgicon4.dll" Attributes="0" SourcePath="dist\qt4_plugins\iconengines\qsvgicon4.dll" SelfReg="false" Sequence="26"/>
<ROW File="qtaccessiblecompatwidgets4.dll" Component_="qtaccessiblecompatwidgets4.dll" FileName="qtacce~1.dll|qtaccessiblecompatwidgets4.dll" Attributes="0" SourcePath="dist\qt4_plugins\accessible\qtaccessiblecompatwidgets4.dll" SelfReg="false" Sequence="20"/>
<ROW File="qtaccessiblewidgets4.dll" Component_="qtaccessiblewidgets4.dll" FileName="qtacce~2.dll|qtaccessiblewidgets4.dll" Attributes="0" SourcePath="dist\qt4_plugins\accessible\qtaccessiblewidgets4.dll" SelfReg="false" Sequence="21"/>
<ROW File="qtiff4.dll" Component_="qtiff4.dll" FileName="qtiff4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qtiff4.dll" SelfReg="false" Sequence="32"/>
<ROW File="qtwcodecs4.dll" Component_="qtwcodecs4.dll" FileName="qtwcod~1.dll|qtwcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qtwcodecs4.dll" SelfReg="false" Sequence="25"/>
<ROW File="select.pyd" Component_="bz2.pyd" FileName="select.pyd" Attributes="0" SourcePath="dist\select.pyd" SelfReg="false" Sequence="35"/>
<ROW File="sip.pyd" Component_="bz2.pyd" FileName="sip.pyd" Attributes="0" SourcePath="dist\sip.pyd" SelfReg="false" Sequence="37"/>
<ROW File="socket.pyd" Component_="bz2.pyd" FileName="_socket.pyd" Attributes="0" SourcePath="dist\_socket.pyd" SelfReg="false" Sequence="49"/>
<ROW File="sqlite3.dll" Component_="sqlite3.dll" FileName="sqlite3.dll" Attributes="0" SourcePath="dist\sqlite3.dll" SelfReg="false" Sequence="38"/>
<ROW File="sqlite3.pyd" Component_="bz2.pyd" FileName="_sqlite3.pyd" Attributes="0" SourcePath="dist\_sqlite3.pyd" SelfReg="false" Sequence="50"/>
<ROW File="ssl.pyd" Component_="bz2.pyd" FileName="_ssl.pyd" Attributes="0" SourcePath="dist\_ssl.pyd" SelfReg="false" Sequence="51"/>
<ROW File="unicodedata.pyd" Component_="bz2.pyd" FileName="unicod~1.pyd|unicodedata.pyd" Attributes="0" SourcePath="dist\unicodedata.pyd" SelfReg="false" Sequence="39"/>
<ROW File="updater.exe" Component_="updater.exe" FileName="updater.exe" Attributes="0" SourcePath="&lt;updater.exe&gt;" SelfReg="false" Sequence="1" DigSign="true"/>
<ROW File="urlmon.dll" Component_="urlmon.dll" FileName="urlmon.dll" Attributes="0" SourcePath="dist\urlmon.dll" SelfReg="false" Sequence="40"/>
<ROW File="win32api.pyd" Component_="bz2.pyd" FileName="win32api.pyd" Attributes="0" SourcePath="dist\win32api.pyd" SelfReg="false" Sequence="41"/>
<ROW File="win32com.shell.shell.pyd" Component_="bz2.pyd" FileName="win32c~1.pyd|win32com.shell.shell.pyd" Attributes="0" SourcePath="dist\win32com.shell.shell.pyd" SelfReg="false" Sequence="42"/>
<ROW File="win32sysloader.pyd" Component_="bz2.pyd" FileName="_win32~1.pyd|_win32sysloader.pyd" Attributes="0" SourcePath="dist\_win32sysloader.pyd" SelfReg="false" Sequence="52"/>
<ROW File="win32trace.pyd" Component_="bz2.pyd" FileName="win32t~1.pyd|win32trace.pyd" Attributes="0" SourcePath="dist\win32trace.pyd" SelfReg="false" Sequence="43"/>
<ROW File="win32ui.pyd" Component_="bz2.pyd" FileName="win32ui.pyd" Attributes="0" SourcePath="dist\win32ui.pyd" SelfReg="false" Sequence="44"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;ui.ail&gt;"/>
<ROW Path="&lt;ui_en.ail&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="FolderDlg.aip" Path="&lt;FolderDlg.aip&gt;"/>
<ROW Fragment="ShortcutsDlg.aip" Path="&lt;ShortcutsDlg.aip&gt;"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;StaticUIStrings.aip&gt;"/>
<ROW Fragment="UI.aip" Path="&lt;UI.aip&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="aicustact.dll" SourcePath="&lt;aicustact.dll&gt;"/>
<ROW Name="default_banner.bmp" SourcePath="&lt;default-banner.bmp&gt;"/>
<ROW Name="default_dialog.bmp" SourcePath="&lt;default-dialog.bmp&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="SHORTCUTDIR" Component_="SHORTCUTDIR"/>
<ROW Directory_="qt4_plugins_DIR" Component_="qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru_PE.exe]"/>
<ROW Action="AI_PREPARE_UPGRADE" Type="1" Source="aicustact.dll" Target="PrepareUpgrade"/>
<ROW Action="AI_RESTORE_LOCATION" Type="1" Source="aicustact.dll" Target="RestoreLocation"/>
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
<ROW Action="AI_UPDATER_UNINSTALL" Type="18" Source="updater.exe" Target="/clean silent"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="SystemFolder_msiexec.exe" SourcePath="&lt;uninstall.ico&gt;" Index="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIniFileComponent">
<ROW IniFile="AppDir" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="AppDir" Value="[APPDIR]" Action="0" Component_="updater.exe"/>
<ROW IniFile="ApplicationName" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="ApplicationName" Value="[ProductName]" Action="0" Component_="updater.exe"/>
<ROW IniFile="CheckFrequency" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="CheckFrequency" Value="2" Action="0" Component_="updater.exe"/>
<ROW IniFile="CompanyName" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="CompanyName" Value="[Manufacturer]" Action="0" Component_="updater.exe"/>
<ROW IniFile="DownloadsFolder" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="DownloadsFolder" Value="[AppDataFolder][Manufacturer]\[ProductName]\updates\" Action="0" Component_="updater.exe"/>
<ROW IniFile="ID" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="ID" Value="[UpgradeCode]" Action="0" Component_="updater.exe"/>
<ROW IniFile="URL" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="URL" Value="http://www.hardcoded.net/updates/dupeguru_pe.aiu" Action="0" Component_="updater.exe"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel &lt;&gt; 5)" Sequence="210"/>
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE=&quot;No&quot; AND (Not Installed)" Sequence="1300"/>
<ROW Action="AI_UPDATER_UNINSTALL" Condition="($updater.exe = 2) AND (?updater.exe = 3) AND NOT (UPGRADINGPRODUCTCODE)" Sequence="1549"/>
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE=&quot;ALL&quot;)" Sequence="1449"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="Version9X OR VersionNT64 OR (VersionNT &gt;= 500 )" Description="[ProductName] can not be installed on systems earlier than [WindowsTypeNT]"/>
<ROW Condition="VersionNT" Description="[ProductName] can not be installed on [WindowsFamily9X]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiMediaComponent">
<ATTRIBUTE name="CabsLocation" value="1"/>
<ATTRIBUTE name="Compress" value="1"/>
<ATTRIBUTE name="CreateMd5" value="true"/>
<ATTRIBUTE name="InstallationType" value="4"/>
<ATTRIBUTE name="Package" value="6"/>
<ATTRIBUTE name="PackageName" value="install\dupeguru_pe_win_[|ProductVersion]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
<ROW Shortcut="Check_for_update" Directory_="SHORTCUTDIR" Name="Checkf~2|Check for update" Component_="updater.exe" Target="[#updater.exe]" Arguments="/checknow" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="Uninstall_dupeGuru_ME" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru PE" Component_="AIShRegAnswer" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
<ROW Shortcut="dupeGuru_PE" Directory_="SHORTCUTDIR" Name="dupeGu~1|dupeGuru PE" Component_="dupeGuru_PE.exe" Target="[#dupeGuru_PE.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="dupeGuru_PE_1" Directory_="DesktopFolder" Name="dupeGu~1|dupeGuru PE" Component_="dupeGuru_PE.exe" Target="[#dupeGuru_PE.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqComponent">
<ROW PrereqKey="0" DisplayName="Visual C++ 2008 SP1 Redistributable" SetupFileUrl="http://download.hardcoded.net/vcredist_90sp1_x86.exe" Location="1" ExactSize="4216840" MinWin9xVer="37" MinWinNTVer="17" Operator="1" ComLine="/q" Sequence="1" MD5="5689d43c3b201dd3810fa3bba4a6476a"/>
<ATTRIBUTE name="ExtractionFolder" value="[AppDataFolder][|Manufacturer]\[|ProductName]\install"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqSearchComponent">
<ROW Prereq="0" SearchType="9" SearchString="HKLM\SOFTWARE\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033\SP" RefContent="M1" Order="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
<ROW Directory_="APPDIR" SourcePath="dist" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store" ExcludeFlags="6"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.UpdaterComponent">
<ROW Updater="updater.exe" URL="URL" SearchFreq="CheckFrequency" DownloadsFolder="DownloadsFolder" ID="ID" TargetDir="AppDir" AppName="ApplicationName" CompanyName="CompanyName" UnistallCASeq="AI_UPDATER_UNINSTALL"/>
</COMPONENT>
</DOCUMENT>

28
qt/pe/main_window.py Normal file
View File

@@ -0,0 +1,28 @@
# Created By: Virgil Dupras
# Created On: 2009-05-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
from PyQt4.QtGui import QMessageBox, QAction
from base.main_window import MainWindow as MainWindowBase
class MainWindow(MainWindowBase):
def _setupUi(self):
MainWindowBase._setupUi(self)
self.actionClearPictureCache = QAction("Clear Picture Cache", self)
self.menuFile.insertAction(self.actionClearIgnoreList, self.actionClearPictureCache)
self.connect(self.actionClearPictureCache, SIGNAL("triggered()"), self.clearPictureCacheTriggered)
def clearPictureCacheTriggered(self):
title = "Clear Picture Cache"
msg = "Do you really want to remove all your cached picture analysis?"
if self._confirm(title, msg, QMessageBox.No):
self.app.scanner.cached_blocks.clear()
QMessageBox.information(self, title, "Picture cache cleared.")

View File

@@ -0,0 +1,51 @@
# $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
cdef object getblock(object image):
cdef int width, height, pixel_count, red, green, blue, i, offset, bytes_per_line
cdef char *s
cdef unsigned char r, g, b
width = image.width()
height = image.height()
if width:
pixel_count = width * height
red = green = blue = 0
bytes_per_line = image.bytesPerLine()
tmp = image.bits().asstring(image.numBytes())
s = tmp
# Qt aligns all its lines on 32bit, which means that if the number of bytes per
# line for image is not divisible by 4, there's going to be crap inserted in "s"
# We have to take this into account when calculating offsets
for i in range(height):
for j in range(width):
offset = i * bytes_per_line + j * 3
r = s[offset]
g = s[offset + 1]
b = s[offset + 2]
red += r
green += g
blue += b
return (red // pixel_count, green // pixel_count, blue // pixel_count)
else:
return (0, 0, 0)
def getblocks(image, int block_count_per_side):
cdef int width, height, block_width, block_height, ih, iw, top, left
width = image.width()
height = image.height()
if not width:
return []
block_width = max(width // block_count_per_side, 1)
block_height = max(height // block_count_per_side, 1)
result = []
for ih in range(block_count_per_side):
top = min(ih*block_height, height-block_height-1)
for iw in range(block_count_per_side):
left = min(iw*block_width, width-block_width-1)
crop = image.copy(left, top, block_width, block_height)
result.append(getblock(crop))
return result

View File

@@ -0,0 +1,14 @@
# 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 distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
setup(
cmdclass = {'build_ext': build_ext},
ext_modules = [Extension("_block", ["block.pyx"])]
)

37
qt/pe/preferences.py Normal file
View File

@@ -0,0 +1,37 @@
# 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 QSettings, QVariant
from base.preferences import Preferences as PreferencesBase
class Preferences(PreferencesBase):
# (width, is_visible)
COLUMNS_DEFAULT_ATTRS = [
(200, True), # name
(180, True), # path
(60, True), # size
(40, False), # kind
(100, True), # dimensions
(120, False), # creation
(120, False), # modification
(60, True), # match %
(80, False), # dupe count
]
def _load_specific(self, settings, get):
self.match_scaled = get('MatchScaled', self.match_scaled)
def _reset_specific(self):
self.filter_hardness = 95
self.match_scaled = False
def _save_specific(self, settings, set_):
set_('MatchScaled', self.match_scaled)

View File

@@ -0,0 +1,58 @@
# Created By: Virgil Dupras
# Created On: 2009-04-29
# $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, QDialogButtonBox
from preferences_dialog_ui import Ui_PreferencesDialog
import preferences
class PreferencesDialog(QDialog, Ui_PreferencesDialog):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
def _setupUi(self):
self.setupUi(self)
def load(self, prefs=None):
if prefs is None:
prefs = self.app.prefs
self.filterHardnessSlider.setValue(prefs.filter_hardness)
self.filterHardnessLabel.setNum(prefs.filter_hardness)
setchecked = lambda cb, b: cb.setCheckState(Qt.Checked if b else Qt.Unchecked)
setchecked(self.matchScaledBox, prefs.match_scaled)
setchecked(self.mixFileKindBox, prefs.mix_file_kind)
setchecked(self.useRegexpBox, prefs.use_regexp)
setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type)
def save(self):
prefs = self.app.prefs
prefs.filter_hardness = self.filterHardnessSlider.value()
ischecked = lambda cb: cb.checkState() == Qt.Checked
prefs.match_scaled = ischecked(self.matchScaledBox)
prefs.mix_file_kind = ischecked(self.mixFileKindBox)
prefs.use_regexp = ischecked(self.useRegexpBox)
prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
def resetToDefaults(self):
self.load(preferences.Preferences())
#--- Events
def buttonClicked(self, button):
role = self.buttonBox.buttonRole(button)
if role == QDialogButtonBox.ResetRole:
self.resetToDefaults()

257
qt/pe/preferences_dialog.ui Normal file
View File

@@ -0,0 +1,257 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PreferencesDialog</class>
<widget class="QDialog" name="PreferencesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
<height>249</height>
</rect>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,0,1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Filter Hardness:</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QSlider" name="filterHardnessSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="tracking">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterHardnessLabel">
<property name="minimumSize">
<size>
<width>21</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>100</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>More Results</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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="QLabel" name="label_3">
<property name="text">
<string>Less Results</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="matchScaledBox">
<property name="text">
<string>Match scaled pictures together</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mixFileKindBox">
<property name="text">
<string>Can mix file kind</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useRegexpBox">
<property name="text">
<string>Use regular expressions when filtering</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="removeEmptyFoldersBox">
<property name="text">
<string>Remove empty folders on delete or move</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Copy and Move:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="copyMoveDestinationComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Right in destination</string>
</property>
</item>
<item>
<property name="text">
<string>Recreate relative path</string>
</property>
</item>
<item>
<property name="text">
<string>Recreate absolute path</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>filterHardnessSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>filterHardnessLabel</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>26</y>
</hint>
<hint type="destinationlabel">
<x>271</x>
<y>26</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PreferencesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>124</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PreferencesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>124</y>
</hint>
</hints>
</connection>
</connections>
</ui>

27
qt/pe/profile.py Normal file
View File

@@ -0,0 +1,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 sys
import cProfile
import pstats
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication
if sys.platform == 'win32':
from app_win import DupeGuru
else:
from app import DupeGuru
if __name__ == "__main__":
app = QApplication(sys.argv)
QCoreApplication.setOrganizationName('Hardcoded Software')
QCoreApplication.setApplicationName('dupeGuru Picture Edition')
dgapp = DupeGuru()
cProfile.run('app.exec_()', '/tmp/prof')
p = pstats.Stats('/tmp/prof')
p.sort_stats('time').print_stats()

27
qt/pe/start.py Normal file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python
# 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 sys
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication, QIcon, QPixmap
import base.dg_rc
from app import DupeGuru
# This is a workaround for a pyinstaller problem where compiled dupeguru can't read tiff files
from PIL import TiffImagePlugin, TiffTags
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(QPixmap(":/logo_pe")))
QCoreApplication.setOrganizationName('Hardcoded Software')
QCoreApplication.setApplicationName(DupeGuru.NAME)
QCoreApplication.setApplicationVersion(DupeGuru.VERSION)
dgapp = DupeGuru()
sys.exit(app.exec_())

28
qt/pe/verinfo Normal file
View File

@@ -0,0 +1,28 @@
VSVersionInfo(
ffi=FixedFileInfo(
filevers=($versioncomma),
prodvers=($versioncomma),
mask=0x17,
flags=0x0,
OS=0x4,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
'040904b0',
[StringStruct('CompanyName', 'Hardcoded Software'),
StringStruct('FileDescription', 'dupeGuru Picture Edition'),
StringStruct('FileVersion', '$version'),
StringStruct('InternalName', 'dupeGuru PE.exe'),
StringStruct('LegalCopyright', '(c) Hardcoded Software. All rights reserved.'),
StringStruct('OriginalFilename', 'dupeGuru PE.exe'),
StringStruct('ProductName', 'dupeGuru Picture Edition'),
StringStruct('ProductVersion', '$versioncomma')])
]),
VarFileInfo([VarStruct('Translation', [1033])])
]
)

57
qt/se/app.py Normal file
View File

@@ -0,0 +1,57 @@
# Created By: Virgil Dupras
# Created On: 2009-05-24
# $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 core_se import data
from core.directories import Directories as DirectoriesBase, STATE_EXCLUDED
from base.app import DupeGuru as DupeGuruBase
from details_dialog import DetailsDialog
from preferences import Preferences
from preferences_dialog import PreferencesDialog
class Directories(DirectoriesBase):
ROOT_PATH_TO_EXCLUDE = frozenset(['windows', 'program files'])
def _default_state_for_path(self, path):
result = DirectoriesBase._default_state_for_path(self, path)
if result is not None:
return result
if len(path) == 2 and path[1].lower() in self.ROOT_PATH_TO_EXCLUDE:
return STATE_EXCLUDED
class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_se'
NAME = 'dupeGuru'
VERSION = '2.9.0'
DELTA_COLUMNS = frozenset([2, 4, 5])
def __init__(self):
DupeGuruBase.__init__(self, data, appid=4)
def _setup(self):
self.directories = Directories()
DupeGuruBase._setup(self)
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.min_match_percentage = self.prefs.filter_hardness
self.scanner.scan_type = self.prefs.scan_type
self.scanner.word_weighting = self.prefs.word_weighting
self.scanner.match_similar_words = self.prefs.match_similar
threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0
self.scanner.size_threshold = threshold * 1024 # threshold is in KB. the scanner wants bytes
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)
def _create_preferences(self):
return Preferences()
def _create_preferences_dialog(self, parent):
return PreferencesDialog(parent, self)

46
qt/se/build.py Normal file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-24
# $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
# On Windows, PyInstaller is used to build an exe (py2exe creates a very bad looking icon
# The release version is outdated. Use at least r672 on http://svn.pyinstaller.org/trunk
import os
import os.path as op
import shutil
from app import DupeGuru
def print_and_do(cmd):
print cmd
os.system(cmd)
# Removing build and dist
if op.exists('build'):
shutil.rmtree('build')
if op.exists('dist'):
shutil.rmtree('dist')
version = DupeGuru.VERSION
versioncomma = version.replace('.', ', ') + ', 0'
verinfo = open('verinfo').read()
verinfo = verinfo.replace('$versioncomma', versioncomma).replace('$version', version)
fp = open('verinfo_tmp', 'w')
fp.write(verinfo)
fp.close()
print_and_do("python C:\\Python26\\pyinstaller\\Build.py dgse.spec")
os.remove('verinfo_tmp')
print_and_do("del dist\\*90.dll") # They're in vcredist, no need to include them
print_and_do("xcopy /Y /S /I help\\dupeguru_help dist\\help")
aicom = '"\\Program Files\\Caphyon\\Advanced Installer\\AdvancedInstaller.com"'
shutil.copy('installer.aip', 'installer_tmp.aip') # this is so we don'a have to re-commit installer.aip at every version change
print_and_do('%s /edit installer_tmp.aip /SetVersion %s' % (aicom, version))
print_and_do('%s /build installer_tmp.aip -force' % aicom)
os.remove('installer_tmp.aip')

22
qt/se/details_dialog.py Normal file
View File

@@ -0,0 +1,22 @@
# Created By: Virgil Dupras
# Created On: 2009-05-24
# $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
from PyQt4.QtGui import QDialog
from base.details_table import DetailsModel
from details_dialog_ui import Ui_DetailsDialog
class DetailsDialog(QDialog, Ui_DetailsDialog):
def __init__(self, parent, app):
QDialog.__init__(self, parent, Qt.Tool)
self.app = app
self.setupUi(self)
self.model = DetailsModel(app)
self.tableView.setModel(self.model)

53
qt/se/details_dialog.ui Normal file
View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DetailsDialog</class>
<widget class="QDialog" name="DetailsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>502</width>
<height>186</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="DetailsTable" name="tableView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DetailsTable</class>
<extends>QTableView</extends>
<header>base.details_table</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

21
qt/se/dgse.spec Normal file
View File

@@ -0,0 +1,21 @@
# -*- mode: python -*-
a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'start.py'],
pathex=[])
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
exclude_binaries=1,
name=os.path.join('build\\pyi.win32\\dupeGuru', 'dupeGuru.exe'),
debug=False,
strip=False,
upx=True,
console=False,
icon='..\\base\\images\\dgse_logo.ico',
version='verinfo_tmp')
coll = COLLECT( exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name=os.path.join('dist'))

19
qt/se/gen.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-24
# $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 os
import os.path as op
from hsutil.build import print_and_do, build_all_qt_ui
build_all_qt_ui(op.join('..', '..', 'qtlib', 'ui'))
build_all_qt_ui(op.join('..', 'base'))
build_all_qt_ui('.')
print_and_do("pyrcc4 {0} > {1}".format(op.join('..', 'base', 'dg.qrc'), op.join('..', 'base', 'dg_rc.py')))

256
qt/se/installer.aip Normal file
View File

@@ -0,0 +1,256 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT type="Advanced Installer" CreateVersion="4.4.1" version="4.9.2" modules="professional" RootPath="." Language="en">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_DESKTOP_SH" Value="1" Type="4"/>
<ROW Property="AI_QUICKLAUNCH_SH" Value="1" Type="4"/>
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/>
<ROW Property="AI_STARTMENU_SH" Value="1" Type="4"/>
<ROW Property="AI_STARTUP_SH" Value="1" Type="4"/>
<ROW Property="ALLUSERS" Value="2"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
<ROW Property="ARPCONTACT" Value="support@hardcoded.net"/>
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru/"/>
<ROW Property="ARPURLUPDATEINFO" Value="http://www.hardcoded.net/dupeguru/"/>
<ROW Property="BannerBitmap" Value="default_banner.bmp" Type="1"/>
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="default_dialog.bmp" Type="1"/>
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
<ROW Property="ProductCode" Value="1033:{D1E765C2-98C4-49AF-80DA-A5F803EB4FC3} "/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="dupeGuru" ValueLocId="*"/>
<ROW Property="ProductVersion" Value="2.7.0"/>
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
<ROW Property="UpgradeCode" Value="{33E0D6C8-D7C6-46ED-B1A9-ECFE409EC9D5}"/>
<ROW Property="WindowsFamily9X" Value="Windows 9x/ME"/>
<ROW Property="WindowsTypeNT" Value="Windows 2000"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
<ROW Directory="DesktopFolder" Directory_Parent="TARGETDIR" DefaultDir="Deskto~1|DesktopFolder" IsPseudoRoot="1"/>
<ROW Directory="SHORTCUTDIR" Directory_Parent="TARGETDIR" DefaultDir="SHORTC~1|SHORTCUTDIR" IsPseudoRoot="1"/>
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
<ROW Directory="accessible_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="access~1|accessible"/>
<ROW Directory="codecs_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="codecs"/>
<ROW Directory="help_DIR" Directory_Parent="APPDIR" DefaultDir="help"/>
<ROW Directory="iconengines_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="iconen~1|iconengines"/>
<ROW Directory="imageformats_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="imagef~1|imageformats"/>
<ROW Directory="images_DIR" Directory_Parent="help_DIR" DefaultDir="images"/>
<ROW Directory="qt4_plugins_DIR" Directory_Parent="APPDIR" DefaultDir="qt4_pl~1|qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
<ROW Component="AIShRegAnswer" ComponentId="{775090B3-2E56-40F5-9DD8-24A2F82DA601}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer" FullKeyPath="HK_UM\Software\Caphyon\Advanced Installer\Installs\[ProductCode]\AIShRegAnswer"/>
<ROW Component="MSVCP90.dll" ComponentId="{EA7F99CE-AA38-4676-863A-4BBD78B635F3}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCP90.dll" FullKeyPath="APPDIR\MSVCP90.dll"/>
<ROW Component="MSVCR90.dll" ComponentId="{9C4646F4-7CB2-4BA2-9F00-16B38EDEF590}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCR90.dll" FullKeyPath="APPDIR\MSVCR90.dll"/>
<ROW Component="POWRPROF.dll" ComponentId="{2896ECE4-56FD-452A-A1CE-5C95919136C6}" Directory_="APPDIR" Attributes="0" KeyPath="POWRPROF.dll" FullKeyPath="APPDIR\POWRPROF.dll"/>
<ROW Component="PyWinTypes26.dll" ComponentId="{B664FF8C-C60F-423C-9AC4-26144896E583}" Directory_="APPDIR" Attributes="0" KeyPath="PyWinTypes26.dll" FullKeyPath="APPDIR\PyWinTypes26.dll"/>
<ROW Component="QtCore4.dll" ComponentId="{F517476C-BC6D-40B6-A063-5A10680ECA05}" Directory_="APPDIR" Attributes="0" KeyPath="QtCore4.dll" FullKeyPath="APPDIR\QtCore4.dll"/>
<ROW Component="QtGui4.dll" ComponentId="{4915BAC4-AFB0-42E1-BF2E-D4C3E58D4BEE}" Directory_="APPDIR" Attributes="0" KeyPath="QtGui4.dll" FullKeyPath="APPDIR\QtGui4.dll"/>
<ROW Component="SHLWAPI.dll" ComponentId="{E533841B-68B8-459F-8C67-6D7721B9E926}" Directory_="APPDIR" Attributes="0" KeyPath="SHLWAPI.dll" FullKeyPath="APPDIR\SHLWAPI.dll"/>
<ROW Component="bz2.pyd" ComponentId="{E03E8F51-0E8D-40A2-9ED0-A8EA0ED4CD19}" Directory_="APPDIR" Attributes="0" KeyPath="bz2.pyd" FullKeyPath="APPDIR"/>
<ROW Component="credits.htm" ComponentId="{0F4F2A16-8BC1-44C0-AEAF-B62CD08992B9}" Directory_="help_DIR" Attributes="0" KeyPath="credits.htm" FullKeyPath="APPDIR\help"/>
<ROW Component="dupeGuru.exe" ComponentId="{A8FFC84F-B54B-4883-B9FD-5C545AF0E51C}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru.exe" FullKeyPath="APPDIR\dupeGuru.exe"/>
<ROW Component="hs_title.png" ComponentId="{161F629F-409B-468A-AD7C-8832B1FA7D83}" Directory_="images_DIR" Attributes="0" KeyPath="hs_title.png" FullKeyPath="APPDIR\help\images"/>
<ROW Component="iertutil.dll" ComponentId="{408B35EA-AD4A-449A-9A6C-DE5301667BB4}" Directory_="APPDIR" Attributes="0" KeyPath="iertutil.dll" FullKeyPath="APPDIR\iertutil.dll"/>
<ROW Component="mfc90.dll" ComponentId="{570D1F1E-F914-4E0A-AC2B-A8DAEDA57D06}" Directory_="APPDIR" Attributes="0" KeyPath="mfc90.dll" FullKeyPath="APPDIR\mfc90.dll"/>
<ROW Component="python26.dll" ComponentId="{C47E3AEB-FCE1-4A7D-90AF-26D52100756F}" Directory_="APPDIR" Attributes="0" KeyPath="python26.dll" FullKeyPath="APPDIR\python26.dll"/>
<ROW Component="pythoncom26.dll" ComponentId="{474C48BA-8C13-428C-B932-49C65A1619FC}" Directory_="APPDIR" Attributes="0" KeyPath="pythoncom26.dll" FullKeyPath="APPDIR\pythoncom26.dll"/>
<ROW Component="qcncodecs4.dll" ComponentId="{1FA15E05-79B4-490E-8BE7-2915DAFECDA0}" Directory_="codecs_DIR" Attributes="0" KeyPath="qcncodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qcncodecs4.dll"/>
<ROW Component="qgif4.dll" ComponentId="{12390BD7-63E5-4BAE-A760-84D6E47387F3}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qgif4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qgif4.dll"/>
<ROW Component="qico4.dll" ComponentId="{7EC94828-5141-4383-BB9C-89C6AE543237}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qico4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qico4.dll"/>
<ROW Component="qjpcodecs4.dll" ComponentId="{A607689B-97DD-4F1F-9655-7EEC2D934A75}" Directory_="codecs_DIR" Attributes="0" KeyPath="qjpcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qjpcodecs4.dll"/>
<ROW Component="qjpeg4.dll" ComponentId="{58EB6546-1E7D-48E3-A407-08B945D68317}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qjpeg4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qjpeg4.dll"/>
<ROW Component="qkrcodecs4.dll" ComponentId="{3D322ADA-9D38-4B5F-8335-44BDE935D5D7}" Directory_="codecs_DIR" Attributes="0" KeyPath="qkrcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qkrcodecs4.dll"/>
<ROW Component="qmng4.dll" ComponentId="{11B243B6-A6E5-4282-A58B-5A4F5A2CB253}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qmng4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qmng4.dll"/>
<ROW Component="qsvg4.dll" ComponentId="{D689DDEB-D4E9-4DE0-B32B-85FD25C40726}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qsvg4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qsvg4.dll"/>
<ROW Component="qsvgicon4.dll" ComponentId="{6EF94FDD-3E92-4886-925F-B264C962E7EF}" Directory_="iconengines_DIR" Attributes="0" KeyPath="qsvgicon4.dll" FullKeyPath="APPDIR\qt4_plugins\iconengines\qsvgicon4.dll"/>
<ROW Component="qt4_plugins" ComponentId="{EC4153B6-0269-4A4F-BF8A-46CB0884773E}" Directory_="qt4_plugins_DIR" Attributes="0"/>
<ROW Component="qtaccessiblewidgets4.dll" ComponentId="{0DB9EE4C-922F-42AC-80CC-4EA3CBBB1629}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblewidgets4.dll" FullKeyPath="APPDIR\qt4_plugins\accessible\qtaccessiblewidgets4.dll"/>
<ROW Component="qtiff4.dll" ComponentId="{660F482B-9508-4A26-BC1A-610E84829CA4}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qtiff4.dll" FullKeyPath="APPDIR\qt4_plugins\imageformats\qtiff4.dll"/>
<ROW Component="qtwcodecs4.dll" ComponentId="{68610953-B652-4340-BBB9-B1EEB3A6AF7A}" Directory_="codecs_DIR" Attributes="0" KeyPath="qtwcodecs4.dll" FullKeyPath="APPDIR\qt4_plugins\codecs\qtwcodecs4.dll"/>
<ROW Component="updater.exe" ComponentId="{CB63C33D-EB1B-420A-8BAA-CD380923F12B}" Directory_="APPDIR" Attributes="0" KeyPath="updater.exe" FullKeyPath="APPDIR\updater.exe"/>
<ROW Component="urlmon.dll" ComponentId="{BCF9A9E0-49E9-4EB2-9159-694FDF98F0AA}" Directory_="APPDIR" Attributes="0" KeyPath="urlmon.dll" FullKeyPath="APPDIR\urlmon.dll"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="updater.exe dupeGuru.exe AIShRegAnswer iertutil.dll mfc90.dll MSVCP90.dll MSVCR90.dll POWRPROF.dll python26.dll pythoncom26.dll PyWinTypes26.dll QtCore4.dll QtGui4.dll SHLWAPI.dll urlmon.dll bz2.pyd qtaccessiblewidgets4.dll qcncodecs4.dll qjpcodecs4.dll qkrcodecs4.dll qtwcodecs4.dll qsvgicon4.dll qgif4.dll qico4.dll qjpeg4.dll qmng4.dll qsvg4.dll qtiff4.dll qt4_plugins credits.htm hs_title.png"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
<ROW File="MSVCP90.dll" Component_="MSVCP90.dll" FileName="MSVCP90.dll" Attributes="0" SourcePath="dist\MSVCP90.dll" SelfReg="false" Sequence="5"/>
<ROW File="MSVCR90.dll" Component_="MSVCR90.dll" FileName="MSVCR90.dll" Attributes="0" SourcePath="dist\MSVCR90.dll" SelfReg="false" Sequence="6"/>
<ROW File="POWRPROF.dll" Component_="POWRPROF.dll" FileName="POWRPROF.dll" Attributes="0" SourcePath="dist\POWRPROF.dll" SelfReg="false" Sequence="7"/>
<ROW File="PyQt4.QtCore.pyd" Component_="bz2.pyd" FileName="PyQt4Q~1.pyd|PyQt4.QtCore.pyd" Attributes="0" SourcePath="dist\PyQt4.QtCore.pyd" SelfReg="false" Sequence="17"/>
<ROW File="PyQt4.QtGui.pyd" Component_="bz2.pyd" FileName="PyQt4Q~2.pyd|PyQt4.QtGui.pyd" Attributes="0" SourcePath="dist\PyQt4.QtGui.pyd" SelfReg="false" Sequence="18"/>
<ROW File="PyWinTypes26.dll" Component_="PyWinTypes26.dll" FileName="PyWinT~1.dll|PyWinTypes26.dll" Attributes="0" SourcePath="dist\PyWinTypes26.dll" SelfReg="false" Sequence="10"/>
<ROW File="QtCore4.dll" Component_="QtCore4.dll" FileName="QtCore4.dll" Attributes="0" SourcePath="dist\QtCore4.dll" SelfReg="false" Sequence="11"/>
<ROW File="QtGui4.dll" Component_="QtGui4.dll" FileName="QtGui4.dll" Attributes="0" SourcePath="dist\QtGui4.dll" SelfReg="false" Sequence="12"/>
<ROW File="SHLWAPI.dll" Component_="SHLWAPI.dll" FileName="SHLWAPI.dll" Attributes="0" SourcePath="dist\SHLWAPI.dll" SelfReg="false" Sequence="13"/>
<ROW File="bz2.pyd" Component_="bz2.pyd" FileName="bz2.pyd" Attributes="0" SourcePath="dist\bz2.pyd" SelfReg="false" Sequence="15"/>
<ROW File="credits.htm" Component_="credits.htm" FileName="credits.htm" Attributes="0" SourcePath="dist\help\credits.htm" SelfReg="false" Sequence="44"/>
<ROW File="ctypes.pyd" Component_="bz2.pyd" FileName="_ctypes.pyd" Attributes="0" SourcePath="dist\_ctypes.pyd" SelfReg="false" Sequence="38"/>
<ROW File="directories.htm" Component_="credits.htm" FileName="direct~1.htm|directories.htm" Attributes="0" SourcePath="dist\help\directories.htm" SelfReg="false" Sequence="45"/>
<ROW File="dupeGuru.exe" Component_="dupeGuru.exe" FileName="dupeGuru.exe" Attributes="0" SourcePath="dist\dupeGuru.exe" SelfReg="false" Sequence="2"/>
<ROW File="faq.htm" Component_="credits.htm" FileName="faq.htm" Attributes="0" SourcePath="dist\help\faq.htm" SelfReg="false" Sequence="46"/>
<ROW File="hardcoded.css" Component_="credits.htm" FileName="hardco~1.css|hardcoded.css" Attributes="0" SourcePath="dist\help\hardcoded.css" SelfReg="false" Sequence="47"/>
<ROW File="hashlib.pyd" Component_="bz2.pyd" FileName="_hashlib.pyd" Attributes="0" SourcePath="dist\_hashlib.pyd" SelfReg="false" Sequence="39"/>
<ROW File="hs_title.png" Component_="hs_title.png" FileName="hs_title.png" Attributes="0" SourcePath="dist\help\images\hs_title.png" SelfReg="false" Sequence="48"/>
<ROW File="iertutil.dll" Component_="iertutil.dll" FileName="iertutil.dll" Attributes="0" SourcePath="dist\iertutil.dll" SelfReg="false" Sequence="3"/>
<ROW File="intro.htm" Component_="credits.htm" FileName="intro.htm" Attributes="0" SourcePath="dist\help\intro.htm" SelfReg="false" Sequence="49"/>
<ROW File="mfc90.dll" Component_="mfc90.dll" FileName="mfc90.dll" Attributes="0" SourcePath="dist\mfc90.dll" SelfReg="false" Sequence="4"/>
<ROW File="multiprocessing.pyd" Component_="bz2.pyd" FileName="_multi~1.pyd|_multiprocessing.pyd" Attributes="0" SourcePath="dist\_multiprocessing.pyd" SelfReg="false" Sequence="40"/>
<ROW File="power_marker.htm" Component_="credits.htm" FileName="power_~1.htm|power_marker.htm" Attributes="0" SourcePath="dist\help\power_marker.htm" SelfReg="false" Sequence="50"/>
<ROW File="preferences.htm" Component_="credits.htm" FileName="prefer~1.htm|preferences.htm" Attributes="0" SourcePath="dist\help\preferences.htm" SelfReg="false" Sequence="51"/>
<ROW File="pyexpat.pyd" Component_="bz2.pyd" FileName="pyexpat.pyd" Attributes="0" SourcePath="dist\pyexpat.pyd" SelfReg="false" Sequence="16"/>
<ROW File="python26.dll" Component_="python26.dll" FileName="python26.dll" Attributes="0" SourcePath="dist\python26.dll" SelfReg="false" Sequence="8"/>
<ROW File="pythoncom26.dll" Component_="pythoncom26.dll" FileName="python~1.dll|pythoncom26.dll" Attributes="0" SourcePath="dist\pythoncom26.dll" SelfReg="false" Sequence="9"/>
<ROW File="qcncodecs4.dll" Component_="qcncodecs4.dll" FileName="qcncod~1.dll|qcncodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qcncodecs4.dll" SelfReg="false" Sequence="20"/>
<ROW File="qgif4.dll" Component_="qgif4.dll" FileName="qgif4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qgif4.dll" SelfReg="false" Sequence="25"/>
<ROW File="qico4.dll" Component_="qico4.dll" FileName="qico4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qico4.dll" SelfReg="false" Sequence="26"/>
<ROW File="qjpcodecs4.dll" Component_="qjpcodecs4.dll" FileName="qjpcod~1.dll|qjpcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qjpcodecs4.dll" SelfReg="false" Sequence="21"/>
<ROW File="qjpeg4.dll" Component_="qjpeg4.dll" FileName="qjpeg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qjpeg4.dll" SelfReg="false" Sequence="27"/>
<ROW File="qkrcodecs4.dll" Component_="qkrcodecs4.dll" FileName="qkrcod~1.dll|qkrcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qkrcodecs4.dll" SelfReg="false" Sequence="22"/>
<ROW File="qmng4.dll" Component_="qmng4.dll" FileName="qmng4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qmng4.dll" SelfReg="false" Sequence="28"/>
<ROW File="qsvg4.dll" Component_="qsvg4.dll" FileName="qsvg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qsvg4.dll" SelfReg="false" Sequence="29"/>
<ROW File="qsvgicon4.dll" Component_="qsvgicon4.dll" FileName="qsvgic~1.dll|qsvgicon4.dll" Attributes="0" SourcePath="dist\qt4_plugins\iconengines\qsvgicon4.dll" SelfReg="false" Sequence="24"/>
<ROW File="qtaccessiblewidgets4.dll" Component_="qtaccessiblewidgets4.dll" FileName="qtacce~2.dll|qtaccessiblewidgets4.dll" Attributes="0" SourcePath="dist\qt4_plugins\accessible\qtaccessiblewidgets4.dll" SelfReg="false" Sequence="19"/>
<ROW File="qtiff4.dll" Component_="qtiff4.dll" FileName="qtiff4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qtiff4.dll" SelfReg="false" Sequence="30"/>
<ROW File="qtwcodecs4.dll" Component_="qtwcodecs4.dll" FileName="qtwcod~1.dll|qtwcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qtwcodecs4.dll" SelfReg="false" Sequence="23"/>
<ROW File="quick_start.htm" Component_="credits.htm" FileName="quick_~1.htm|quick_start.htm" Attributes="0" SourcePath="dist\help\quick_start.htm" SelfReg="false" Sequence="52"/>
<ROW File="results.htm" Component_="credits.htm" FileName="results.htm" Attributes="0" SourcePath="dist\help\results.htm" SelfReg="false" Sequence="53"/>
<ROW File="select.pyd" Component_="bz2.pyd" FileName="select.pyd" Attributes="0" SourcePath="dist\select.pyd" SelfReg="false" Sequence="31"/>
<ROW File="sip.pyd" Component_="bz2.pyd" FileName="sip.pyd" Attributes="0" SourcePath="dist\sip.pyd" SelfReg="false" Sequence="32"/>
<ROW File="socket.pyd" Component_="bz2.pyd" FileName="_socket.pyd" Attributes="0" SourcePath="dist\_socket.pyd" SelfReg="false" Sequence="41"/>
<ROW File="ssl.pyd" Component_="bz2.pyd" FileName="_ssl.pyd" Attributes="0" SourcePath="dist\_ssl.pyd" SelfReg="false" Sequence="42"/>
<ROW File="unicodedata.pyd" Component_="bz2.pyd" FileName="unicod~1.pyd|unicodedata.pyd" Attributes="0" SourcePath="dist\unicodedata.pyd" SelfReg="false" Sequence="33"/>
<ROW File="updater.exe" Component_="updater.exe" FileName="updater.exe" Attributes="0" SourcePath="&lt;updater.exe&gt;" SelfReg="false" Sequence="1"/>
<ROW File="urlmon.dll" Component_="urlmon.dll" FileName="urlmon.dll" Attributes="0" SourcePath="dist\urlmon.dll" SelfReg="false" Sequence="14"/>
<ROW File="versions.htm" Component_="credits.htm" FileName="versions.htm" Attributes="0" SourcePath="dist\help\versions.htm" SelfReg="false" Sequence="54"/>
<ROW File="win32api.pyd" Component_="bz2.pyd" FileName="win32api.pyd" Attributes="0" SourcePath="dist\win32api.pyd" SelfReg="false" Sequence="34"/>
<ROW File="win32com.shell.shell.pyd" Component_="bz2.pyd" FileName="win32c~1.pyd|win32com.shell.shell.pyd" Attributes="0" SourcePath="dist\win32com.shell.shell.pyd" SelfReg="false" Sequence="35"/>
<ROW File="win32sysloader.pyd" Component_="bz2.pyd" FileName="_win32~1.pyd|_win32sysloader.pyd" Attributes="0" SourcePath="dist\_win32sysloader.pyd" SelfReg="false" Sequence="43"/>
<ROW File="win32trace.pyd" Component_="bz2.pyd" FileName="win32t~1.pyd|win32trace.pyd" Attributes="0" SourcePath="dist\win32trace.pyd" SelfReg="false" Sequence="36"/>
<ROW File="win32ui.pyd" Component_="bz2.pyd" FileName="win32ui.pyd" Attributes="0" SourcePath="dist\win32ui.pyd" SelfReg="false" Sequence="37"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;ui.ail&gt;"/>
<ROW Path="&lt;ui_en.ail&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="FolderDlg.aip" Path="&lt;FolderDlg.aip&gt;"/>
<ROW Fragment="ShortcutsDlg.aip" Path="&lt;ShortcutsDlg.aip&gt;"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;StaticUIStrings.aip&gt;"/>
<ROW Fragment="UI.aip" Path="&lt;UI.aip&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="aicustact.dll" SourcePath="&lt;aicustact.dll&gt;"/>
<ROW Name="default_banner.bmp" SourcePath="&lt;default-banner.bmp&gt;"/>
<ROW Name="default_dialog.bmp" SourcePath="&lt;default-dialog.bmp&gt;"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
<ROW Dialog_="ShortcutsDlg" Control_="StartmenuShortcutsCheckBox" Action="Show" Condition="(Not Installed)"/>
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/>
<ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#StartmenuShortcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="3"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="2"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="1"/>
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="3"/>
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="qt4_plugins_DIR" Component_="qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru.exe]"/>
<ROW Action="AI_PREPARE_UPGRADE" Type="1" Source="aicustact.dll" Target="PrepareUpgrade"/>
<ROW Action="AI_RESTORE_LOCATION" Type="1" Source="aicustact.dll" Target="RestoreLocation"/>
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
<ROW Action="AI_UPDATER_UNINSTALL" Type="18" Source="updater.exe" Target="/clean silent"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="SystemFolder_msiexec.exe" SourcePath="&lt;uninstall.ico&gt;" Index="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIniFileComponent">
<ROW IniFile="AppDir" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="AppDir" Value="[APPDIR]" Action="0" Component_="updater.exe"/>
<ROW IniFile="ApplicationName" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="ApplicationName" Value="[ProductName]" Action="0" Component_="updater.exe"/>
<ROW IniFile="CheckFrequency" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="CheckFrequency" Value="2" Action="0" Component_="updater.exe"/>
<ROW IniFile="CompanyName" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="CompanyName" Value="[Manufacturer]" Action="0" Component_="updater.exe"/>
<ROW IniFile="DownloadsFolder" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="DownloadsFolder" Value="[AppDataFolder][Manufacturer]\[ProductName]\updates\" Action="0" Component_="updater.exe"/>
<ROW IniFile="ID" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="ID" Value="[UpgradeCode]" Action="0" Component_="updater.exe"/>
<ROW IniFile="URL" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="URL" Value="http://www.hardcoded.net/updates/dupeguru.aiu" Action="0" Component_="updater.exe"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel &lt;&gt; 5)" Sequence="210"/>
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/>
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE=&quot;No&quot; AND (Not Installed)" Sequence="1300"/>
<ROW Action="AI_UPDATER_UNINSTALL" Condition="($updater.exe = 2) AND (?updater.exe = 3) AND NOT (UPGRADINGPRODUCTCODE)" Sequence="1549"/>
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE=&quot;ALL&quot;)" Sequence="1449"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="Version9X OR VersionNT64 OR (VersionNT &gt;= 500 )" Description="[ProductName] can not be installed on systems earlier than [WindowsTypeNT]"/>
<ROW Condition="VersionNT" Description="[ProductName] can not be installed on [WindowsFamily9X]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiMediaComponent">
<ATTRIBUTE name="CabsLocation" value="1"/>
<ATTRIBUTE name="Compress" value="1"/>
<ATTRIBUTE name="CreateMd5" value="true"/>
<ATTRIBUTE name="EXEName" value="dupeguru_win_[|ProductVersion]"/>
<ATTRIBUTE name="InstallationType" value="4"/>
<ATTRIBUTE name="Package" value="6"/>
<ATTRIBUTE name="PackageName" value="install\dupeguru_win_[|ProductVersion]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
<ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
<ROW Shortcut="Check_for_updates" Directory_="SHORTCUTDIR" Name="Checkf~1|Check for update" Component_="updater.exe" Target="[#updater.exe]" Arguments="/checknow" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="Uninstall_dupeGuru" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru" Component_="MSVCP90.dll" Target="[SystemFolder]msiexec.exe" Arguments="/x [ProductCode]" Hotkey="0" Icon_="SystemFolder_msiexec.exe" IconIndex="0" ShowCmd="1"/>
<ROW Shortcut="dupeGuru" Directory_="DesktopFolder" Name="dupeGuru" Component_="dupeGuru.exe" Target="[#dupeGuru.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
<ROW Shortcut="dupeGuru_1" Directory_="SHORTCUTDIR" Name="dupeGuru" Component_="dupeGuru.exe" Target="[#dupeGuru.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqComponent">
<ROW PrereqKey="0" DisplayName="Visual C++ 2008 SP1 Redistributable" SetupFileUrl="http://download.hardcoded.net/vcredist_90sp1_x86.exe" Location="1" ExactSize="4216840" MinWin9xVer="37" MinWinNTVer="17" Operator="1" ComLine="/q" Sequence="1" MD5="5689d43c3b201dd3810fa3bba4a6476a"/>
<ATTRIBUTE name="ExtractionFolder" value="[AppDataFolder][|Manufacturer]\[|ProductName]\install"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqSearchComponent">
<ROW Prereq="0" SearchType="9" SearchString="HKLM\SOFTWARE\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033\SP" RefContent="M1" Order="1"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent">
<ROW Directory_="APPDIR" SourcePath="dist" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store|*.vshost.*" ExcludeFlags="6"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.UpdaterComponent">
<ROW Updater="updater.exe" URL="URL" SearchFreq="CheckFrequency" DownloadsFolder="DownloadsFolder" ID="ID" TargetDir="AppDir" AppName="ApplicationName" CompanyName="CompanyName" UnistallCASeq="AI_UPDATER_UNINSTALL"/>
</COMPONENT>
</DOCUMENT>

49
qt/se/preferences.py Normal file
View File

@@ -0,0 +1,49 @@
# Created By: Virgil Dupras
# Created On: 2009-05-24
# $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 core.scanner import SCAN_TYPE_FILENAME, SCAN_TYPE_CONTENT
from base.preferences import Preferences as PreferencesBase
class Preferences(PreferencesBase):
# (width, is_visible)
COLUMNS_DEFAULT_ATTRS = [
(200, True), # name
(180, True), # path
(60, True), # size
(40, False), # Kind
(120, False), # creation
(120, False), # modification
(60, True), # match %
(120, False), # Words Used
(80, False), # dupe count
]
def _load_specific(self, settings, get):
self.scan_type = get('ScanType', self.scan_type)
self.word_weighting = get('WordWeighting', self.word_weighting)
self.match_similar = get('MatchSimilar', self.match_similar)
self.ignore_small_files = get('IgnoreSmallFiles', self.ignore_small_files)
self.small_file_threshold = get('SmallFileThreshold', self.small_file_threshold)
def _reset_specific(self):
self.filter_hardness = 80
self.scan_type = SCAN_TYPE_CONTENT
self.word_weighting = True
self.match_similar = False
self.ignore_small_files = True
self.small_file_threshold = 10 # KB
def _save_specific(self, settings, set_):
set_('ScanType', self.scan_type)
set_('WordWeighting', self.word_weighting)
set_('MatchSimilar', self.match_similar)
set_('IgnoreSmallFiles', self.ignore_small_files)
set_('SmallFileThreshold', self.small_file_threshold)

View File

@@ -0,0 +1,84 @@
# Created By: Virgil Dupras
# Created On: 2009-05-24
# $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, QDialogButtonBox
from hsutil.misc import tryint
from core.scanner import SCAN_TYPE_FILENAME, SCAN_TYPE_CONTENT
from preferences_dialog_ui import Ui_PreferencesDialog
import preferences
SCAN_TYPE_ORDER = [
SCAN_TYPE_FILENAME,
SCAN_TYPE_CONTENT,
]
class PreferencesDialog(QDialog, Ui_PreferencesDialog):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
self.connect(self.scanTypeComboBox, SIGNAL('currentIndexChanged(int)'), self.scanTypeChanged)
def _setupUi(self):
self.setupUi(self)
def load(self, prefs=None):
if prefs is None:
prefs = self.app.prefs
self.filterHardnessSlider.setValue(prefs.filter_hardness)
self.filterHardnessLabel.setNum(prefs.filter_hardness)
scan_type_index = SCAN_TYPE_ORDER.index(prefs.scan_type)
self.scanTypeComboBox.setCurrentIndex(scan_type_index)
setchecked = lambda cb, b: cb.setCheckState(Qt.Checked if b else Qt.Unchecked)
setchecked(self.matchSimilarBox, prefs.match_similar)
setchecked(self.wordWeightingBox, prefs.word_weighting)
setchecked(self.mixFileKindBox, prefs.mix_file_kind)
setchecked(self.useRegexpBox, prefs.use_regexp)
setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
setchecked(self.ignoreSmallFilesBox, prefs.ignore_small_files)
self.sizeThresholdEdit.setText(unicode(prefs.small_file_threshold))
self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type)
def save(self):
prefs = self.app.prefs
prefs.filter_hardness = self.filterHardnessSlider.value()
prefs.scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()]
ischecked = lambda cb: cb.checkState() == Qt.Checked
prefs.match_similar = ischecked(self.matchSimilarBox)
prefs.word_weighting = ischecked(self.wordWeightingBox)
prefs.mix_file_kind = ischecked(self.mixFileKindBox)
prefs.use_regexp = ischecked(self.useRegexpBox)
prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
prefs.ignore_small_files = ischecked(self.ignoreSmallFilesBox)
prefs.small_file_threshold = tryint(self.sizeThresholdEdit.text())
prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
def resetToDefaults(self):
self.load(preferences.Preferences())
#--- Events
def buttonClicked(self, button):
role = self.buttonBox.buttonRole(button)
if role == QDialogButtonBox.ResetRole:
self.resetToDefaults()
def scanTypeChanged(self, index):
scan_type = SCAN_TYPE_ORDER[self.scanTypeComboBox.currentIndex()]
word_based = scan_type == SCAN_TYPE_FILENAME
self.filterHardnessSlider.setEnabled(word_based)
self.matchSimilarBox.setEnabled(word_based)
self.wordWeightingBox.setEnabled(word_based)

389
qt/se/preferences_dialog.ui Normal file
View File

@@ -0,0 +1,389 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PreferencesDialog</class>
<widget class="QDialog" name="PreferencesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>294</width>
<height>296</height>
</rect>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Scan Type:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="scanTypeComboBox">
<item>
<property name="text">
<string>Filename</string>
</property>
</item>
<item>
<property name="text">
<string>Contents</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Filter Hardness:</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QSlider" name="filterHardnessSlider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="tracking">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterHardnessLabel">
<property name="minimumSize">
<size>
<width>21</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>100</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>More Results</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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="QLabel" name="label_3">
<property name="text">
<string>Less Results</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>134</height>
</size>
</property>
<widget class="QCheckBox" name="wordWeightingBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Word weighting</string>
</property>
</widget>
<widget class="QCheckBox" name="matchSimilarBox">
<property name="geometry">
<rect>
<x>0</x>
<y>22</y>
<width>350</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Match similar words</string>
</property>
</widget>
<widget class="QCheckBox" name="mixFileKindBox">
<property name="geometry">
<rect>
<x>0</x>
<y>44</y>
<width>350</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Can mix file kind</string>
</property>
</widget>
<widget class="QCheckBox" name="useRegexpBox">
<property name="geometry">
<rect>
<x>0</x>
<y>66</y>
<width>350</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Use regular expressions when filtering</string>
</property>
</widget>
<widget class="QCheckBox" name="removeEmptyFoldersBox">
<property name="geometry">
<rect>
<x>0</x>
<y>88</y>
<width>350</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Remove empty folders on delete or move</string>
</property>
</widget>
<widget class="QCheckBox" name="ignoreSmallFilesBox">
<property name="geometry">
<rect>
<x>0</x>
<y>110</y>
<width>171</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Ignore files smaller than</string>
</property>
</widget>
<widget class="QLineEdit" name="sizeThresholdEdit">
<property name="geometry">
<rect>
<x>170</x>
<y>110</y>
<width>51</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>230</x>
<y>110</y>
<width>21</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>KB</string>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Copy and Move:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="copyMoveDestinationComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Right in destination</string>
</property>
</item>
<item>
<property name="text">
<string>Recreate relative path</string>
</property>
</item>
<item>
<property name="text">
<string>Recreate absolute path</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>filterHardnessSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>filterHardnessLabel</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>26</y>
</hint>
<hint type="destinationlabel">
<x>271</x>
<y>26</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PreferencesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>124</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PreferencesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>182</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>124</y>
</hint>
</hints>
</connection>
</connections>
</ui>

27
qt/se/profile.py Normal file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python
# 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 sys
import cProfile
import pstats
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication
if sys.platform == 'win32':
from app_win import DupeGuru
else:
from app import DupeGuru
if __name__ == "__main__":
app = QApplication(sys.argv)
QCoreApplication.setOrganizationName('Hardcoded Software')
QCoreApplication.setApplicationName('dupeGuru')
dgapp = DupeGuru()
cProfile.run('app.exec_()', '/tmp/prof')
p = pstats.Stats('/tmp/prof')
p.sort_stats('time').print_stats()

24
qt/se/start.py Normal file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
# 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 sys
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtGui import QApplication, QIcon, QPixmap
import base.dg_rc
from app import DupeGuru
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(QPixmap(":/logo_se")))
QCoreApplication.setOrganizationName('Hardcoded Software')
QCoreApplication.setApplicationName(DupeGuru.NAME)
QCoreApplication.setApplicationVersion(DupeGuru.VERSION)
dgapp = DupeGuru()
sys.exit(app.exec_())

28
qt/se/verinfo Normal file
View File

@@ -0,0 +1,28 @@
VSVersionInfo(
ffi=FixedFileInfo(
filevers=($versioncomma),
prodvers=($versioncomma),
mask=0x17,
flags=0x0,
OS=0x4,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
'040904b0',
[StringStruct('CompanyName', 'Hardcoded Software'),
StringStruct('FileDescription', 'dupeGuru'),
StringStruct('FileVersion', '$version'),
StringStruct('InternalName', 'dupeGuru.exe'),
StringStruct('LegalCopyright', '(c) Hardcoded Software. All rights reserved.'),
StringStruct('OriginalFilename', 'dupeGuru.exe'),
StringStruct('ProductName', 'dupeGuru'),
StringStruct('ProductVersion', '$versioncomma')])
]),
VarFileInfo([VarStruct('Translation', [1033])])
]
)