1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-25 08:01:39 +00:00

Compare commits

..

55 Commits

Author SHA1 Message Date
Virgil Dupras
b182585d46 Fixed column reloading which was broken since the mark-->marked rename. 2010-02-13 14:08:37 +01:00
Virgil Dupras
e8f92535d3 me v5.7.2 2010-02-13 13:00:41 +01:00
Virgil Dupras
d62c3663e9 qt: scroll to selection on results refresh. 2010-02-13 12:34:36 +01:00
Virgil Dupras
6b0bfda9fb During Make Selected Reference, it's now the selection *paths* that are restored rather than the selected *dupes* 2010-02-13 10:39:54 +01:00
Virgil Dupras
7477330961 Fixed ResultOutline.selectedDupeCount(). 2010-02-12 21:58:50 +01:00
Virgil Dupras
1f71157063 Updated cocoalib subrepo. 2010-02-12 20:10:50 +01:00
Virgil Dupras
905988c592 Removed MatchesView and took advantage of HSOutlineView's delete and space triggered delegate methods. 2010-02-12 17:15:48 +01:00
Virgil Dupras
310951bfa8 Removed getSelectedPaths() from ResultsWindow. 2010-02-12 16:30:32 +01:00
Virgil Dupras
64c1087856 Fixed app_test which was broken since connext() calls aren't made by the gui themselves. 2010-02-12 16:28:15 +01:00
Virgil Dupras
cab6d924aa Adapted the Qt codebase to the addition of core.gui.result_tree and core.gui.stats_label. 2010-02-12 15:39:29 +01:00
Virgil Dupras
c3a972d39b Fixed renaming in results. 2010-02-12 13:52:40 +01:00
Virgil Dupras
33d44d4d24 Remove Marked now correctly updates the results. 2010-02-12 13:39:50 +01:00
Virgil Dupras
fd89cf2482 Pushed some code down from app_cocoa to app and re-organized test units. 2010-02-12 12:43:50 +01:00
Virgil Dupras
112ffb981f Cleaned up some cruft. 2010-02-12 12:30:00 +01:00
Virgil Dupras
514426b980 Re-added the root children count optimization in the results outline. 2010-02-12 11:34:00 +01:00
Virgil Dupras
a4bf1c8be6 Made marking changes much faster and also made data fetching lazy in dupe nodes. 2010-02-12 11:21:39 +01:00
Virgil Dupras
9b82e1478f Re-added multiple selection support in the results. 2010-02-12 11:07:33 +01:00
Virgil Dupras
d5f145d57e Fixed sorting. 2010-02-11 21:03:22 +01:00
Virgil Dupras
bab891ee74 Added the StatsLabel. 2010-02-11 20:54:06 +01:00
Virgil Dupras
a65fd7d0d0 Brought back delta values. 2010-02-11 19:22:31 +01:00
Virgil Dupras
46836cc805 Pushed down some result refresh calls to the core code. 2010-02-11 18:47:45 +01:00
Virgil Dupras
42559f13d8 Began the transition to a HSOutline based result outline. There's still a lot of glitches, the most glaring one being the lack of support for multiple selection. 2010-02-11 17:52:18 +01:00
Virgil Dupras
87351b5920 Removed Table from cocoalib and fixed the license of the newly added units. 2010-02-11 13:38:34 +01:00
Virgil Dupras
e68dcf189c Adapted the ME project to the latest structural changes. 2010-02-11 13:35:14 +01:00
Virgil Dupras
5d62b8389c Added tag pe1.8.3 for changeset 1cef6d39855f 2010-02-11 12:37:15 +01:00
Virgil Dupras
c50aebe76d pe v1.8.3 2010-02-11 10:04:54 +01:00
Virgil Dupras
a610f3fde7 Adapted the PE project to the latest structural changes. 2010-02-10 12:07:31 +01:00
Virgil Dupras
626391a1d9 [#94 state:fixed] Fixed bug in block_osx causing blocks containing nil values to be created. 2010-02-10 11:58:05 +01:00
Virgil Dupras
1bedfe75ea Added tag se2.9.2 for changeset 7b7c5a66ebee 2010-02-10 10:50:05 +01:00
Virgil Dupras
86ecc8d4d5 Fixed build script 2010-02-10 00:18:25 -08:00
Virgil Dupras
9eca84efe1 se v2.9.2 2010-02-10 08:48:01 +01:00
Virgil Dupras
8a6fb6dcba Updated Andvanced Installer project file for 7.5. 2010-02-09 15:03:36 +00:00
Virgil Dupras
e3706fa923 Fixed qt packaging. 2010-02-09 14:52:09 +00:00
Virgil Dupras
8193bc5f60 build.add_to_pythonpath() now also adds the path to sys.path. 2010-02-09 15:47:22 +01:00
Virgil Dupras
504ecaee5e Straightened out qt's packaging process. 2010-02-09 15:42:48 +01:00
Virgil Dupras
7c9e836572 Straightened out qt's build process. 2010-02-09 15:32:52 +01:00
Virgil Dupras
5db0f09b43 Fixed Reveal File on Qt. 2010-02-09 15:24:57 +01:00
Virgil Dupras
195bc4ef21 Eliminated code duplication in ResultsWindow. 2010-02-09 14:59:35 +01:00
Virgil Dupras
6b190bc184 Fixed a bug where double clicking a column would open the selected file. 2010-02-09 14:55:51 +01:00
Virgil Dupras
39f1cac2c8 Eliminated code duplication in ResultsWindow's awakeFromNib. 2010-02-09 14:50:27 +01:00
Virgil Dupras
d193eed519 [#93 state:fixed] Straightened out selection and matches reloading. 2010-02-09 14:45:14 +01:00
Virgil Dupras
2d80b0e12f Updated hsutil subrepo. 2010-02-08 08:37:40 +01:00
Virgil Dupras
b50d99be9c Added the PyRegistrable cocoa interface. 2010-02-07 16:29:39 +01:00
Virgil Dupras
af41876a5e DetailsPanel is now a subclass of HGWindowController. 2010-02-07 16:19:14 +01:00
Virgil Dupras
76d351d8be Adapted th qt part to core.gui.directory_tree. 2010-02-07 16:00:58 +01:00
Virgil Dupras
b5dd9651c3 Huge refactoring. I moved MGOutline from moneyGuru (as well as everything that comes with it) and used it to create DirectoryOutline for the directories panel. 2010-02-07 15:26:50 +01:00
Virgil Dupras
3e34502014 Added the hsgui subrepo. 2010-02-06 15:35:51 +01:00
Virgil Dupras
5e57f9cbd6 Removed logic duplication across toolkit code in "Reveal Selected" action. 2010-02-06 15:31:35 +01:00
Virgil Dupras
8edb869fdc Removed logic duplication across toolkit code in "Remove Selected" action. 2010-02-06 12:44:21 +01:00
Virgil Dupras
37238c7f57 Removed logic duplication across toolkit code in "Open Selected" action. 2010-02-06 12:36:43 +01:00
Virgil Dupras
9edee82fa1 Removed logic duplication across toolkit code in "Make Reference" action. 2010-02-06 12:27:11 +01:00
Virgil Dupras
f7aaea79af Removed useless add_to_ignore_list() 2010-02-06 12:14:33 +01:00
Virgil Dupras
3c75d2f8b7 Removed logic duplication across toolkit code in "Add to Ignore List" action. 2010-02-06 12:12:20 +01:00
Virgil Dupras
64c67e19d2 Reduced code duplication among editions in ResultsWindow. 2010-02-06 11:40:10 +01:00
Virgil Dupras
d4db8faad8 Added tag pe1.8.2 for changeset 19e40bab2052 2010-02-06 10:49:13 +01:00
70 changed files with 2500 additions and 2770 deletions

View File

@@ -8,3 +8,6 @@ cbcf9c80fee4c908ef2efbf1c143c9e47676c9b2 pe1.8.0
61c4101851bdea3cb37dfb76f0d404c78c7c594c se2.9.1 61c4101851bdea3cb37dfb76f0d404c78c7c594c se2.9.1
0e923897a3389331d4ab3debbc40b8dd616199d9 pe1.8.1 0e923897a3389331d4ab3debbc40b8dd616199d9 pe1.8.1
2c454eca9ebe93b6cf34916068f828a6a39e3eaf me5.7.1 2c454eca9ebe93b6cf34916068f828a6a39e3eaf me5.7.1
19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2
7b7c5a66ebee4e4b8125330d24fe9ce1a070ff25 se2.9.2
1cef6d39855f85d4be728646bc78b860e6d4e398 pe1.8.3

105
build.py
View File

@@ -18,6 +18,62 @@ import yaml
from hsdocgen import generate_help, filters from hsdocgen import generate_help, filters
from hsutil.build import add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages from hsutil.build import add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages
def build_cocoa(edition, dev, help_destpath):
if not dev:
print "Building help index"
os.system('open -a /Developer/Applications/Utilities/Help\\ Indexer.app {0}'.format(help_destpath))
print "Building dg_cocoa.plugin"
if op.exists('build'):
shutil.rmtree('build')
os.mkdir('build')
if not dev:
specific_packages = {
'se': ['core_se'],
'me': ['core_me', 'hsmedia'],
'pe': ['core_pe'],
}[edition]
copy_packages(['core', 'hsutil'] + specific_packages, 'build')
cocoa_project_path = 'cocoa/{0}'.format(edition)
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
os.chdir('build')
script_args = ['py2app', '-A'] if dev else ['py2app']
setup(
script_args = script_args,
plugin = ['dg_cocoa.py'],
setup_requires = ['py2app'],
)
os.chdir('..')
pluginpath = op.join(cocoa_project_path, 'dg_cocoa.plugin')
if op.exists(pluginpath):
shutil.rmtree(pluginpath)
shutil.move('build/dist/dg_cocoa.plugin', pluginpath)
if dev:
# In alias mode, the tweakings we do to the pythonpath aren't counted in. We have to
# manually put a .pth in the plugin
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
open(pthpath, 'w').write(op.abspath('.'))
os.chdir(cocoa_project_path)
print "Building the XCode project"
args = []
if dev:
args.append('-configuration dev')
else:
args.append('-configuration release')
args = ' '.join(args)
os.system('xcodebuild {0}'.format(args))
os.chdir('..')
def build_qt(edition, dev):
build_all_qt_ui(op.join('qtlib', 'ui'))
build_all_qt_ui(op.join('qt', 'base'))
build_all_qt_ui(op.join('qt', edition))
print_and_do("pyrcc4 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
if edition == 'pe':
os.chdir(op.join('qt', edition))
os.system('python gen.py')
os.chdir(op.join('..', '..'))
def main(): def main():
conf = yaml.load(open('conf.yaml')) conf = yaml.load(open('conf.yaml'))
edition = conf['edition'] edition = conf['edition']
@@ -42,54 +98,9 @@ def main():
os.system('python gen.py') os.system('python gen.py')
os.chdir('..') os.chdir('..')
if ui == 'cocoa': if ui == 'cocoa':
if not dev: build_cocoa(edition, dev, help_destpath)
print "Building help index"
os.system('open -a /Developer/Applications/Utilities/Help\\ Indexer.app {0}'.format(help_destpath))
print "Building dg_cocoa.plugin"
if op.exists('build'):
shutil.rmtree('build')
os.mkdir('build')
if not dev:
specific_packages = {
'se': ['core_se'],
'me': ['core_me', 'hsmedia'],
'pe': ['core_pe'],
}[edition]
copy_packages(['core', 'hsutil'] + specific_packages, 'build')
cocoa_project_path = 'cocoa/{0}'.format(edition)
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
os.chdir('build')
script_args = ['py2app', '-A'] if dev else ['py2app']
setup(
script_args = script_args,
plugin = ['dg_cocoa.py'],
setup_requires = ['py2app'],
)
os.chdir('..')
pluginpath = op.join(cocoa_project_path, 'dg_cocoa.plugin')
if op.exists(pluginpath):
shutil.rmtree(pluginpath)
shutil.move('build/dist/dg_cocoa.plugin', pluginpath)
if dev:
# In alias mode, the tweakings we do to the pythonpath aren't counted in. We have to
# manually put a .pth in the plugin
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
open(pthpath, 'w').write(op.abspath('.'))
os.chdir(cocoa_project_path)
print "Building the XCode project"
args = []
if dev:
args.append('-configuration dev')
else:
args.append('-configuration release')
args = ' '.join(args)
os.system('xcodebuild {0}'.format(args))
os.chdir('..')
elif ui == 'qt': elif ui == 'qt':
os.chdir(op.join('qt', edition)) build_qt(edition, dev)
os.system('python gen.py')
os.chdir(op.join('..', '..'))
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -8,11 +8,6 @@ http://www.hardcoded.net/licenses/hs_license
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
/* ResultsChangedNotification happens on major changes, which requires a complete reload of the data*/
#define ResultsChangedNotification @"ResultsChangedNotification"
/* ResultsChangedNotification happens on minor changes, which requires buffer flush*/
#define ResultsUpdatedNotification @"ResultsUpdatedNotification"
#define ResultsMarkingChangedNotification @"ResultsMarkingChangedNotification"
#define RegistrationRequired @"RegistrationRequired" #define RegistrationRequired @"RegistrationRequired"
#define JobStarted @"JobStarted" #define JobStarted @"JobStarted"
#define JobInProgress @"JobInProgress" #define JobInProgress @"JobInProgress"
@@ -21,6 +16,4 @@ http://www.hardcoded.net/licenses/hs_license
#define jobScan @"job_scan" #define jobScan @"job_scan"
#define jobCopy @"job_copy" #define jobCopy @"job_copy"
#define jobMove @"job_move" #define jobMove @"job_move"
#define jobDelete @"job_delete" #define jobDelete @"job_delete"
#define DEMO_MAX_ACTION_COUNT 10

View File

@@ -7,16 +7,16 @@ http://www.hardcoded.net/licenses/hs_license
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "HSWindowController.h"
#import "PyApp.h" #import "PyApp.h"
#import "PyDetailsPanel.h" #import "PyDetailsPanel.h"
@interface DetailsPanel : NSWindowController @interface DetailsPanel : HSWindowController
{ {
IBOutlet NSTableView *detailsTable; IBOutlet NSTableView *detailsTable;
PyDetailsPanel *py;
} }
- (id)initWithPy:(PyApp *)aPy; - (id)initWithPy:(PyApp *)aPy;
- (PyDetailsPanel *)py;
- (void)toggleVisibility; - (void)toggleVisibility;

View File

@@ -12,19 +12,23 @@ http://www.hardcoded.net/licenses/hs_license
@implementation DetailsPanel @implementation DetailsPanel
- (id)initWithPy:(PyApp *)aPy - (id)initWithPy:(PyApp *)aPy
{ {
self = [super initWithWindowNibName:@"DetailsPanel"]; self = [super initWithNibName:@"DetailsPanel" pyClassName:@"PyDetailsPanel" pyParent:aPy];
[self window]; //So the detailsTable is initialized. [self window]; //So the detailsTable is initialized.
Class pyClass = [Utils classNamed:@"PyDetailsPanel"]; [self connect];
py = [[pyClass alloc] initWithCocoa:self pyParent:aPy];
return self; return self;
} }
- (void)dealloc - (void)dealloc
{ {
[py release]; [self disconnect];
[super dealloc]; [super dealloc];
} }
- (PyDetailsPanel *)py
{
return (PyDetailsPanel *)py;
}
- (void)refreshDetails - (void)refreshDetails
{ {
[detailsTable reloadData]; [detailsTable reloadData];
@@ -44,12 +48,12 @@ http://www.hardcoded.net/licenses/hs_license
/* NSTableView Delegate */ /* NSTableView Delegate */
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{ {
return [py numberOfRows]; return [[self py] numberOfRows];
} }
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
{ {
return [py valueForColumn:[column identifier] row:row]; return [[self py] valueForColumn:[column identifier] row:row];
} }
/* Python --> Cocoa */ /* Python --> Cocoa */

View File

@@ -0,0 +1,16 @@
/*
Copyright 2010 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 <Cocoa/Cocoa.h>
#import "HSOutline.h"
#import "PyDirectoryOutline.h"
@interface DirectoryOutline : HSOutline {}
- (id)initWithPyParent:(id)aPyParent view:(HSOutlineView *)aOutlineView;
- (PyDirectoryOutline *)py;
@end;

View File

@@ -0,0 +1,84 @@
/*
Copyright 2010 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 "DirectoryOutline.h"
@implementation DirectoryOutline
- (id)initWithPyParent:(id)aPyParent view:(HSOutlineView *)aOutlineView
{
self = [super initWithPyClassName:@"PyDirectoryOutline" pyParent:aPyParent view:aOutlineView];
[outlineView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[super dealloc];
}
- (PyDirectoryOutline *)py
{
return (PyDirectoryOutline *)py;
}
/* Delegate */
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
if (sourceDragMask & NSDragOperationLink)
return NSDragOperationLink;
}
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
if (!(sourceDragMask & NSDragOperationLink))
return NO;
for (NSString *filename in filenames) {
[[self py] addDirectory:filename];
}
}
return YES;
}
- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
NSTextFieldCell *textCell = cell;
NSIndexPath *path = item;
BOOL selected = [path isEqualTo:[outlineView selectedPath]];
if (selected) {
[textCell setTextColor:[NSColor blackColor]];
return;
}
NSInteger state = [self intProperty:@"state" valueAtPath:path];
if (state == 1) {
[textCell setTextColor:[NSColor blueColor]];
}
else if (state == 2) {
[textCell setTextColor:[NSColor redColor]];
}
else {
[textCell setTextColor:[NSColor blackColor]];
}
}
}
@end

View File

@@ -8,31 +8,23 @@ http://www.hardcoded.net/licenses/hs_license
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "RecentDirectories.h" #import "RecentDirectories.h"
#import "Outline.h" #import "HSOutlineView.h"
#import "DirectoryOutline.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
@interface DirectoryOutline : OutlineView
{
}
@end
@protocol DirectoryOutlineDelegate
- (void)outlineView:(NSOutlineView *)outlineView addDirectory:(NSString *)directory;
@end
@interface DirectoryPanel : NSWindowController @interface DirectoryPanel : NSWindowController
{ {
IBOutlet NSPopUpButton *addButtonPopUp; IBOutlet NSPopUpButton *addButtonPopUp;
IBOutlet DirectoryOutline *directories; IBOutlet HSOutlineView *outlineView;
IBOutlet NSButton *removeButton; IBOutlet NSButton *removeButton;
PyDupeGuruBase *_py; PyDupeGuruBase *_py;
RecentDirectories *_recentDirectories; RecentDirectories *_recentDirectories;
DirectoryOutline *outline;
} }
- (id)initWithParentApp:(id)aParentApp; - (id)initWithParentApp:(id)aParentApp;
- (IBAction)askForDirectory:(id)sender; - (IBAction)askForDirectory:(id)sender;
- (IBAction)changeDirectoryState:(id)sender;
- (IBAction)popupAddDirectoryMenu:(id)sender; - (IBAction)popupAddDirectoryMenu:(id)sender;
- (IBAction)removeSelectedDirectory:(id)sender; - (IBAction)removeSelectedDirectory:(id)sender;
- (IBAction)toggleVisible:(id)sender; - (IBAction)toggleVisible:(id)sender;

View File

@@ -11,48 +11,6 @@ http://www.hardcoded.net/licenses/hs_license
#import "Utils.h" #import "Utils.h"
#import "AppDelegate.h" #import "AppDelegate.h"
@implementation DirectoryOutline
- (void)doInit
{
[super doInit];
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType])
{
if (sourceDragMask & NSDragOperationLink)
return NSDragOperationLink;
}
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] )
{
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
if (!(sourceDragMask & NSDragOperationLink))
return NO;
if (([self delegate] == nil) || (![[self delegate] respondsToSelector:@selector(outlineView:addDirectory:)]))
return NO;
for (NSString *filename in filenames)
[[self delegate] outlineView:self addDirectory:filename];
}
return YES;
}
@end
@implementation DirectoryPanel @implementation DirectoryPanel
- (id)initWithParentApp:(id)aParentApp - (id)initWithParentApp:(id)aParentApp
{ {
@@ -61,23 +19,19 @@ http://www.hardcoded.net/licenses/hs_license
AppDelegateBase *app = aParentApp; AppDelegateBase *app = aParentApp;
_py = [app py]; _py = [app py];
_recentDirectories = [app recentDirectories]; _recentDirectories = [app recentDirectories];
[directories setPy:_py]; outline = [[DirectoryOutline alloc] initWithPyParent:_py view:outlineView];
NSPopUpButtonCell *cell = [[directories tableColumnWithIdentifier:@"1"] dataCell];
[cell addItemWithTitle:@"Normal"];
[cell addItemWithTitle:@"Reference"];
[cell addItemWithTitle:@"Excluded"];
for (NSInteger i=0;i<[[cell itemArray] count];i++)
{
NSMenuItem *mi = [[cell itemArray] objectAtIndex:i];
[mi setTarget:self];
[mi setAction:@selector(changeDirectoryState:)];
[mi setTag:i];
}
[self refreshRemoveButtonText]; [self refreshRemoveButtonText];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:) name:NSOutlineViewSelectionDidChangeNotification object:directories]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
return self; return self;
} }
- (void)dealloc
{
[outline release];
[super dealloc];
}
/* Actions */ /* Actions */
- (IBAction)askForDirectory:(id)sender - (IBAction)askForDirectory:(id)sender
@@ -95,15 +49,6 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (IBAction)changeDirectoryState:(id)sender
{
OVNode *node = [directories itemAtRow:[directories clickedRow]];
[_py setDirectory:p2a([node indexPath]) state:i2n([sender tag])];
[node resetAllBuffers];
[directories reloadItem:node reloadChildren:YES];
[directories display];
}
- (IBAction)popupAddDirectoryMenu:(id)sender - (IBAction)popupAddDirectoryMenu:(id)sender
{ {
if ([[_recentDirectories directories] count] == 0) if ([[_recentDirectories directories] count] == 0)
@@ -125,21 +70,17 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)removeSelectedDirectory:(id)sender - (IBAction)removeSelectedDirectory:(id)sender
{ {
[[self window] makeKeyAndOrderFront:nil]; [[self window] makeKeyAndOrderFront:nil];
if ([directories selectedRow] < 0) if ([outlineView selectedRow] < 0)
return; return;
OVNode *node = [directories itemAtRow:[directories selectedRow]]; NSIndexPath *path = [outline selectedIndexPath];
if ([node level] == 1) NSInteger state = [outline intProperty:@"state" valueAtPath:path];
{ if (([path length] == 1) && (state != 2)) {
[_py removeDirectory:i2n([node index])]; [_py removeDirectory:i2n([path indexAtPosition:0])];
[directories reloadData];
} }
else else {
{
NSInteger state = n2i([[node buffer] objectAtIndex:1]);
NSInteger newState = state == 2 ? 0 : 2; // If excluded, put it back NSInteger newState = state == 2 ? 0 : 2; // If excluded, put it back
[_py setDirectory:p2a([node indexPath]) state:i2n(newState)]; [outline setIntProperty:@"state" value:newState atPath:path];
[node resetAllBuffers]; [outlineView display];
[directories display];
} }
[self refreshRemoveButtonText]; [self refreshRemoveButtonText];
} }
@@ -150,70 +91,40 @@ http://www.hardcoded.net/licenses/hs_license
} }
/* Public */ /* Public */
- (void)addDirectory:(NSString *)directory - (void)addDirectory:(NSString *)directory
{ {
NSInteger r = [[_py addDirectory:directory] intValue]; NSInteger r = [[_py addDirectory:directory] intValue];
if (r) if (r) {
{
NSString *m; NSString *m;
switch (r) switch (r) {
{ case 1: {
case 1:
{
m = @"This directory already is in the list."; m = @"This directory already is in the list.";
break; break;
} }
case 2: case 2: {
{
m = @"This directory does not exist."; m = @"This directory does not exist.";
break; break;
} }
} }
[Dialogs showMessage:m]; [Dialogs showMessage:m];
} }
[directories reloadData];
[_recentDirectories addDirectory:directory]; [_recentDirectories addDirectory:directory];
[[self window] makeKeyAndOrderFront:nil]; [[self window] makeKeyAndOrderFront:nil];
} }
- (void)refreshRemoveButtonText - (void)refreshRemoveButtonText
{ {
if ([directories selectedRow] < 0) if ([outlineView selectedRow] < 0) {
{
[removeButton setEnabled:NO]; [removeButton setEnabled:NO];
return; return;
} }
[removeButton setEnabled:YES]; [removeButton setEnabled:YES];
OVNode *node = [directories itemAtRow:[directories selectedRow]]; NSInteger state = [outline intProperty:@"state" valueAtPath:[outline selectedIndexPath]];
NSInteger state = n2i([[node buffer] objectAtIndex:1]);
NSString *buttonText = state == 2 ? @"Put Back" : @"Remove"; NSString *buttonText = state == 2 ? @"Put Back" : @"Remove";
[removeButton setTitle:buttonText]; [removeButton setTitle:buttonText];
} }
/* Delegate */ /* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView addDirectory:(NSString *)directory
{
[self addDirectory:directory];
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
OVNode *node = item;
NSInteger state = n2i([[node buffer] objectAtIndex:1]);
if ([cell isKindOfClass:[NSTextFieldCell class]])
{
NSTextFieldCell *textCell = cell;
if (state == 1)
[textCell setTextColor:[NSColor blueColor]];
else if (state == 2)
[textCell setTextColor:[NSColor redColor]];
else
[textCell setTextColor:[NSColor blackColor]];
}
}
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
{ {
BOOL isdir; BOOL isdir;

View File

@@ -7,9 +7,9 @@ http://www.hardcoded.net/licenses/hs_license
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "PyGUI.h"
@interface PyDetailsPanel : NSObject @interface PyDetailsPanel : PyGUI
- (id)initWithCocoa:(id)cocoa pyParent:(id)pyParent;
- (NSInteger)numberOfRows; - (NSInteger)numberOfRows;
- (id)valueForColumn:(NSString *)column row:(NSInteger)row; - (id)valueForColumn:(NSString *)column row:(NSInteger)row;
@end @end

View File

@@ -0,0 +1,14 @@
/*
Copyright 2010 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 <Cocoa/Cocoa.h>
#import "PyOutline.h"
@interface PyDirectoryOutline : PyOutline
- (void)addDirectory:(NSString *)directoryPath;
@end

View File

@@ -13,7 +13,6 @@ http://www.hardcoded.net/licenses/hs_license
//Actions //Actions
- (NSNumber *)addDirectory:(NSString *)name; - (NSNumber *)addDirectory:(NSString *)name;
- (void)removeDirectory:(NSNumber *)index; - (void)removeDirectory:(NSNumber *)index;
- (void)setDirectory:(NSArray *)indexPath state:(NSNumber *)state;
- (void)loadResults; - (void)loadResults;
- (void)saveResults; - (void)saveResults;
- (void)loadIgnoreList; - (void)loadIgnoreList;
@@ -24,27 +23,17 @@ http://www.hardcoded.net/licenses/hs_license
- (NSNumber *)doScan; - (NSNumber *)doScan;
- (NSArray *)selectedPowerMarkerNodePaths;
- (void)selectPowerMarkerNodePaths:(NSArray *)aIndexPaths;
- (NSArray *)selectedResultNodePaths;
- (void)selectResultNodePaths:(NSArray *)aIndexPaths;
- (void)toggleSelectedMark; - (void)toggleSelectedMark;
- (void)markAll; - (void)markAll;
- (void)markInvert; - (void)markInvert;
- (void)markNone; - (void)markNone;
- (void)addSelectedToIgnoreList; - (void)addSelectedToIgnoreList;
- (void)removeSelected;
- (void)openSelected; - (void)openSelected;
- (NSNumber *)renameSelected:(NSString *)aNewName;
- (void)revealSelected; - (void)revealSelected;
- (void)makeSelectedReference; - (void)makeSelectedReference;
- (void)applyFilter:(NSString *)filter; - (void)applyFilter:(NSString *)filter;
- (void)sortGroupsBy:(NSNumber *)aIdentifier ascending:(NSNumber *)aAscending;
- (void)sortDupesBy:(NSNumber *)aIdentifier ascending:(NSNumber *)aAscending;
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType; - (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
- (void)deleteMarked; - (void)deleteMarked;
- (void)removeMarked; - (void)removeMarked;
@@ -52,13 +41,11 @@ http://www.hardcoded.net/licenses/hs_license
//Data //Data
- (NSNumber *)getIgnoreListCount; - (NSNumber *)getIgnoreListCount;
- (NSNumber *)getMarkCount; - (NSNumber *)getMarkCount;
- (NSString *)getStatLine;
- (NSNumber *)getOperationalErrorCount; - (NSNumber *)getOperationalErrorCount;
//Scanning options //Scanning options
- (void)setMinMatchPercentage:(NSNumber *)percentage; - (void)setMinMatchPercentage:(NSNumber *)percentage;
- (void)setMixFileKind:(NSNumber *)mix_file_kind; - (void)setMixFileKind:(NSNumber *)mix_file_kind;
- (void)setDisplayDeltaValues:(NSNumber *)display_delta_values;
- (void)setEscapeFilterRegexp:(NSNumber *)escape_filter_regexp; - (void)setEscapeFilterRegexp:(NSNumber *)escape_filter_regexp;
- (void)setRemoveEmptyFolders:(NSNumber *)remove_empty_folders; - (void)setRemoveEmptyFolders:(NSNumber *)remove_empty_folders;
- (void)setSizeThreshold:(NSInteger)size_threshold; - (void)setSizeThreshold:(NSInteger)size_threshold;

24
cocoa/base/PyResultTree.h Normal file
View File

@@ -0,0 +1,24 @@
/*
Copyright 2010 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 <Cocoa/Cocoa.h>
#import "PyOutline.h"
@interface PyResultTree : PyOutline
- (BOOL)powerMarkerMode;
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
- (BOOL)deltaValuesMode;
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
- (NSString *)valueForPath:(NSArray *)aPath column:(NSInteger)aColumn;
- (BOOL)renameSelected:(NSString *)aNewName;
- (void)sortBy:(NSInteger)aIdentifier ascending:(BOOL)aAscending;
- (void)markSelected;
- (void)removeSelected;
- (NSArray *)rootChildrenCounts;
@end

14
cocoa/base/PyStatsLabel.h Normal file
View File

@@ -0,0 +1,14 @@
/*
Copyright 2010 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 <Cocoa/Cocoa.h>
#import "PyGUI.h"
@interface PyStatsLabel : PyGUI
- (NSString *)display;
@end

View File

@@ -0,0 +1,26 @@
/*
Copyright 2010 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 <Cocoa/Cocoa.h>
#import "HSOutline.h"
#import "PyResultTree.h"
@interface ResultOutline : HSOutline
{
NSIndexSet *_deltaColumns;
NSArray *_rootChildrenCounts;
}
- (PyResultTree *)py;
- (BOOL)powerMarkerMode;
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
- (BOOL)deltaValuesMode;
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns;
- (NSInteger)selectedDupeCount;
- (void)removeSelected;
@end;

207
cocoa/base/ResultOutline.m Normal file
View File

@@ -0,0 +1,207 @@
/*
Copyright 2010 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 "ResultOutline.h"
#import "Dialogs.h"
#import "Utils.h"
#import "Consts.h"
@implementation ResultOutline
- (id)initWithPyParent:(id)aPyParent view:(HSOutlineView *)aOutlineView
{
self = [super initWithPyClassName:@"PyResultOutline" pyParent:aPyParent view:aOutlineView];
_rootChildrenCounts = nil;
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[_deltaColumns release];
[super dealloc];
}
- (PyResultTree *)py
{
return (PyResultTree *)py;
}
/* Public */
- (BOOL)powerMarkerMode
{
return [[self py] powerMarkerMode];
}
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
{
[[self py] setPowerMarkerMode:aPowerMarkerMode];
}
- (BOOL)deltaValuesMode
{
return [[self py] deltaValuesMode];
}
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
{
[[self py] setDeltaValuesMode:aDeltaValuesMode];
}
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns
{
[_deltaColumns release];
_deltaColumns = [aDeltaColumns retain];
}
- (NSInteger)selectedDupeCount
{
NSArray *selected = [self selectedIndexPaths];
if ([self powerMarkerMode]) {
return [selected count];
}
else {
NSInteger r = 0;
for (NSIndexPath *path in selected) {
if ([path length] == 2) {
r++;
}
}
return r;
}
}
- (void)removeSelected
{
NSInteger selectedDupeCount = [self selectedDupeCount];
if (!selectedDupeCount)
return;
NSString *msg = [NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",selectedDupeCount];
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
return;
[[self py] removeSelected];
}
/* Datasource */
- (NSInteger)outlineView:(NSOutlineView *)aOutlineView numberOfChildrenOfItem:(id)item
{
NSIndexPath *path = item;
if ((path != nil) && ([path length] == 1)) {
if (_rootChildrenCounts == nil) {
_rootChildrenCounts = [[[self py] rootChildrenCounts] retain];
}
NSInteger index = [path indexAtPosition:0];
return n2i([_rootChildrenCounts objectAtIndex:index]);
}
return [super outlineView:aOutlineView numberOfChildrenOfItem:item];
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item
{
NSIndexPath *path = item;
NSString *identifier = [column identifier];
if ([identifier isEqual:@"marked"]) {
return b2n([self boolProperty:@"marked" valueAtPath:path]);
}
NSInteger columnId = [identifier integerValue];
return [[self py] valueForPath:p2a(path) column:columnId];
}
- (void)outlineView:(NSOutlineView *)aOutlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if ([[tableColumn identifier] isEqual:@"0"]) {
NSIndexPath *path = item;
NSString *oldName = [[self py] valueForPath:p2a(path) column:0];
NSString *newName = object;
if (![newName isEqual:oldName]) {
BOOL renamed = [[self py] renameSelected:newName];
if (!renamed) {
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.", newName]];
}
else {
[self refreshItemAtPath:path];
}
}
}
else {
[super outlineView:aOutlineView setObjectValue:object forTableColumn:tableColumn byItem:item];
}
}
/* Delegate */
- (void)outlineView:(NSOutlineView *)aOutlineView didClickTableColumn:(NSTableColumn *)tableColumn
{
if ([[outlineView sortDescriptors] count] < 1)
return;
NSSortDescriptor *sd = [[outlineView sortDescriptors] objectAtIndex:0];
[[self py] sortBy:[[sd key] integerValue] ascending:[sd ascending]];
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSIndexPath *path = item;
BOOL isMarkable = [self boolProperty:@"markable" valueAtPath:path];
if ([[tableColumn identifier] isEqual:@"marked"]) {
[cell setEnabled:isMarkable];
}
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
// Determine if the text color will be blue due to directory being reference.
NSTextFieldCell *textCell = cell;
if (isMarkable) {
[textCell setTextColor:[NSColor blackColor]];
}
else {
[textCell setTextColor:[NSColor blueColor]];
}
if (([self deltaValuesMode]) && ([self powerMarkerMode] || ([path length] > 1))) {
NSInteger i = [[tableColumn identifier] integerValue];
if ([_deltaColumns containsIndex:i]) {
[textCell setTextColor:[NSColor orangeColor]];
}
}
}
}
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
{
[self removeSelected];
return YES;
}
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
{
[[self py] markSelected];
return YES;
}
/* don't calls saveEdits and cancelEdits */
- (void)outlineViewDidEndEditing:(HSOutlineView *)outlineView
{
}
- (void)outlineViewCancelsEdition:(HSOutlineView *)outlineView
{
}
/* Python --> Cocoa */
- (void)refresh /* Override */
{
[_rootChildrenCounts release];
_rootChildrenCounts = nil;
[super refresh];
[outlineView expandItem:nil expandChildren:YES];
}
- (void)invalidateMarkings
{
for (NSMutableDictionary *props in [itemData objectEnumerator]) {
[props removeObjectForKey:@"marked"];
}
[outlineView setNeedsDisplay:YES];
}
@end

View File

@@ -7,54 +7,62 @@ http://www.hardcoded.net/licenses/hs_license
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "Outline.h" #import "HSOutlineView.h"
#import "StatsLabel.h"
#import "ResultOutline.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
@interface MatchesView : OutlineView
- (void)keyDown:(NSEvent *)theEvent;
@end
@interface ResultWindowBase : NSWindowController @interface ResultWindowBase : NSWindowController
{ {
@protected @protected
IBOutlet PyDupeGuruBase *py; IBOutlet PyDupeGuruBase *py;
IBOutlet id app; IBOutlet id app;
IBOutlet NSSegmentedControl *deltaSwitch; IBOutlet NSSegmentedControl *deltaSwitch;
IBOutlet MatchesView *matches; IBOutlet HSOutlineView *matches;
IBOutlet NSSegmentedControl *pmSwitch; IBOutlet NSSegmentedControl *pmSwitch;
IBOutlet NSTextField *stats; IBOutlet NSTextField *stats;
IBOutlet NSMenu *columnsMenu; IBOutlet NSMenu *columnsMenu;
IBOutlet NSSearchField *filterField;
BOOL _powerMode;
BOOL _displayDelta;
NSMutableArray *_resultColumns; NSMutableArray *_resultColumns;
NSWindowController *preferencesPanel; NSWindowController *preferencesPanel;
ResultOutline *outline;
StatsLabel *statsLabel;
} }
/* Helpers */ /* Helpers */
- (void)fillColumnsMenu; - (void)fillColumnsMenu;
- (NSTableColumn *)getColumnForIdentifier:(NSInteger)aIdentifier title:(NSString *)aTitle width:(NSInteger)aWidth refCol:(NSTableColumn *)aColumn; - (NSTableColumn *)getColumnForIdentifier:(NSInteger)aIdentifier title:(NSString *)aTitle width:(NSInteger)aWidth refCol:(NSTableColumn *)aColumn;
- (NSArray *)getColumnsOrder; - (NSArray *)getColumnsOrder;
- (NSDictionary *)getColumnsWidth; - (NSDictionary *)getColumnsWidth;
- (NSArray *)getSelected:(BOOL)aDupesOnly;
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly;
- (void)initResultColumns; - (void)initResultColumns;
- (void)updatePySelection;
- (void)performPySelection:(NSArray *)aIndexPaths;
- (void)refreshStats;
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth; - (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender;
- (IBAction)changeDelta:(id)sender; - (IBAction)changeDelta:(id)sender;
- (IBAction)changePowerMarker:(id)sender; - (IBAction)changePowerMarker:(id)sender;
- (IBAction)copyMarked:(id)sender; - (IBAction)copyMarked:(id)sender;
- (IBAction)deleteMarked:(id)sender; - (IBAction)deleteMarked:(id)sender;
- (IBAction)expandAll:(id)sender;
- (IBAction)exportToXHTML:(id)sender; - (IBAction)exportToXHTML:(id)sender;
- (IBAction)filter:(id)sender;
- (IBAction)ignoreSelected:(id)sender;
- (IBAction)markAll:(id)sender;
- (IBAction)markInvert:(id)sender;
- (IBAction)markNone:(id)sender;
- (IBAction)markSelected:(id)sender;
- (IBAction)moveMarked:(id)sender; - (IBAction)moveMarked:(id)sender;
- (IBAction)openClicked:(id)sender;
- (IBAction)openSelected:(id)sender;
- (IBAction)removeMarked:(id)sender;
- (IBAction)removeSelected:(id)sender;
- (IBAction)renameSelected:(id)sender;
- (IBAction)resetColumnsToDefault:(id)sender; - (IBAction)resetColumnsToDefault:(id)sender;
- (IBAction)revealSelected:(id)sender;
- (IBAction)showPreferencesPanel:(id)sender; - (IBAction)showPreferencesPanel:(id)sender;
- (IBAction)startDuplicateScan:(id)sender;
- (IBAction)switchSelected:(id)sender; - (IBAction)switchSelected:(id)sender;
- (IBAction)toggleColumn:(id)sender; - (IBAction)toggleColumn:(id)sender;
- (IBAction)toggleDelta:(id)sender;
- (IBAction)toggleDetailsPanel:(id)sender; - (IBAction)toggleDetailsPanel:(id)sender;
- (IBAction)togglePowerMarker:(id)sender; - (IBAction)togglePowerMarker:(id)sender;

View File

@@ -14,56 +14,29 @@ http://www.hardcoded.net/licenses/hs_license
#import "AppDelegate.h" #import "AppDelegate.h"
#import "Consts.h" #import "Consts.h"
@implementation MatchesView
- (void)keyDown:(NSEvent *)theEvent
{
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
// get flags and strip the lower 16 (device dependant) bits
NSUInteger flags = ( [theEvent modifierFlags] & 0x00FF );
if (((key == NSDeleteFunctionKey) || (key == NSDeleteCharacter)) && (flags == 0))
[self sendAction:@selector(removeSelected:) to:[self delegate]];
else
if ((key == 0x20) && (flags == 0)) // Space
[self sendAction:@selector(markSelected:) to:[self delegate]];
else
[super keyDown:theEvent];
}
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if (![[tableColumn identifier] isEqual:@"0"])
return; //We only want to cover renames.
OVNode *node = item;
NSString *oldName = [[node buffer] objectAtIndex:0];
NSString *newName = object;
if (![newName isEqual:oldName])
{
BOOL renamed = n2b([(PyDupeGuruBase *)py renameSelected:newName]);
if (renamed)
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
else
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.",newName]];
}
}
@end
@implementation ResultWindowBase @implementation ResultWindowBase
- (void)awakeFromNib - (void)awakeFromNib
{ {
[self window]; [self window];
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"]; preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
outline = [[ResultOutline alloc] initWithPyParent:py view:matches];
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
[self initResultColumns]; [self initResultColumns];
[self fillColumnsMenu]; [self fillColumnsMenu];
[deltaSwitch setSelectedSegment:0];
[pmSwitch setSelectedSegment:0];
[matches setTarget:self];
[matches setDoubleAction:@selector(openClicked:)];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsChanged:) name:ResultsChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsUpdated:) name:ResultsUpdatedNotification object:nil];
} }
- (void)dealloc - (void)dealloc
{ {
[outline release];
[preferencesPanel release]; [preferencesPanel release];
[super dealloc]; [super dealloc];
} }
@@ -101,13 +74,9 @@ http://www.hardcoded.net/licenses/hs_license
//Returns an array of identifiers, in order. //Returns an array of identifiers, in order.
- (NSArray *)getColumnsOrder - (NSArray *)getColumnsOrder
{ {
NSTableColumn *col;
NSString *colId;
NSMutableArray *result = [NSMutableArray array]; NSMutableArray *result = [NSMutableArray array];
NSEnumerator *e = [[matches tableColumns] objectEnumerator]; for (NSTableColumn *col in [matches tableColumns]) {
while (col = [e nextObject]) NSString *colId = [col identifier];
{
colId = [col identifier];
[result addObject:colId]; [result addObject:colId];
} }
return result; return result;
@@ -116,48 +85,14 @@ http://www.hardcoded.net/licenses/hs_license
- (NSDictionary *)getColumnsWidth - (NSDictionary *)getColumnsWidth
{ {
NSMutableDictionary *result = [NSMutableDictionary dictionary]; NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSTableColumn *col; for (NSTableColumn *col in [matches tableColumns]) {
NSString *colId; NSString *colId = [col identifier];
NSNumber *width; NSNumber *width = [NSNumber numberWithDouble:[col width]];
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
while (col = [e nextObject])
{
colId = [col identifier];
width = [NSNumber numberWithDouble:[col width]];
[result setObject:width forKey:colId]; [result setObject:width forKey:colId];
} }
return result; return result;
} }
- (NSArray *)getSelected:(BOOL)aDupesOnly
{
if (_powerMode)
aDupesOnly = NO;
NSIndexSet *indexes = [matches selectedRowIndexes];
NSMutableArray *nodeList = [NSMutableArray array];
OVNode *node;
NSInteger i = [indexes firstIndex];
while (i != NSNotFound)
{
node = [matches itemAtRow:i];
if (!aDupesOnly || ([node level] > 1))
[nodeList addObject:node];
i = [indexes indexGreaterThanIndex:i];
}
return nodeList;
}
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly
{
NSMutableArray *r = [NSMutableArray array];
NSArray *selected = [self getSelected:aDupesOnly];
NSEnumerator *e = [selected objectEnumerator];
OVNode *node;
while (node = [e nextObject])
[r addObject:p2a([node indexPath])];
return r;
}
- (void)initResultColumns - (void)initResultColumns
{ {
// Virtual // Virtual
@@ -165,75 +100,46 @@ http://www.hardcoded.net/licenses/hs_license
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth - (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
{ {
NSTableColumn *col; for (NSMenuItem *mi in [columnsMenu itemArray]) {
NSString *colId; if ([mi state] == NSOnState) {
NSNumber *width;
NSMenuItem *mi;
//Remove all columns
NSEnumerator *e = [[columnsMenu itemArray] objectEnumerator];
while (mi = [e nextObject])
{
if ([mi state] == NSOnState)
[self toggleColumn:mi];
}
//Add columns and set widths
e = [aColumnsOrder objectEnumerator];
while (colId = [e nextObject])
{
if (![colId isEqual:@"mark"])
{
col = [_resultColumns objectAtIndex:[colId intValue]];
width = [aColumnsWidth objectForKey:[col identifier]];
mi = [columnsMenu itemWithTag:[colId intValue]];
if (width)
[col setWidth:[width floatValue]];
[self toggleColumn:mi]; [self toggleColumn:mi];
} }
} }
} //Add columns and set widths
for (NSString *colId in aColumnsOrder) {
- (void)updatePySelection NSInteger colIndex = [colId integerValue];
{ if ((colIndex == 0) && (![colId isEqual:@"0"])) {
NSArray *selection; continue;
if (_powerMode) }
selection = [py selectedPowerMarkerNodePaths]; NSTableColumn *col = [_resultColumns objectAtIndex:colIndex];
else NSNumber *width = [aColumnsWidth objectForKey:[col identifier]];
selection = [py selectedResultNodePaths]; NSMenuItem *mi = [columnsMenu itemWithTag:colIndex];
[matches selectNodePaths:selection]; if (width) {
} [col setWidth:[width floatValue]];
}
- (void)performPySelection:(NSArray *)aIndexPaths [self toggleColumn:mi];
{ }
if (_powerMode)
[py selectPowerMarkerNodePaths:aIndexPaths];
else
[py selectResultNodePaths:aIndexPaths];
}
- (void)refreshStats
{
[stats setStringValue:[py getStatLine]];
} }
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender
{
NSInteger i = n2i([py getIgnoreListCount]);
if (!i)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"Do you really want to remove all %d items from the ignore list?",i]] == NSAlertSecondButtonReturn) // NO
return;
[py clearIgnoreList];
}
- (IBAction)changeDelta:(id)sender - (IBAction)changeDelta:(id)sender
{ {
_displayDelta = [deltaSwitch selectedSegment] == 1; [outline setDeltaValuesMode:[deltaSwitch selectedSegment] == 1];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches reloadData];
[self expandAll:nil];
} }
- (IBAction)changePowerMarker:(id)sender - (IBAction)changePowerMarker:(id)sender
{ {
_powerMode = [pmSwitch selectedSegment] == 1; [outline setPowerMarkerMode:[pmSwitch selectedSegment] == 1];
if (_powerMode)
[matches setTag:2];
else
[matches setTag:0];
[self expandAll:nil];
[self outlineView:matches didClickTableColumn:nil];
[self updatePySelection];
} }
- (IBAction)copyMarked:(id)sender - (IBAction)copyMarked:(id)sender
@@ -267,18 +173,50 @@ http://www.hardcoded.net/licenses/hs_license
[py deleteMarked]; [py deleteMarked];
} }
- (IBAction)expandAll:(id)sender
{
for (NSInteger i=0;i < [matches numberOfRows];i++)
[matches expandItem:[matches itemAtRow:i]];
}
- (IBAction)exportToXHTML:(id)sender - (IBAction)exportToXHTML:(id)sender
{ {
NSString *exported = [py exportToXHTMLwithColumns:[self getColumnsOrder]]; NSString *exported = [py exportToXHTMLwithColumns:[self getColumnsOrder]];
[[NSWorkspace sharedWorkspace] openFile:exported]; [[NSWorkspace sharedWorkspace] openFile:exported];
} }
- (IBAction)filter:(id)sender
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
[py applyFilter:[filterField stringValue]];
}
- (IBAction)ignoreSelected:(id)sender
{
NSInteger selectedDupeCount = [outline selectedDupeCount];
if (!selectedDupeCount)
return;
NSString *msg = [NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",selectedDupeCount];
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
return;
[py addSelectedToIgnoreList];
}
- (IBAction)markAll:(id)sender
{
[py markAll];
}
- (IBAction)markInvert:(id)sender
{
[py markInvert];
}
- (IBAction)markNone:(id)sender
{
[py markNone];
}
- (IBAction)markSelected:(id)sender
{
[py toggleSelectedMark];
}
- (IBAction)moveMarked:(id)sender - (IBAction)moveMarked:(id)sender
{ {
NSInteger mark_count = [[py getMarkCount] intValue]; NSInteger mark_count = [[py getMarkCount] intValue];
@@ -299,32 +237,65 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (IBAction)openClicked:(id)sender
{
if ([matches clickedRow] < 0) {
return;
}
[matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO];
[py openSelected];
}
- (IBAction)openSelected:(id)sender
{
[py openSelected];
}
- (IBAction)removeMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return;
[py removeMarked];
}
- (IBAction)removeSelected:(id)sender
{
[outline removeSelected];
}
- (IBAction)renameSelected:(id)sender
{
NSInteger col = [matches columnWithIdentifier:@"0"];
NSInteger row = [matches selectedRow];
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
}
- (IBAction)resetColumnsToDefault:(id)sender - (IBAction)resetColumnsToDefault:(id)sender
{ {
// Virtual // Virtual
} }
- (IBAction)revealSelected:(id)sender
{
[py revealSelected];
}
- (IBAction)showPreferencesPanel:(id)sender - (IBAction)showPreferencesPanel:(id)sender
{ {
[preferencesPanel showWindow:sender]; [preferencesPanel showWindow:sender];
} }
- (IBAction)startDuplicateScan:(id)sender
{
// Virtual
}
- (IBAction)switchSelected:(id)sender - (IBAction)switchSelected:(id)sender
{ {
// It might look like a complicated way to get the length of the current dupe list on the py side
// but after a lot of fussing around, believe it or not, it actually is.
NSInteger matchesTag = _powerMode ? 2 : 0;
NSInteger startLen = [[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count];
[self performPySelection:[self getSelectedPaths:YES]];
[py makeSelectedReference]; [py makeSelectedReference];
[self performPySelection:[self getSelectedPaths:NO]];
// In some cases (when in a filtered view in Power Marker mode, it's possible that the demoted
// ref is not a part of the filter, making the table smaller. In those cases, we want to do a
// complete reload of the table to avoid a crash.
if ([[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count] == startLen)
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsUpdatedNotification object:self];
else
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
} }
- (IBAction)toggleColumn:(id)sender - (IBAction)toggleColumn:(id)sender
@@ -347,6 +318,15 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (IBAction)toggleDelta:(id)sender
{
if ([deltaSwitch selectedSegment] == 1)
[deltaSwitch setSelectedSegment:0];
else
[deltaSwitch setSelectedSegment:1];
[self changeDelta:sender];
}
- (IBAction)toggleDetailsPanel:(id)sender - (IBAction)toggleDetailsPanel:(id)sender
{ {
[[(AppDelegateBase *)app detailsPanel] toggleVisibility]; [[(AppDelegateBase *)app detailsPanel] toggleVisibility];
@@ -361,21 +341,6 @@ http://www.hardcoded.net/licenses/hs_license
[self changePowerMarker:sender]; [self changePowerMarker:sender];
} }
/* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView didClickTableColumn:(NSTableColumn *)tableColumn
{
if ([[outlineView sortDescriptors] count] < 1)
return;
NSSortDescriptor *sd = [[outlineView sortDescriptors] objectAtIndex:0];
if (_powerMode)
[py sortDupesBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
else
[py sortGroupsBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
[matches reloadData];
[self expandAll:nil];
}
/* Notifications */ /* Notifications */
- (void)windowWillClose:(NSNotification *)aNotification - (void)windowWillClose:(NSNotification *)aNotification
{ {
@@ -384,7 +349,6 @@ http://www.hardcoded.net/licenses/hs_license
- (void)jobCompleted:(NSNotification *)aNotification - (void)jobCompleted:(NSNotification *)aNotification
{ {
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
NSInteger r = n2i([py getOperationalErrorCount]); NSInteger r = n2i([py getOperationalErrorCount]);
id lastAction = [[ProgressController mainProgressController] jobId]; id lastAction = [[ProgressController mainProgressController] jobId];
if ([lastAction isEqualTo:jobCopy]) { if ([lastAction isEqualTo:jobCopy]) {
@@ -409,7 +373,7 @@ http://www.hardcoded.net/licenses/hs_license
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."]; [Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
} }
else if ([lastAction isEqualTo:jobScan]) { else if ([lastAction isEqualTo:jobScan]) {
NSInteger groupCount = [[py getOutlineView:0 childCountsForPath:[NSArray array]] count]; NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil];
if (groupCount == 0) if (groupCount == 0)
[Dialogs showMessage:@"No duplicates found."]; [Dialogs showMessage:@"No duplicates found."];
} }
@@ -430,7 +394,6 @@ http://www.hardcoded.net/licenses/hs_license
NSString *desc = [ui valueForKey:@"desc"]; NSString *desc = [ui valueForKey:@"desc"];
[[ProgressController mainProgressController] setJobDesc:desc]; [[ProgressController mainProgressController] setJobDesc:desc];
NSString *jobid = [ui valueForKey:@"jobid"]; NSString *jobid = [ui valueForKey:@"jobid"];
// NSLog(jobid);
[[ProgressController mainProgressController] setJobId:jobid]; [[ProgressController mainProgressController] setJobId:jobid];
[[ProgressController mainProgressController] showSheetForParent:[self window]]; [[ProgressController mainProgressController] showSheetForParent:[self window]];
} }
@@ -441,31 +404,6 @@ http://www.hardcoded.net/licenses/hs_license
[Dialogs showMessage:msg]; [Dialogs showMessage:msg];
} }
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
[self performPySelection:[self getSelectedPaths:NO]];
}
- (void)resultsChanged:(NSNotification *)aNotification
{
[matches reloadData];
[self expandAll:nil];
[self outlineViewSelectionDidChange:nil];
[self refreshStats];
}
- (void)resultsMarkingChanged:(NSNotification *)aNotification
{
[matches invalidateMarkings];
[self refreshStats];
}
- (void)resultsUpdated:(NSNotification *)aNotification
{
[matches invalidateBuffers];
[matches invalidateMarkings];
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{ {
return ![[ProgressController mainProgressController] isShown]; return ![[ProgressController mainProgressController] isShown];

19
cocoa/base/StatsLabel.h Normal file
View File

@@ -0,0 +1,19 @@
/*
Copyright 2010 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 <Cocoa/Cocoa.h>
#import "HSGUIController.h"
#import "PyStatsLabel.h"
@interface StatsLabel : HSGUIController
{
NSTextField *labelView;
}
- (id)initWithPyParent:(id)aPyParent labelView:(NSTextField *)aLabelView;
- (PyStatsLabel *)py;
@end

38
cocoa/base/StatsLabel.m Normal file
View File

@@ -0,0 +1,38 @@
/*
Copyright 2010 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 "StatsLabel.h"
#import "Utils.h"
@implementation StatsLabel
- (id)initWithPyParent:(id)aPyParent labelView:(NSTextField *)aLabelView
{
self = [super initWithPyClassName:@"PyStatsLabel" pyParent:aPyParent];
labelView = [aLabelView retain];
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[labelView release];
[super dealloc];
}
- (PyStatsLabel *)py
{
return (PyStatsLabel *)py;
}
/* Python --> Cocoa */
- (void)refresh
{
[labelView setStringValue:[[self py] display]];
}
@end

View File

@@ -2,17 +2,17 @@
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10"> <archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data> <data>
<int key="IBDocument.SystemTarget">1050</int> <int key="IBDocument.SystemTarget">1050</int>
<string key="IBDocument.SystemVersion">10B504</string> <string key="IBDocument.SystemVersion">10C540</string>
<string key="IBDocument.InterfaceBuilderVersion">740</string> <string key="IBDocument.InterfaceBuilderVersion">740</string>
<string key="IBDocument.AppKitVersion">1038.2</string> <string key="IBDocument.AppKitVersion">1038.25</string>
<string key="IBDocument.HIToolboxVersion">437.00</string> <string key="IBDocument.HIToolboxVersion">458.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions"> <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">740</string> <string key="NS.object.0">740</string>
</object> </object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<integer value="6"/> <integer value="50"/>
</object> </object>
<object class="NSArray" key="IBDocument.PluginDependencies"> <object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
@@ -70,7 +70,6 @@
<int key="NSvFlags">256</int> <int key="NSvFlags">256</int>
<string key="NSFrameSize">{327, 165}</string> <string key="NSFrameSize">{327, 165}</string>
<reference key="NSSuperview" ref="514281221"/> <reference key="NSSuperview" ref="514281221"/>
<int key="NSTag">1</int>
<bool key="NSEnabled">YES</bool> <bool key="NSEnabled">YES</bool>
<object class="NSTableHeaderView" key="NSHeaderView" id="885660940"> <object class="NSTableHeaderView" key="NSHeaderView" id="885660940">
<reference key="NSNextResponder" ref="395832192"/> <reference key="NSNextResponder" ref="395832192"/>
@@ -88,7 +87,7 @@
<object class="NSMutableArray" key="NSTableColumns"> <object class="NSMutableArray" key="NSTableColumns">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSTableColumn" id="547470852"> <object class="NSTableColumn" id="547470852">
<string key="NSIdentifier">0</string> <string key="NSIdentifier">name</string>
<double key="NSWidth">236</double> <double key="NSWidth">236</double>
<double key="NSMinWidth">16</double> <double key="NSMinWidth">16</double>
<double key="NSMaxWidth">1000</double> <double key="NSMaxWidth">1000</double>
@@ -142,7 +141,7 @@
<reference key="NSTableView" ref="10140319"/> <reference key="NSTableView" ref="10140319"/>
</object> </object>
<object class="NSTableColumn" id="50798966"> <object class="NSTableColumn" id="50798966">
<string key="NSIdentifier">1</string> <string key="NSIdentifier">state</string>
<double key="NSWidth">85.35595703125</double> <double key="NSWidth">85.35595703125</double>
<double key="NSMinWidth">30.35595703125</double> <double key="NSMinWidth">30.35595703125</double>
<double key="NSMaxWidth">1000</double> <double key="NSMaxWidth">1000</double>
@@ -173,15 +172,41 @@
<string key="NSKeyEquivalent"/> <string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">400</int> <int key="NSPeriodicDelay">400</int>
<int key="NSPeriodicInterval">75</int> <int key="NSPeriodicInterval">75</int>
<nil key="NSMenuItem"/> <object class="NSMenuItem" key="NSMenuItem" id="71151438">
<reference key="NSMenu" ref="104112446"/>
<string key="NSTitle">Normal</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<int key="NSState">1</int>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="867721721"/>
</object>
<bool key="NSMenuItemRespectAlignment">YES</bool> <bool key="NSMenuItemRespectAlignment">YES</bool>
<object class="NSMenu" key="NSMenu" id="104112446"> <object class="NSMenu" key="NSMenu" id="104112446">
<string key="NSTitle"/> <string key="NSTitle">Normal</string>
<object class="NSMutableArray" key="NSMenuItems"> <object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="71151438"/>
<object class="NSMenuItem" id="828402206">
<reference key="NSMenu" ref="104112446"/>
<string key="NSTitle">Reference</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="867721721"/>
</object>
<object class="NSMenuItem" id="142495353">
<reference key="NSMenu" ref="104112446"/>
<string key="NSTitle">Excluded</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<string key="NSAction">_popUpItemAction:</string>
<reference key="NSTarget" ref="867721721"/>
</object>
</object> </object>
<bool key="NSNoAutoenable">YES</bool>
<bool key="NSMenuExcludeMarkColumn">YES</bool>
</object> </object>
<int key="NSSelectedIndex">-1</int>
<int key="NSPreferredEdge">3</int> <int key="NSPreferredEdge">3</int>
<bool key="NSUsesItemFromMenu">YES</bool> <bool key="NSUsesItemFromMenu">YES</bool>
<bool key="NSAltersState">YES</bool> <bool key="NSAltersState">YES</bool>
@@ -189,6 +214,7 @@
</object> </object>
<int key="NSResizingMask">2</int> <int key="NSResizingMask">2</int>
<bool key="NSIsResizeable">YES</bool> <bool key="NSIsResizeable">YES</bool>
<bool key="NSIsEditable">YES</bool>
<reference key="NSTableView" ref="10140319"/> <reference key="NSTableView" ref="10140319"/>
</object> </object>
</object> </object>
@@ -415,30 +441,6 @@
</object> </object>
<int key="connectionID">19</int> <int key="connectionID">19</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">nextKeyView</string>
<reference key="source" ref="963602908"/>
<reference key="destination" ref="10140319"/>
</object>
<int key="connectionID">20</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">nextKeyView</string>
<reference key="source" ref="10140319"/>
<reference key="destination" ref="630693842"/>
</object>
<int key="connectionID">21</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">nextKeyView</string>
<reference key="source" ref="630693842"/>
<reference key="destination" ref="963602908"/>
</object>
<int key="connectionID">22</int>
</object>
<object class="IBConnectionRecord"> <object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection"> <object class="IBActionConnection" key="connection">
<string key="label">popupAddDirectoryMenu:</string> <string key="label">popupAddDirectoryMenu:</string>
@@ -471,22 +473,6 @@
</object> </object>
<int key="connectionID">26</int> <int key="connectionID">26</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">directories</string>
<reference key="source" ref="566600593"/>
<reference key="destination" ref="10140319"/>
</object>
<int key="connectionID">27</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="10140319"/>
<reference key="destination" ref="566600593"/>
</object>
<int key="connectionID">29</int>
</object>
<object class="IBConnectionRecord"> <object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection"> <object class="IBActionConnection" key="connection">
<string key="label">performClose:</string> <string key="label">performClose:</string>
@@ -503,6 +489,14 @@
</object> </object>
<int key="connectionID">43</int> <int key="connectionID">43</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">outlineView</string>
<reference key="source" ref="566600593"/>
<reference key="destination" ref="10140319"/>
</object>
<int key="connectionID">54</int>
</object>
</object> </object>
<object class="IBMutableOrderedSet" key="objectRecords"> <object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects"> <object class="NSArray" key="orderedObjects">
@@ -685,6 +679,12 @@
<object class="IBObjectRecord"> <object class="IBObjectRecord">
<int key="objectID">50</int> <int key="objectID">50</int>
<reference key="object" ref="104112446"/> <reference key="object" ref="104112446"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="71151438"/>
<reference ref="828402206"/>
<reference ref="142495353"/>
</object>
<reference key="parent" ref="867721721"/> <reference key="parent" ref="867721721"/>
</object> </object>
<object class="IBObjectRecord"> <object class="IBObjectRecord">
@@ -702,6 +702,21 @@
<reference key="object" ref="885660940"/> <reference key="object" ref="885660940"/>
<reference key="parent" ref="242279311"/> <reference key="parent" ref="242279311"/>
</object> </object>
<object class="IBObjectRecord">
<int key="objectID">55</int>
<reference key="object" ref="71151438"/>
<reference key="parent" ref="104112446"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">56</int>
<reference key="object" ref="828402206"/>
<reference key="parent" ref="104112446"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">57</int>
<reference key="object" ref="142495353"/>
<reference key="parent" ref="104112446"/>
</object>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="flattenedProperties"> <object class="NSMutableDictionary" key="flattenedProperties">
@@ -739,6 +754,7 @@
<string>5.ImportedFromIB2</string> <string>5.ImportedFromIB2</string>
<string>5.windowTemplate.hasMinSize</string> <string>5.windowTemplate.hasMinSize</string>
<string>5.windowTemplate.minSize</string> <string>5.windowTemplate.minSize</string>
<string>50.IBEditorWindowLastContentRect</string>
<string>50.IBPluginDependency</string> <string>50.IBPluginDependency</string>
<string>51.IBPluginDependency</string> <string>51.IBPluginDependency</string>
<string>51.IBShouldRemoveOnLegacySave</string> <string>51.IBShouldRemoveOnLegacySave</string>
@@ -746,6 +762,9 @@
<string>52.IBShouldRemoveOnLegacySave</string> <string>52.IBShouldRemoveOnLegacySave</string>
<string>53.IBPluginDependency</string> <string>53.IBPluginDependency</string>
<string>53.IBShouldRemoveOnLegacySave</string> <string>53.IBShouldRemoveOnLegacySave</string>
<string>55.IBPluginDependency</string>
<string>56.IBPluginDependency</string>
<string>57.IBPluginDependency</string>
<string>6.IBPluginDependency</string> <string>6.IBPluginDependency</string>
<string>6.ImportedFromIB2</string> <string>6.ImportedFromIB2</string>
<string>7.IBPluginDependency</string> <string>7.IBPluginDependency</string>
@@ -761,7 +780,7 @@
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>DirectoryOutline</string> <string>HSOutlineView</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -788,6 +807,7 @@
<boolean value="YES"/> <boolean value="YES"/>
<boolean value="YES"/> <boolean value="YES"/>
<string>{369, 269}</string> <string>{369, 269}</string>
<string>{{98, 740}, {327, 63}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
@@ -796,6 +816,9 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
@@ -821,27 +844,11 @@
</object> </object>
</object> </object>
<nil key="sourceID"/> <nil key="sourceID"/>
<int key="maxID">53</int> <int key="maxID">57</int>
</object> </object>
<object class="IBClassDescriber" key="IBDocument.Classes"> <object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions"> <object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">DirectoryOutline</string>
<string key="superclassName">OutlineView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="462913745">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">dgbase/DirectoryPanel.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">DirectoryPanel</string>
<string key="superclassName">NSWindowController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">DirectoryPanel.h</string>
</object>
</object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">DirectoryPanel</string> <string key="className">DirectoryPanel</string>
<string key="superclassName">NSWindowController</string> <string key="superclassName">NSWindowController</string>
@@ -850,7 +857,6 @@
<object class="NSArray" key="dict.sortedKeys"> <object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<string>askForDirectory:</string> <string>askForDirectory:</string>
<string>changeDirectoryState:</string>
<string>popupAddDirectoryMenu:</string> <string>popupAddDirectoryMenu:</string>
<string>removeSelectedDirectory:</string> <string>removeSelectedDirectory:</string>
<string>toggleVisible:</string> <string>toggleVisible:</string>
@@ -861,7 +867,6 @@
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="outlets"> <object class="NSMutableDictionary" key="outlets">
@@ -869,60 +874,29 @@
<object class="NSArray" key="dict.sortedKeys"> <object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<string>addButtonPopUp</string> <string>addButtonPopUp</string>
<string>directories</string> <string>outlineView</string>
<string>removeButton</string> <string>removeButton</string>
</object> </object>
<object class="NSMutableArray" key="dict.values"> <object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<string>NSPopUpButton</string> <string>NSPopUpButton</string>
<string>NSOutlineView</string> <string>HSOutlineView</string>
<string>NSButton</string> <string>NSButton</string>
</object> </object>
</object> </object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../base/DirectoryPanel.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">DirectoryPanel</string>
<string key="superclassName">NSWindowController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBUserSource</string> <string key="majorKey">IBUserSource</string>
<string key="minorKey"/> <string key="minorKey"/>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription">
<string key="className">DirectoryPanel</string>
<string key="superclassName">NSWindowController</string>
<object class="NSMutableDictionary" key="actions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>askForDirectory:</string>
<string>changeDirectoryState:</string>
<string>popupAddDirectoryMenu:</string>
<string>removeSelectedDirectory:</string>
<string>toggleVisible:</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
</object>
</object>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>addButtonPopUp</string>
<string>directories</string>
<string>removeButton</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSPopUpButton</string>
<string>DirectoryOutline</string>
<string>NSButton</string>
</object>
</object>
<reference key="sourceIdentifier" ref="462913745"/>
</object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">FirstResponder</string> <string key="className">FirstResponder</string>
<string key="superclassName">NSObject</string> <string key="superclassName">NSObject</string>
@@ -932,40 +906,27 @@
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">OutlineView</string> <string key="className">HSOutlineView</string>
<string key="superclassName">NSOutlineView</string> <string key="superclassName">NSOutlineView</string>
<object class="NSMutableDictionary" key="outlets"> <object class="IBClassDescriptionSource" key="sourceIdentifier" id="53364925">
<string key="NS.key.0">py</string>
<string key="NS.object.0">PyApp</string>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/Outline.h</string> <string key="minorKey">../views/HSOutlineView.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">OutlineView</string> <string key="className">NSObject</string>
<string key="superclassName">NSOutlineView</string> <reference key="sourceIdentifier" ref="53364925"/>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> </object>
<string key="majorKey">IBUserSource</string> <object class="IBPartialClassDescription">
<string key="minorKey"/> <string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="42597526">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../views/NSTableViewAdditions.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">PyApp</string> <string key="className">NSTableView</string>
<string key="superclassName">PyRegistrable</string> <reference key="sourceIdentifier" ref="42597526"/>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/PyApp.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">PyRegistrable</string>
<string key="superclassName">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/PyRegistrable.h</string>
</object>
</object> </object>
</object> </object>
<object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+"> <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+">
@@ -1497,7 +1458,7 @@
<integer value="3000" key="NS.object.0"/> <integer value="3000" key="NS.object.0"/>
</object> </object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<string key="IBDocument.LastKnownRelativeProjectPath">../../dupeguru.xcodeproj</string> <string key="IBDocument.LastKnownRelativeProjectPath">../../se/dupeguru.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int> <int key="IBDocument.defaultPropertyAccessControl">3</int>
</data> </data>
</archive> </archive>

View File

@@ -12,8 +12,8 @@
</object> </object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> <object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<integer value="219"/>
<integer value="29"/> <integer value="29"/>
<integer value="21"/>
</object> </object>
<object class="NSArray" key="IBDocument.PluginDependencies"> <object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
@@ -710,7 +710,7 @@
<object class="NSMutableArray" key="NSTableColumns"> <object class="NSMutableArray" key="NSTableColumns">
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSTableColumn" id="430098394"> <object class="NSTableColumn" id="430098394">
<string key="NSIdentifier">mark</string> <string key="NSIdentifier">marked</string>
<double key="NSWidth">47</double> <double key="NSWidth">47</double>
<double key="NSMinWidth">16</double> <double key="NSMinWidth">16</double>
<double key="NSMaxWidth">1000</double> <double key="NSMaxWidth">1000</double>
@@ -752,6 +752,7 @@
<int key="NSPeriodicDelay">400</int> <int key="NSPeriodicDelay">400</int>
<int key="NSPeriodicInterval">75</int> <int key="NSPeriodicInterval">75</int>
</object> </object>
<bool key="NSIsEditable">YES</bool>
<reference key="NSTableView" ref="40047569"/> <reference key="NSTableView" ref="40047569"/>
</object> </object>
<object class="NSTableColumn" id="932540235"> <object class="NSTableColumn" id="932540235">
@@ -1667,14 +1668,6 @@
</object> </object>
<int key="connectionID">212</int> <int key="connectionID">212</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">matches</string>
<reference key="source" ref="339936126"/>
<reference key="destination" ref="40047569"/>
</object>
<int key="connectionID">245</int>
</object>
<object class="IBConnectionRecord"> <object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection"> <object class="IBOutletConnection" key="connection">
<string key="label">initialFirstResponder</string> <string key="label">initialFirstResponder</string>
@@ -1683,22 +1676,6 @@
</object> </object>
<int key="connectionID">279</int> <int key="connectionID">279</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="40047569"/>
<reference key="destination" ref="339936126"/>
</object>
<int key="connectionID">410</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">markToggle:</string>
<reference key="source" ref="339936126"/>
<reference key="destination" ref="705360835"/>
</object>
<int key="connectionID">414</int>
</object>
<object class="IBConnectionRecord"> <object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection"> <object class="IBOutletConnection" key="connection">
<string key="label">stats</string> <string key="label">stats</string>
@@ -1939,14 +1916,6 @@
</object> </object>
<int key="connectionID">758</int> <int key="connectionID">758</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">py</string>
<reference key="source" ref="40047569"/>
<reference key="destination" ref="875360857"/>
</object>
<int key="connectionID">764</int>
</object>
<object class="IBConnectionRecord"> <object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection"> <object class="IBActionConnection" key="connection">
<string key="label">removeSelected:</string> <string key="label">removeSelected:</string>
@@ -2227,6 +2196,14 @@
</object> </object>
<int key="connectionID">1175</int> <int key="connectionID">1175</int>
</object> </object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">matches</string>
<reference key="source" ref="339936126"/>
<reference key="destination" ref="40047569"/>
</object>
<int key="connectionID">1176</int>
</object>
</object> </object>
<object class="IBMutableOrderedSet" key="objectRecords"> <object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects"> <object class="NSArray" key="orderedObjects">
@@ -3406,7 +3383,7 @@
<string>{340, 340}</string> <string>{340, 340}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>MatchesView</string> <string>HSOutlineView</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/> <boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string> <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -3584,7 +3561,7 @@
</object> </object>
</object> </object>
<nil key="sourceID"/> <nil key="sourceID"/>
<int key="maxID">1175</int> <int key="maxID">1176</int>
</object> </object>
<object class="IBClassDescriber" key="IBDocument.Classes"> <object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions"> <object class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -3663,7 +3640,7 @@
</object> </object>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">dgbase/AppDelegate.h</string> <string key="minorKey">../base/AppDelegate.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
@@ -3675,19 +3652,22 @@
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">MatchesView</string> <string key="className">HSOutlineView</string>
<string key="superclassName">OutlineView</string> <string key="superclassName">NSOutlineView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="417275989"> <object class="IBClassDescriptionSource" key="sourceIdentifier" id="384069338">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">dgbase/ResultWindow.h</string> <string key="minorKey">../views/HSOutlineView.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">MatchesView</string> <string key="className">NSObject</string>
<string key="superclassName">OutlineView</string> <reference key="sourceIdentifier" ref="384069338"/>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> </object>
<string key="majorKey">IBUserSource</string> <object class="IBPartialClassDescription">
<string key="minorKey"/> <string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="653924221">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../views/NSTableViewAdditions.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
@@ -3699,31 +3679,15 @@
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">OutlineView</string> <string key="className">NSTableView</string>
<string key="superclassName">NSOutlineView</string> <reference key="sourceIdentifier" ref="653924221"/>
<object class="NSMutableDictionary" key="outlets">
<string key="NS.key.0">py</string>
<string key="NS.object.0">PyApp</string>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/Outline.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">OutlineView</string>
<string key="superclassName">NSOutlineView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBUserSource</string>
<string key="minorKey"/>
</object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">PyApp</string> <string key="className">PyApp</string>
<string key="superclassName">PyRegistrable</string> <string key="superclassName">PyRegistrable</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/PyApp.h</string> <string key="minorKey">../PyApp.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
@@ -3755,7 +3719,7 @@
<string key="superclassName">PyApp</string> <string key="superclassName">PyApp</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">dgbase/PyDupeGuru.h</string> <string key="minorKey">../base/PyDupeGuru.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
@@ -3763,7 +3727,7 @@
<string key="superclassName">NSObject</string> <string key="superclassName">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/PyRegistrable.h</string> <string key="minorKey">../PyRegistrable.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
@@ -3797,7 +3761,7 @@
</object> </object>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">cocoalib/RecentDirectories.h</string> <string key="minorKey">../RecentDirectories.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
@@ -3812,123 +3776,14 @@
<string key="className">ResultWindow</string> <string key="className">ResultWindow</string>
<string key="superclassName">ResultWindowBase</string> <string key="superclassName">ResultWindowBase</string>
<object class="NSMutableDictionary" key="actions"> <object class="NSMutableDictionary" key="actions">
<bool key="EncodedWithXMLCoder">YES</bool> <string key="NS.key.0">removeDeadTracks:</string>
<object class="NSArray" key="dict.sortedKeys"> <string key="NS.object.0">id</string>
<bool key="EncodedWithXMLCoder">YES</bool>
<string>clearIgnoreList:</string>
<string>filter:</string>
<string>ignoreSelected:</string>
<string>markAll:</string>
<string>markInvert:</string>
<string>markNone:</string>
<string>markSelected:</string>
<string>markToggle:</string>
<string>openSelected:</string>
<string>refresh:</string>
<string>removeMarked:</string>
<string>removeSelected:</string>
<string>renameSelected:</string>
<string>resetColumnsToDefault:</string>
<string>revealSelected:</string>
<string>startDuplicateScan:</string>
<string>toggleDelta:</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
</object>
</object>
<object class="NSMutableDictionary" key="outlets">
<string key="NS.key.0">filterField</string>
<string key="NS.object.0">NSSearchField</string>
</object> </object>
<object class="IBClassDescriptionSource" key="sourceIdentifier"> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string> <string key="majorKey">IBProjectSource</string>
<string key="minorKey">ResultWindow.h</string> <string key="minorKey">ResultWindow.h</string>
</object> </object>
</object> </object>
<object class="IBPartialClassDescription">
<string key="className">ResultWindow</string>
<string key="superclassName">ResultWindowBase</string>
<object class="NSMutableDictionary" key="actions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>changeDelta:</string>
<string>changePowerMarker:</string>
<string>collapseAll:</string>
<string>copyMarked:</string>
<string>deleteMarked:</string>
<string>expandAll:</string>
<string>exportToXHTML:</string>
<string>moveMarked:</string>
<string>switchSelected:</string>
<string>togglePowerMarker:</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
</object>
</object>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>actionMenuView</string>
<string>app</string>
<string>deltaSwitch</string>
<string>deltaSwitchView</string>
<string>filterFieldView</string>
<string>matches</string>
<string>pmSwitch</string>
<string>pmSwitchView</string>
<string>py</string>
<string>stats</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSView</string>
<string>id</string>
<string>NSSegmentedControl</string>
<string>NSView</string>
<string>NSView</string>
<string>MatchesView</string>
<string>NSSegmentedControl</string>
<string>NSView</string>
<string>PyDupeGuru</string>
<string>NSTextField</string>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBUserSource</string>
<string key="minorKey"/>
</object>
</object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">ResultWindowBase</string> <string key="className">ResultWindowBase</string>
<string key="superclassName">NSWindowController</string> <string key="superclassName">NSWindowController</string>
@@ -3938,15 +3793,29 @@
<bool key="EncodedWithXMLCoder">YES</bool> <bool key="EncodedWithXMLCoder">YES</bool>
<string>changeDelta:</string> <string>changeDelta:</string>
<string>changePowerMarker:</string> <string>changePowerMarker:</string>
<string>clearIgnoreList:</string>
<string>copyMarked:</string> <string>copyMarked:</string>
<string>deleteMarked:</string> <string>deleteMarked:</string>
<string>expandAll:</string>
<string>exportToXHTML:</string> <string>exportToXHTML:</string>
<string>filter:</string>
<string>ignoreSelected:</string>
<string>markAll:</string>
<string>markInvert:</string>
<string>markNone:</string>
<string>markSelected:</string>
<string>moveMarked:</string> <string>moveMarked:</string>
<string>openClicked:</string>
<string>openSelected:</string>
<string>removeMarked:</string>
<string>removeSelected:</string>
<string>renameSelected:</string>
<string>resetColumnsToDefault:</string> <string>resetColumnsToDefault:</string>
<string>revealSelected:</string>
<string>showPreferencesPanel:</string> <string>showPreferencesPanel:</string>
<string>startDuplicateScan:</string>
<string>switchSelected:</string> <string>switchSelected:</string>
<string>toggleColumn:</string> <string>toggleColumn:</string>
<string>toggleDelta:</string>
<string>toggleDetailsPanel:</string> <string>toggleDetailsPanel:</string>
<string>togglePowerMarker:</string> <string>togglePowerMarker:</string>
</object> </object>
@@ -3965,6 +3834,20 @@
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string> <string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
<string>id</string>
</object> </object>
</object> </object>
<object class="NSMutableDictionary" key="outlets"> <object class="NSMutableDictionary" key="outlets">
@@ -3974,6 +3857,7 @@
<string>app</string> <string>app</string>
<string>columnsMenu</string> <string>columnsMenu</string>
<string>deltaSwitch</string> <string>deltaSwitch</string>
<string>filterField</string>
<string>matches</string> <string>matches</string>
<string>pmSwitch</string> <string>pmSwitch</string>
<string>py</string> <string>py</string>
@@ -3984,13 +3868,17 @@
<string>id</string> <string>id</string>
<string>NSMenu</string> <string>NSMenu</string>
<string>NSSegmentedControl</string> <string>NSSegmentedControl</string>
<string>MatchesView</string> <string>NSSearchField</string>
<string>HSOutlineView</string>
<string>NSSegmentedControl</string> <string>NSSegmentedControl</string>
<string>PyDupeGuruBase</string> <string>PyDupeGuruBase</string>
<string>NSTextField</string> <string>NSTextField</string>
</object> </object>
</object> </object>
<reference key="sourceIdentifier" ref="417275989"/> <object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../base/ResultWindow.h</string>
</object>
</object> </object>
<object class="IBPartialClassDescription"> <object class="IBPartialClassDescription">
<string key="className">SUUpdater</string> <string key="className">SUUpdater</string>
@@ -4612,7 +4500,7 @@
<integer value="3000" key="NS.object.0"/> <integer value="3000" key="NS.object.0"/>
</object> </object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<string key="IBDocument.LastKnownRelativeProjectPath">../../dupeguru.xcodeproj</string> <string key="IBDocument.LastKnownRelativeProjectPath">../../me/dupeguru.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int> <int key="IBDocument.defaultPropertyAccessControl">3</int>
</data> </data>
</archive> </archive>

View File

@@ -23,7 +23,7 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>hsft</string> <string>hsft</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>5.7.1</string> <string>5.7.2</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>
<string>MainMenu</string> <string>MainMenu</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>

View File

@@ -7,32 +7,9 @@ http://www.hardcoded.net/licenses/hs_license
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "../../cocoalib/Outline.h"
#import "../base/ResultWindow.h" #import "../base/ResultWindow.h"
#import "DirectoryPanel.h" #import "DirectoryPanel.h"
@interface ResultWindow : ResultWindowBase @interface ResultWindow : ResultWindowBase {}
{
IBOutlet NSSearchField *filterField;
NSString *_lastAction;
NSMutableIndexSet *_deltaColumns;
}
- (IBAction)clearIgnoreList:(id)sender;
- (IBAction)filter:(id)sender;
- (IBAction)ignoreSelected:(id)sender;
- (IBAction)markAll:(id)sender;
- (IBAction)markInvert:(id)sender;
- (IBAction)markNone:(id)sender;
- (IBAction)markSelected:(id)sender;
- (IBAction)markToggle:(id)sender;
- (IBAction)openSelected:(id)sender;
- (IBAction)refresh:(id)sender;
- (IBAction)removeDeadTracks:(id)sender; - (IBAction)removeDeadTracks:(id)sender;
- (IBAction)removeMarked:(id)sender;
- (IBAction)removeSelected:(id)sender;
- (IBAction)renameSelected:(id)sender;
- (IBAction)revealSelected:(id)sender;
- (IBAction)startDuplicateScan:(id)sender;
- (IBAction)toggleDelta:(id)sender;
@end @end

View File

@@ -20,130 +20,17 @@ http://www.hardcoded.net/licenses/hs_license
{ {
[super awakeFromNib]; [super awakeFromNib];
[[self window] setTitle:@"dupeGuru Music Edition"]; [[self window] setTitle:@"dupeGuru Music Edition"];
_displayDelta = NO; NSMutableIndexSet *deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
_powerMode = NO; [deltaColumns removeIndex:6];
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain]; [outline setDeltaColumns:deltaColumns];
[_deltaColumns removeIndex:6];
[deltaSwitch setSelectedSegment:0];
[pmSwitch setSelectedSegment:0];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches setTarget:self];
[matches setDoubleAction:@selector(openSelected:)];
[self refreshStats];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
} }
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender
{
NSInteger i = n2i([py getIgnoreListCount]);
if (!i)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"Do you really want to remove all %d items from the ignore list?",i]] == NSAlertSecondButtonReturn) // NO
return;
[py clearIgnoreList];
}
- (IBAction)filter:(id)sender
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
[py applyFilter:[filterField stringValue]];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)ignoreSelected:(id)sender
{
NSArray *nodeList = [self getSelected:YES];
if (![nodeList count])
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
return;
[self performPySelection:[self getSelectedPaths:YES]];
[py addSelectedToIgnoreList];
[py removeSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)markAll:(id)sender
{
[py markAll];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markInvert:(id)sender
{
[py markInvert];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markNone:(id)sender
{
[py markNone];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:YES]];
[py toggleSelectedMark];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markToggle:(id)sender
{
OVNode *node = [matches itemAtRow:[matches clickedRow]];
[self performPySelection:[NSArray arrayWithObject:p2a([node indexPath])]];
[py toggleSelectedMark];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)openSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py openSelected];
}
- (IBAction)refresh:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeDeadTracks:(id)sender - (IBAction)removeDeadTracks:(id)sender
{ {
[(PyDupeGuru *)py scanDeadTracks]; [(PyDupeGuru *)py scanDeadTracks];
} }
- (IBAction)removeMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return;
[py removeMarked];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeSelected:(id)sender
{
NSArray *nodeList = [self getSelected:YES];
if (![nodeList count])
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
return;
[self performPySelection:[self getSelectedPaths:YES]];
[py removeSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)renameSelected:(id)sender
{
NSInteger col = [matches columnWithIdentifier:@"0"];
NSInteger row = [matches selectedRow];
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
}
- (IBAction)resetColumnsToDefault:(id)sender - (IBAction)resetColumnsToDefault:(id)sender
{ {
NSMutableArray *columnsOrder = [NSMutableArray array]; NSMutableArray *columnsOrder = [NSMutableArray array];
@@ -161,12 +48,6 @@ http://www.hardcoded.net/licenses/hs_license
[self restoreColumnsPosition:columnsOrder widths:columnsWidth]; [self restoreColumnsPosition:columnsOrder widths:columnsWidth];
} }
- (IBAction)revealSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py revealSelected];
}
- (IBAction)startDuplicateScan:(id)sender - (IBAction)startDuplicateScan:(id)sender
{ {
if ([matches numberOfRows] > 0) if ([matches numberOfRows] > 0)
@@ -188,8 +69,6 @@ http://www.hardcoded.net/licenses/hs_license
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]]; [_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]]; [_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
NSInteger r = n2i([py doScan]); NSInteger r = n2i([py doScan]);
[matches reloadData];
[self refreshStats];
if (r == 1) if (r == 1)
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."]; [Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
if (r == 3) if (r == 3)
@@ -199,15 +78,6 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (IBAction)toggleDelta:(id)sender
{
if ([deltaSwitch selectedSegment] == 1)
[deltaSwitch setSelectedSegment:0];
else
[deltaSwitch setSelectedSegment:1];
[self changeDelta:sender];
}
/* Public */ /* Public */
- (void)initResultColumns - (void)initResultColumns
{ {
@@ -240,31 +110,6 @@ http://www.hardcoded.net/licenses/hs_license
[_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]]; [_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]];
} }
/* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
OVNode *node = item;
if ([[tableColumn identifier] isEqual:@"mark"])
{
[cell setEnabled: [node isMarkable]];
}
if ([cell isKindOfClass:[NSTextFieldCell class]])
{
// Determine if the text color will be blue due to directory being reference.
NSTextFieldCell *textCell = cell;
if ([node isMarkable])
[textCell setTextColor:[NSColor blackColor]];
else
[textCell setTextColor:[NSColor blueColor]];
if ((_displayDelta) && (_powerMode || ([node level] > 1)))
{
int i = [[tableColumn identifier] intValue];
if ([_deltaColumns containsIndex:i])
[textCell setTextColor:[NSColor orangeColor]];
}
}
}
/* Notifications */ /* Notifications */
- (void)jobCompleted:(NSNotification *)aNotification - (void)jobCompleted:(NSNotification *)aNotification
{ {

View File

@@ -18,27 +18,27 @@ from hsmedia import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
class PyDupeGuru(PyDupeGuruBase): class PyDupeGuru(PyDupeGuruBase):
def init(self): def init(self):
self = super(PyDupeGuru,self).init() self = super(PyDupeGuru,self).init()
self.app = DupeGuruME() self.py = DupeGuruME()
return self return self
def removeDeadTracks(self): def removeDeadTracks(self):
self.app.remove_dead_tracks() self.py.remove_dead_tracks()
def scanDeadTracks(self): def scanDeadTracks(self):
self.app.scan_dead_tracks() self.py.scan_dead_tracks()
#---Information #---Information
@signature('i@:') @signature('i@:')
def deadTrackCount(self): def deadTrackCount(self):
return len(self.app.dead_tracks) return len(self.py.dead_tracks)
#---Properties #---Properties
def setMinMatchPercentage_(self, percentage): def setMinMatchPercentage_(self, percentage):
self.app.scanner.min_match_percentage = int(percentage) self.py.scanner.min_match_percentage = int(percentage)
def setScanType_(self, scan_type): def setScanType_(self, scan_type):
try: try:
self.app.scanner.scan_type = [ self.py.scanner.scan_type = [
SCAN_TYPE_FILENAME, SCAN_TYPE_FILENAME,
SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS,
SCAN_TYPE_FIELDS_NO_ORDER, SCAN_TYPE_FIELDS_NO_ORDER,
@@ -50,16 +50,16 @@ class PyDupeGuru(PyDupeGuruBase):
pass pass
def setWordWeighting_(self, words_are_weighted): def setWordWeighting_(self, words_are_weighted):
self.app.scanner.word_weighting = words_are_weighted self.py.scanner.word_weighting = words_are_weighted
def setMatchSimilarWords_(self, match_similar_words): def setMatchSimilarWords_(self, match_similar_words):
self.app.scanner.match_similar_words = match_similar_words self.py.scanner.match_similar_words = match_similar_words
def enable_scanForTag_(self, enable, scan_tag): def enable_scanForTag_(self, enable, scan_tag):
if enable: if enable:
self.app.scanner.scanned_tags.add(scan_tag) self.py.scanner.scanned_tags.add(scan_tag)
else: else:
self.app.scanner.scanned_tags.discard(scan_tag) self.py.scanner.scanned_tags.discard(scan_tag)
#---Registration #---Registration
def appName(self): def appName(self):

View File

@@ -21,7 +21,16 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
CE003CC611242D00004B0AA7 /* HSGUIController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CB411242D00004B0AA7 /* HSGUIController.m */; };
CE003CC711242D00004B0AA7 /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CB611242D00004B0AA7 /* HSOutline.m */; };
CE003CC811242D00004B0AA7 /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CB811242D00004B0AA7 /* HSWindowController.m */; };
CE003CC911242D00004B0AA7 /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CBA11242D00004B0AA7 /* NSEventAdditions.m */; };
CE003CCA11242D00004B0AA7 /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CC111242D00004B0AA7 /* HSOutlineView.m */; };
CE003CCB11242D00004B0AA7 /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CC311242D00004B0AA7 /* NSIndexPathAdditions.m */; };
CE003CCC11242D00004B0AA7 /* NSTableViewAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CC511242D00004B0AA7 /* NSTableViewAdditions.m */; };
CE003CD011242D2C004B0AA7 /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */; };
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; }; CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
CE0B3D6711243F83009A7A30 /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE0B3D6611243F83009A7A30 /* ResultOutline.m */; };
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; }; CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; }; CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; }; CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
@@ -35,7 +44,6 @@
CE4B59CA1119919700C06C9E /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C71119919700C06C9E /* registration.xib */; }; CE4B59CA1119919700C06C9E /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE4B59C71119919700C06C9E /* registration.xib */; };
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; }; CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; };
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */; }; CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */; };
CE515DF50FC6C12E00EC695D /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE50FC6C12E00EC695D /* Outline.m */; };
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE70FC6C12E00EC695D /* ProgressController.m */; }; CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE70FC6C12E00EC695D /* ProgressController.m */; };
CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */; }; CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */; };
CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */; }; CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */; };
@@ -50,6 +58,7 @@
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; }; CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; }; CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; };
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; }; CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; };
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDF07A2112493B200EE5BC0 /* StatsLabel.m */; };
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; }; CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; }; CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; }; CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
@@ -78,7 +87,30 @@
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
8D1107320486CEB800E47090 /* dupeGuru ME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "dupeGuru ME.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 8D1107320486CEB800E47090 /* dupeGuru ME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "dupeGuru ME.app"; sourceTree = BUILT_PRODUCTS_DIR; };
CE003CB311242D00004B0AA7 /* HSGUIController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSGUIController.h; sourceTree = "<group>"; };
CE003CB411242D00004B0AA7 /* HSGUIController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSGUIController.m; sourceTree = "<group>"; };
CE003CB511242D00004B0AA7 /* HSOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutline.h; sourceTree = "<group>"; };
CE003CB611242D00004B0AA7 /* HSOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutline.m; sourceTree = "<group>"; };
CE003CB711242D00004B0AA7 /* HSWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSWindowController.h; sourceTree = "<group>"; };
CE003CB811242D00004B0AA7 /* HSWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSWindowController.m; sourceTree = "<group>"; };
CE003CB911242D00004B0AA7 /* NSEventAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSEventAdditions.h; path = ../../cocoalib/NSEventAdditions.h; sourceTree = SOURCE_ROOT; };
CE003CBA11242D00004B0AA7 /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
CE003CBC11242D00004B0AA7 /* PyGUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyGUI.h; sourceTree = "<group>"; };
CE003CBD11242D00004B0AA7 /* PyOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyOutline.h; sourceTree = "<group>"; };
CE003CBE11242D00004B0AA7 /* PyRegistrable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyRegistrable.h; path = ../../cocoalib/PyRegistrable.h; sourceTree = SOURCE_ROOT; };
CE003CC011242D00004B0AA7 /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
CE003CC111242D00004B0AA7 /* HSOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutlineView.m; sourceTree = "<group>"; };
CE003CC211242D00004B0AA7 /* NSIndexPathAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSIndexPathAdditions.h; sourceTree = "<group>"; };
CE003CC311242D00004B0AA7 /* NSIndexPathAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSIndexPathAdditions.m; sourceTree = "<group>"; };
CE003CC411242D00004B0AA7 /* NSTableViewAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSTableViewAdditions.h; sourceTree = "<group>"; };
CE003CC511242D00004B0AA7 /* NSTableViewAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSTableViewAdditions.m; sourceTree = "<group>"; };
CE003CCD11242D2C004B0AA7 /* DirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DirectoryOutline.h; path = ../base/DirectoryOutline.h; sourceTree = SOURCE_ROOT; };
CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryOutline.m; path = ../base/DirectoryOutline.m; sourceTree = SOURCE_ROOT; };
CE003CCF11242D2C004B0AA7 /* PyDirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDirectoryOutline.h; path = ../base/PyDirectoryOutline.h; sourceTree = SOURCE_ROOT; };
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_me_help; path = ../../help_me/dupeguru_me_help; sourceTree = "<group>"; }; CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_me_help; path = ../../help_me/dupeguru_me_help; sourceTree = "<group>"; };
CE0B3D6411243F83009A7A30 /* PyResultTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTree.h; path = ../base/PyResultTree.h; sourceTree = SOURCE_ROOT; };
CE0B3D6511243F83009A7A30 /* ResultOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultOutline.h; path = ../base/ResultOutline.h; sourceTree = SOURCE_ROOT; };
CE0B3D6611243F83009A7A30 /* ResultOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultOutline.m; path = ../base/ResultOutline.m; sourceTree = SOURCE_ROOT; };
CE1425880AFB718500BD5167 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; }; CE1425880AFB718500BD5167 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; }; CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
CE381C9509914ACE003581CE /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; }; CE381C9509914ACE003581CE /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; };
@@ -96,8 +128,6 @@
CE515DE10FC6C12E00EC695D /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; }; CE515DE10FC6C12E00EC695D /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; }; CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; }; CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; };
CE515DE40FC6C12E00EC695D /* Outline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Outline.h; path = ../../cocoalib/Outline.h; sourceTree = SOURCE_ROOT; };
CE515DE50FC6C12E00EC695D /* Outline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Outline.m; path = ../../cocoalib/Outline.m; sourceTree = SOURCE_ROOT; };
CE515DE60FC6C12E00EC695D /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; }; CE515DE60FC6C12E00EC695D /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
CE515DE70FC6C12E00EC695D /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; }; CE515DE70FC6C12E00EC695D /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
CE515DE80FC6C12E00EC695D /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; }; CE515DE80FC6C12E00EC695D /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
@@ -128,6 +158,9 @@
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; }; CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; }; CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; }; CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
CEDF07A0112493B200EE5BC0 /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
CEDF07A1112493B200EE5BC0 /* StatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StatsLabel.h; path = ../base/StatsLabel.h; sourceTree = SOURCE_ROOT; };
CEDF07A2112493B200EE5BC0 /* StatsLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StatsLabel.m; path = ../base/StatsLabel.m; sourceTree = SOURCE_ROOT; };
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; }; CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = ../../images/folder32.png; sourceTree = SOURCE_ROOT; }; CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = ../../images/folder32.png; sourceTree = SOURCE_ROOT; };
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = ../../images/details32.png; sourceTree = SOURCE_ROOT; }; CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = ../../images/details32.png; sourceTree = SOURCE_ROOT; };
@@ -237,6 +270,44 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CE003CB211242D00004B0AA7 /* controllers */ = {
isa = PBXGroup;
children = (
CE003CB311242D00004B0AA7 /* HSGUIController.h */,
CE003CB411242D00004B0AA7 /* HSGUIController.m */,
CE003CB511242D00004B0AA7 /* HSOutline.h */,
CE003CB611242D00004B0AA7 /* HSOutline.m */,
CE003CB711242D00004B0AA7 /* HSWindowController.h */,
CE003CB811242D00004B0AA7 /* HSWindowController.m */,
);
name = controllers;
path = ../../cocoalib/controllers;
sourceTree = SOURCE_ROOT;
};
CE003CBB11242D00004B0AA7 /* proxies */ = {
isa = PBXGroup;
children = (
CE003CBC11242D00004B0AA7 /* PyGUI.h */,
CE003CBD11242D00004B0AA7 /* PyOutline.h */,
);
name = proxies;
path = ../../cocoalib/proxies;
sourceTree = SOURCE_ROOT;
};
CE003CBF11242D00004B0AA7 /* views */ = {
isa = PBXGroup;
children = (
CE003CC011242D00004B0AA7 /* HSOutlineView.h */,
CE003CC111242D00004B0AA7 /* HSOutlineView.m */,
CE003CC211242D00004B0AA7 /* NSIndexPathAdditions.h */,
CE003CC311242D00004B0AA7 /* NSIndexPathAdditions.m */,
CE003CC411242D00004B0AA7 /* NSTableViewAdditions.h */,
CE003CC511242D00004B0AA7 /* NSTableViewAdditions.m */,
);
name = views;
path = ../../cocoalib/views;
sourceTree = SOURCE_ROOT;
};
CE3FBDD01094637800B72D77 /* xib */ = { CE3FBDD01094637800B72D77 /* xib */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -272,17 +343,21 @@
CE515DDD0FC6C09400EC695D /* cocoalib */ = { CE515DDD0FC6C09400EC695D /* cocoalib */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE003CB211242D00004B0AA7 /* controllers */,
CE003CBB11242D00004B0AA7 /* proxies */,
CE003CBF11242D00004B0AA7 /* views */,
CE4B59C41119919700C06C9E /* xib */, CE4B59C41119919700C06C9E /* xib */,
CE49DEF10FDFEB810098617B /* brsinglelineformatter */, CE49DEF10FDFEB810098617B /* brsinglelineformatter */,
CE515DE00FC6C12E00EC695D /* Dialogs.h */, CE515DE00FC6C12E00EC695D /* Dialogs.h */,
CE515DE10FC6C12E00EC695D /* Dialogs.m */, CE515DE10FC6C12E00EC695D /* Dialogs.m */,
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */, CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */,
CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */, CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */,
CE515DE40FC6C12E00EC695D /* Outline.h */, CE003CB911242D00004B0AA7 /* NSEventAdditions.h */,
CE515DE50FC6C12E00EC695D /* Outline.m */, CE003CBA11242D00004B0AA7 /* NSEventAdditions.m */,
CE515DE60FC6C12E00EC695D /* ProgressController.h */, CE515DE60FC6C12E00EC695D /* ProgressController.h */,
CE515DE70FC6C12E00EC695D /* ProgressController.m */, CE515DE70FC6C12E00EC695D /* ProgressController.m */,
CE515DE80FC6C12E00EC695D /* PyApp.h */, CE515DE80FC6C12E00EC695D /* PyApp.h */,
CE003CBE11242D00004B0AA7 /* PyRegistrable.h */,
CE515DE90FC6C12E00EC695D /* RecentDirectories.h */, CE515DE90FC6C12E00EC695D /* RecentDirectories.h */,
CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */, CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */,
CE515DEB0FC6C12E00EC695D /* RegistrationInterface.h */, CE515DEB0FC6C12E00EC695D /* RegistrationInterface.h */,
@@ -298,6 +373,9 @@
CE515E140FC6C17900EC695D /* dgbase */ = { CE515E140FC6C17900EC695D /* dgbase */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE003CCD11242D2C004B0AA7 /* DirectoryOutline.h */,
CE003CCE11242D2C004B0AA7 /* DirectoryOutline.m */,
CE003CCF11242D2C004B0AA7 /* PyDirectoryOutline.h */,
CE515E150FC6C19300EC695D /* AppDelegate.h */, CE515E150FC6C19300EC695D /* AppDelegate.h */,
CE515E160FC6C19300EC695D /* AppDelegate.m */, CE515E160FC6C19300EC695D /* AppDelegate.m */,
CE515E170FC6C19300EC695D /* Consts.h */, CE515E170FC6C19300EC695D /* Consts.h */,
@@ -305,10 +383,16 @@
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */, CE6032BF0FE6784C007E33FF /* DetailsPanel.m */,
CE515E180FC6C19300EC695D /* DirectoryPanel.h */, CE515E180FC6C19300EC695D /* DirectoryPanel.h */,
CE515E190FC6C19300EC695D /* DirectoryPanel.m */, CE515E190FC6C19300EC695D /* DirectoryPanel.m */,
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */,
CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */,
CE515E1B0FC6C19300EC695D /* ResultWindow.h */, CE515E1B0FC6C19300EC695D /* ResultWindow.h */,
CE515E1C0FC6C19300EC695D /* ResultWindow.m */, CE515E1C0FC6C19300EC695D /* ResultWindow.m */,
CE0B3D6511243F83009A7A30 /* ResultOutline.h */,
CE0B3D6611243F83009A7A30 /* ResultOutline.m */,
CEDF07A1112493B200EE5BC0 /* StatsLabel.h */,
CEDF07A2112493B200EE5BC0 /* StatsLabel.m */,
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */,
CED0A591111C9FD10020AD7D /* PyDetailsPanel.h */,
CE0B3D6411243F83009A7A30 /* PyResultTree.h */,
CEDF07A0112493B200EE5BC0 /* PyStatsLabel.h */,
); );
name = dgbase; name = dgbase;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -398,7 +482,6 @@
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */, CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */,
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */, CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */,
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */, CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */,
CE515DF50FC6C12E00EC695D /* Outline.m in Sources */,
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */, CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */,
CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */, CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */,
CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */, CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */,
@@ -409,6 +492,16 @@
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */, CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */,
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */, CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */,
CE6032C00FE6784C007E33FF /* DetailsPanel.m in Sources */, CE6032C00FE6784C007E33FF /* DetailsPanel.m in Sources */,
CE003CC611242D00004B0AA7 /* HSGUIController.m in Sources */,
CE003CC711242D00004B0AA7 /* HSOutline.m in Sources */,
CE003CC811242D00004B0AA7 /* HSWindowController.m in Sources */,
CE003CC911242D00004B0AA7 /* NSEventAdditions.m in Sources */,
CE003CCA11242D00004B0AA7 /* HSOutlineView.m in Sources */,
CE003CCB11242D00004B0AA7 /* NSIndexPathAdditions.m in Sources */,
CE003CCC11242D00004B0AA7 /* NSTableViewAdditions.m in Sources */,
CE003CD011242D2C004B0AA7 /* DirectoryOutline.m in Sources */,
CE0B3D6711243F83009A7A30 /* ResultOutline.m in Sources */,
CEDF07A3112493B200EE5BC0 /* StatsLabel.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -44,9 +44,8 @@ static NSString* jobAddIPhoto = @"jobAddIPhoto";
- (void)jobCompleted:(NSNotification *)aNotification - (void)jobCompleted:(NSNotification *)aNotification
{ {
if ([[ProgressController mainProgressController] jobId] == jobAddIPhoto) if ([[ProgressController mainProgressController] jobId] == jobAddIPhoto) {
{ [outlineView reloadData];
[directories reloadData];
} }
} }
@end @end

View File

@@ -23,7 +23,7 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>hsft</string> <string>hsft</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.8.2</string> <string>1.8.3</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>
<string>MainMenu</string> <string>MainMenu</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>

View File

@@ -10,28 +10,8 @@ http://www.hardcoded.net/licenses/hs_license
#import "Outline.h" #import "Outline.h"
#import "../base/ResultWindow.h" #import "../base/ResultWindow.h"
@interface ResultWindow : ResultWindowBase @interface ResultWindow : ResultWindowBase {}
{
IBOutlet NSSearchField *filterField;
NSMutableIndexSet *_deltaColumns;
}
- (IBAction)clearIgnoreList:(id)sender;
- (IBAction)clearPictureCache:(id)sender; - (IBAction)clearPictureCache:(id)sender;
- (IBAction)filter:(id)sender;
- (IBAction)ignoreSelected:(id)sender;
- (IBAction)markAll:(id)sender;
- (IBAction)markInvert:(id)sender;
- (IBAction)markNone:(id)sender;
- (IBAction)markSelected:(id)sender;
- (IBAction)markToggle:(id)sender;
- (IBAction)openSelected:(id)sender;
- (IBAction)refresh:(id)sender;
- (IBAction)removeMarked:(id)sender;
- (IBAction)removeSelected:(id)sender;
- (IBAction)renameSelected:(id)sender;
- (IBAction)revealSelected:(id)sender;
- (IBAction)startDuplicateScan:(id)sender; - (IBAction)startDuplicateScan:(id)sender;
- (IBAction)toggleDelta:(id)sender;
- (IBAction)toggleDirectories:(id)sender; - (IBAction)toggleDirectories:(id)sender;
@end @end

View File

@@ -20,31 +20,12 @@ http://www.hardcoded.net/licenses/hs_license
{ {
[super awakeFromNib]; [super awakeFromNib];
[[self window] setTitle:@"dupeGuru Picture Edition"]; [[self window] setTitle:@"dupeGuru Picture Edition"];
_displayDelta = NO;
_powerMode = NO;
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)] retain]; _deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)] retain];
[_deltaColumns removeIndex:3]; [_deltaColumns removeIndex:3];
[_deltaColumns removeIndex:4]; [_deltaColumns removeIndex:4];
[deltaSwitch setSelectedSegment:0];
[pmSwitch setSelectedSegment:0];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches setTarget:self];
[matches setDoubleAction:@selector(openSelected:)];
[self refreshStats];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
} }
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender
{
int i = n2i([py getIgnoreListCount]);
if (!i)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"Do you really want to remove all %d items from the ignore list?",i]] == NSAlertSecondButtonReturn) // NO
return;
[py clearIgnoreList];
}
- (IBAction)clearPictureCache:(id)sender - (IBAction)clearPictureCache:(id)sender
{ {
if ([Dialogs askYesNo:@"Do you really want to remove all your cached picture analysis?"] == NSAlertSecondButtonReturn) // NO if ([Dialogs askYesNo:@"Do you really want to remove all your cached picture analysis?"] == NSAlertSecondButtonReturn) // NO
@@ -52,101 +33,6 @@ http://www.hardcoded.net/licenses/hs_license
[(PyDupeGuru *)py clearPictureCache]; [(PyDupeGuru *)py clearPictureCache];
} }
- (IBAction)filter:(id)sender
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
[py applyFilter:[filterField stringValue]];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)ignoreSelected:(id)sender
{
NSArray *nodeList = [self getSelected:YES];
if (![nodeList count])
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
return;
[self performPySelection:[self getSelectedPaths:YES]];
[py addSelectedToIgnoreList];
[py removeSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)markAll:(id)sender
{
[py markAll];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markInvert:(id)sender
{
[py markInvert];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markNone:(id)sender
{
[py markNone];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:YES]];
[py toggleSelectedMark];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markToggle:(id)sender
{
OVNode *node = [matches itemAtRow:[matches clickedRow]];
[self performPySelection:[NSArray arrayWithObject:p2a([node indexPath])]];
[py toggleSelectedMark];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)openSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py openSelected];
}
- (IBAction)refresh:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return;
[py removeMarked];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeSelected:(id)sender
{
NSArray *nodeList = [self getSelected:YES];
if (![nodeList count])
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
return;
[self performPySelection:[self getSelectedPaths:YES]];
[py removeSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)renameSelected:(id)sender
{
int col = [matches columnWithIdentifier:@"0"];
int row = [matches selectedRow];
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
}
- (IBAction)resetColumnsToDefault:(id)sender - (IBAction)resetColumnsToDefault:(id)sender
{ {
NSMutableArray *columnsOrder = [NSMutableArray array]; NSMutableArray *columnsOrder = [NSMutableArray array];
@@ -164,12 +50,6 @@ http://www.hardcoded.net/licenses/hs_license
[self restoreColumnsPosition:columnsOrder widths:columnsWidth]; [self restoreColumnsPosition:columnsOrder widths:columnsWidth];
} }
- (IBAction)revealSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py revealSelected];
}
- (IBAction)startDuplicateScan:(id)sender - (IBAction)startDuplicateScan:(id)sender
{ {
if ([matches numberOfRows] > 0) if ([matches numberOfRows] > 0)
@@ -196,15 +76,6 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (IBAction)toggleDelta:(id)sender
{
if ([deltaSwitch selectedSegment] == 1)
[deltaSwitch setSelectedSegment:0];
else
[deltaSwitch setSelectedSegment:1];
[self changeDelta:sender];
}
- (IBAction)toggleDirectories:(id)sender - (IBAction)toggleDirectories:(id)sender
{ {
[(AppDelegate *)app toggleDirectories:sender]; [(AppDelegate *)app toggleDirectories:sender];
@@ -227,29 +98,4 @@ http://www.hardcoded.net/licenses/hs_license
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Match %" width:58 refCol:refCol]]; [_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Match %" width:58 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]]; [_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
} }
/* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
OVNode *node = item;
if ([[tableColumn identifier] isEqual:@"mark"])
{
[cell setEnabled: [node isMarkable]];
}
if ([cell isKindOfClass:[NSTextFieldCell class]])
{
// Determine if the text color will be blue due to directory being reference.
NSTextFieldCell *textCell = cell;
if ([node isMarkable])
[textCell setTextColor:[NSColor blackColor]];
else
[textCell setTextColor:[NSColor blueColor]];
if ((_displayDelta) && (_powerMode || ([node level] > 1)))
{
int i = [[tableColumn identifier] intValue];
if ([_deltaColumns containsIndex:i])
[textCell setTextColor:[NSColor orangeColor]];
}
}
}
@end @end

View File

@@ -13,25 +13,25 @@ from core_pe import block, cache, matchbase, data, _block_osx
class PyDupeGuru(PyDupeGuruBase): class PyDupeGuru(PyDupeGuruBase):
def init(self): def init(self):
self = super(PyDupeGuru, self).init() self = super(PyDupeGuru, self).init()
self.app = app_pe_cocoa.DupeGuruPE() self.py = app_pe_cocoa.DupeGuruPE()
return self return self
def clearPictureCache(self): def clearPictureCache(self):
self.app.scanner.clear_picture_cache() self.py.scanner.clear_picture_cache()
#---Information #---Information
def getSelectedDupePath(self): def getSelectedDupePath(self):
return unicode(self.app.selected_dupe_path()) return unicode(self.py.selected_dupe_path())
def getSelectedDupeRefPath(self): def getSelectedDupeRefPath(self):
return unicode(self.app.selected_dupe_ref_path()) return unicode(self.py.selected_dupe_ref_path())
#---Properties #---Properties
def setMatchScaled_(self,match_scaled): def setMatchScaled_(self,match_scaled):
self.app.scanner.match_scaled = match_scaled self.py.scanner.match_scaled = match_scaled
def setMinMatchPercentage_(self,percentage): def setMinMatchPercentage_(self,percentage):
self.app.scanner.threshold = int(percentage) self.py.scanner.threshold = int(percentage)
#---Registration #---Registration
def appName(self): def appName(self):

View File

@@ -39,6 +39,14 @@
CE80DB8B0FC1951C0086DCA6 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */; }; CE80DB8B0FC1951C0086DCA6 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */; };
CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB890FC1951C0086DCA6 /* ResultWindow.m */; }; CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DB890FC1951C0086DCA6 /* ResultWindow.m */; };
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; }; CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
CE9EA7561122C96C008CD2BC /* HSGUIController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7441122C96C008CD2BC /* HSGUIController.m */; };
CE9EA7571122C96C008CD2BC /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7461122C96C008CD2BC /* HSOutline.m */; };
CE9EA7581122C96C008CD2BC /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7481122C96C008CD2BC /* HSWindowController.m */; };
CE9EA7591122C96C008CD2BC /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA74A1122C96C008CD2BC /* NSEventAdditions.m */; };
CE9EA75A1122C96C008CD2BC /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7511122C96C008CD2BC /* HSOutlineView.m */; };
CE9EA75B1122C96C008CD2BC /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7531122C96C008CD2BC /* NSIndexPathAdditions.m */; };
CE9EA75C1122C96C008CD2BC /* NSTableViewAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7551122C96C008CD2BC /* NSTableViewAdditions.m */; };
CE9EA7721122CA0B008CD2BC /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE9EA7701122CA0B008CD2BC /* DirectoryOutline.m */; };
CEBAE4270FDA97E000B7887D /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */; }; CEBAE4270FDA97E000B7887D /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */; };
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; }; CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; }; CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
@@ -122,6 +130,26 @@
CE80DB880FC1951C0086DCA6 /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; }; CE80DB880FC1951C0086DCA6 /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; };
CE80DB890FC1951C0086DCA6 /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = ../base/ResultWindow.m; sourceTree = SOURCE_ROOT; }; CE80DB890FC1951C0086DCA6 /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = ../base/ResultWindow.m; sourceTree = SOURCE_ROOT; };
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; }; CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
CE9EA7431122C96C008CD2BC /* HSGUIController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSGUIController.h; sourceTree = "<group>"; };
CE9EA7441122C96C008CD2BC /* HSGUIController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSGUIController.m; sourceTree = "<group>"; };
CE9EA7451122C96C008CD2BC /* HSOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutline.h; sourceTree = "<group>"; };
CE9EA7461122C96C008CD2BC /* HSOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutline.m; sourceTree = "<group>"; };
CE9EA7471122C96C008CD2BC /* HSWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSWindowController.h; sourceTree = "<group>"; };
CE9EA7481122C96C008CD2BC /* HSWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSWindowController.m; sourceTree = "<group>"; };
CE9EA7491122C96C008CD2BC /* NSEventAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSEventAdditions.h; path = ../../cocoalib/NSEventAdditions.h; sourceTree = SOURCE_ROOT; };
CE9EA74A1122C96C008CD2BC /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
CE9EA74C1122C96C008CD2BC /* PyGUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyGUI.h; sourceTree = "<group>"; };
CE9EA74D1122C96C008CD2BC /* PyOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyOutline.h; sourceTree = "<group>"; };
CE9EA74E1122C96C008CD2BC /* PyRegistrable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyRegistrable.h; path = ../../cocoalib/PyRegistrable.h; sourceTree = SOURCE_ROOT; };
CE9EA7501122C96C008CD2BC /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
CE9EA7511122C96C008CD2BC /* HSOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutlineView.m; sourceTree = "<group>"; };
CE9EA7521122C96C008CD2BC /* NSIndexPathAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSIndexPathAdditions.h; sourceTree = "<group>"; };
CE9EA7531122C96C008CD2BC /* NSIndexPathAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSIndexPathAdditions.m; sourceTree = "<group>"; };
CE9EA7541122C96C008CD2BC /* NSTableViewAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSTableViewAdditions.h; sourceTree = "<group>"; };
CE9EA7551122C96C008CD2BC /* NSTableViewAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSTableViewAdditions.m; sourceTree = "<group>"; };
CE9EA76F1122CA0B008CD2BC /* DirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DirectoryOutline.h; path = ../base/DirectoryOutline.h; sourceTree = SOURCE_ROOT; };
CE9EA7701122CA0B008CD2BC /* DirectoryOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryOutline.m; path = ../base/DirectoryOutline.m; sourceTree = SOURCE_ROOT; };
CE9EA7711122CA0B008CD2BC /* PyDirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDirectoryOutline.h; path = ../base/PyDirectoryOutline.h; sourceTree = SOURCE_ROOT; };
CEBAE4230FDA97E000B7887D /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; }; CEBAE4230FDA97E000B7887D /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; }; CEBAE4240FDA97E000B7887D /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; }; CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
@@ -261,12 +289,17 @@
CE80DB1A0FC192AB0086DCA6 /* cocoalib */ = { CE80DB1A0FC192AB0086DCA6 /* cocoalib */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE9EA7421122C96C008CD2BC /* controllers */,
CE9EA74B1122C96C008CD2BC /* proxies */,
CE9EA74F1122C96C008CD2BC /* views */,
CE7AC9141119911200D02F6C /* xib */, CE7AC9141119911200D02F6C /* xib */,
CEBAE4220FDA97E000B7887D /* brsinglelineformatter */, CEBAE4220FDA97E000B7887D /* brsinglelineformatter */,
CE80DB480FC193770086DCA6 /* NSImageAdditions.h */, CE80DB480FC193770086DCA6 /* NSImageAdditions.h */,
CE80DB490FC193770086DCA6 /* NSImageAdditions.m */, CE80DB490FC193770086DCA6 /* NSImageAdditions.m */,
CE80DB450FC193650086DCA6 /* NSNotificationAdditions.h */, CE80DB450FC193650086DCA6 /* NSNotificationAdditions.h */,
CE80DB460FC193650086DCA6 /* NSNotificationAdditions.m */, CE80DB460FC193650086DCA6 /* NSNotificationAdditions.m */,
CE9EA7491122C96C008CD2BC /* NSEventAdditions.h */,
CE9EA74A1122C96C008CD2BC /* NSEventAdditions.m */,
CE80DB1B0FC192D60086DCA6 /* Dialogs.h */, CE80DB1B0FC192D60086DCA6 /* Dialogs.h */,
CE80DB1C0FC192D60086DCA6 /* Dialogs.m */, CE80DB1C0FC192D60086DCA6 /* Dialogs.m */,
CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */, CE80DB1D0FC192D60086DCA6 /* HSErrorReportWindow.h */,
@@ -276,6 +309,7 @@
CE80DB210FC192D60086DCA6 /* ProgressController.h */, CE80DB210FC192D60086DCA6 /* ProgressController.h */,
CE80DB220FC192D60086DCA6 /* ProgressController.m */, CE80DB220FC192D60086DCA6 /* ProgressController.m */,
CE80DB230FC192D60086DCA6 /* PyApp.h */, CE80DB230FC192D60086DCA6 /* PyApp.h */,
CE9EA74E1122C96C008CD2BC /* PyRegistrable.h */,
CE80DB240FC192D60086DCA6 /* RecentDirectories.h */, CE80DB240FC192D60086DCA6 /* RecentDirectories.h */,
CE80DB250FC192D60086DCA6 /* RecentDirectories.m */, CE80DB250FC192D60086DCA6 /* RecentDirectories.m */,
CE80DB260FC192D60086DCA6 /* RegistrationInterface.h */, CE80DB260FC192D60086DCA6 /* RegistrationInterface.h */,
@@ -291,6 +325,7 @@
CE80DB810FC194BD0086DCA6 /* dgbase */ = { CE80DB810FC194BD0086DCA6 /* dgbase */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE9EA7711122CA0B008CD2BC /* PyDirectoryOutline.h */,
CE80DB820FC1951C0086DCA6 /* AppDelegate.h */, CE80DB820FC1951C0086DCA6 /* AppDelegate.h */,
CE80DB830FC1951C0086DCA6 /* AppDelegate.m */, CE80DB830FC1951C0086DCA6 /* AppDelegate.m */,
CE80DB840FC1951C0086DCA6 /* Consts.h */, CE80DB840FC1951C0086DCA6 /* Consts.h */,
@@ -298,6 +333,8 @@
CE6044EB0FE6796200B71262 /* DetailsPanel.m */, CE6044EB0FE6796200B71262 /* DetailsPanel.m */,
CE80DB850FC1951C0086DCA6 /* DirectoryPanel.h */, CE80DB850FC1951C0086DCA6 /* DirectoryPanel.h */,
CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */, CE80DB860FC1951C0086DCA6 /* DirectoryPanel.m */,
CE9EA76F1122CA0B008CD2BC /* DirectoryOutline.h */,
CE9EA7701122CA0B008CD2BC /* DirectoryOutline.m */,
CE80DB870FC1951C0086DCA6 /* PyDupeGuru.h */, CE80DB870FC1951C0086DCA6 /* PyDupeGuru.h */,
CE18126F111C9D5100E49FCE /* PyDetailsPanel.h */, CE18126F111C9D5100E49FCE /* PyDetailsPanel.h */,
CE80DB880FC1951C0086DCA6 /* ResultWindow.h */, CE80DB880FC1951C0086DCA6 /* ResultWindow.h */,
@@ -306,6 +343,44 @@
name = dgbase; name = dgbase;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
CE9EA7421122C96C008CD2BC /* controllers */ = {
isa = PBXGroup;
children = (
CE9EA7431122C96C008CD2BC /* HSGUIController.h */,
CE9EA7441122C96C008CD2BC /* HSGUIController.m */,
CE9EA7451122C96C008CD2BC /* HSOutline.h */,
CE9EA7461122C96C008CD2BC /* HSOutline.m */,
CE9EA7471122C96C008CD2BC /* HSWindowController.h */,
CE9EA7481122C96C008CD2BC /* HSWindowController.m */,
);
name = controllers;
path = ../../cocoalib/controllers;
sourceTree = SOURCE_ROOT;
};
CE9EA74B1122C96C008CD2BC /* proxies */ = {
isa = PBXGroup;
children = (
CE9EA74C1122C96C008CD2BC /* PyGUI.h */,
CE9EA74D1122C96C008CD2BC /* PyOutline.h */,
);
name = proxies;
path = ../../cocoalib/proxies;
sourceTree = SOURCE_ROOT;
};
CE9EA74F1122C96C008CD2BC /* views */ = {
isa = PBXGroup;
children = (
CE9EA7501122C96C008CD2BC /* HSOutlineView.h */,
CE9EA7511122C96C008CD2BC /* HSOutlineView.m */,
CE9EA7521122C96C008CD2BC /* NSIndexPathAdditions.h */,
CE9EA7531122C96C008CD2BC /* NSIndexPathAdditions.m */,
CE9EA7541122C96C008CD2BC /* NSTableViewAdditions.h */,
CE9EA7551122C96C008CD2BC /* NSTableViewAdditions.m */,
);
name = views;
path = ../../cocoalib/views;
sourceTree = SOURCE_ROOT;
};
CEBAE4220FDA97E000B7887D /* brsinglelineformatter */ = { CEBAE4220FDA97E000B7887D /* brsinglelineformatter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -416,6 +491,14 @@
CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */, CE80DB8C0FC1951C0086DCA6 /* ResultWindow.m in Sources */,
CEBAE4270FDA97E000B7887D /* BRSingleLineFormatter.m in Sources */, CEBAE4270FDA97E000B7887D /* BRSingleLineFormatter.m in Sources */,
CE6044EC0FE6796200B71262 /* DetailsPanel.m in Sources */, CE6044EC0FE6796200B71262 /* DetailsPanel.m in Sources */,
CE9EA7561122C96C008CD2BC /* HSGUIController.m in Sources */,
CE9EA7571122C96C008CD2BC /* HSOutline.m in Sources */,
CE9EA7581122C96C008CD2BC /* HSWindowController.m in Sources */,
CE9EA7591122C96C008CD2BC /* NSEventAdditions.m in Sources */,
CE9EA75A1122C96C008CD2BC /* HSOutlineView.m in Sources */,
CE9EA75B1122C96C008CD2BC /* NSIndexPathAdditions.m in Sources */,
CE9EA75C1122C96C008CD2BC /* NSTableViewAdditions.m in Sources */,
CE9EA7721122CA0B008CD2BC /* DirectoryOutline.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -23,7 +23,7 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>hsft</string> <string>hsft</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2.9.1</string> <string>2.9.2</string>
<key>NSMainNibFile</key> <key>NSMainNibFile</key>
<string>MainMenu</string> <string>MainMenu</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>

View File

@@ -12,27 +12,9 @@ http://www.hardcoded.net/licenses/hs_license
#import "DirectoryPanel.h" #import "DirectoryPanel.h"
@interface ResultWindow : ResultWindowBase @interface ResultWindow : ResultWindowBase
{ {
IBOutlet NSSearchField *filterField;
NSString *_lastAction; NSString *_lastAction;
NSMutableIndexSet *_deltaColumns;
} }
- (IBAction)clearIgnoreList:(id)sender;
- (IBAction)filter:(id)sender;
- (IBAction)ignoreSelected:(id)sender;
- (IBAction)markAll:(id)sender;
- (IBAction)markInvert:(id)sender;
- (IBAction)markNone:(id)sender;
- (IBAction)markSelected:(id)sender;
- (IBAction)markToggle:(id)sender;
- (IBAction)openSelected:(id)sender;
- (IBAction)refresh:(id)sender;
- (IBAction)removeMarked:(id)sender;
- (IBAction)removeSelected:(id)sender;
- (IBAction)renameSelected:(id)sender;
- (IBAction)resetColumnsToDefault:(id)sender; - (IBAction)resetColumnsToDefault:(id)sender;
- (IBAction)revealSelected:(id)sender;
- (IBAction)startDuplicateScan:(id)sender; - (IBAction)startDuplicateScan:(id)sender;
- (IBAction)toggleDelta:(id)sender;
@end @end

View File

@@ -18,125 +18,11 @@ http://www.hardcoded.net/licenses/hs_license
- (void)awakeFromNib - (void)awakeFromNib
{ {
[super awakeFromNib]; [super awakeFromNib];
_displayDelta = NO;
_powerMode = NO;
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,4)] retain]; _deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,4)] retain];
[_deltaColumns removeIndex:3]; [_deltaColumns removeIndex:3];
[deltaSwitch setSelectedSegment:0];
[pmSwitch setSelectedSegment:0];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches setTarget:self];
[matches setDoubleAction:@selector(openSelected:)];
[self refreshStats];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
} }
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender
{
int i = n2i([py getIgnoreListCount]);
if (!i)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"Do you really want to remove all %d items from the ignore list?",i]] == NSAlertSecondButtonReturn) // NO
return;
[py clearIgnoreList];
}
- (IBAction)filter:(id)sender
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
[py applyFilter:[filterField stringValue]];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)ignoreSelected:(id)sender
{
NSArray *nodeList = [self getSelected:YES];
if (![nodeList count])
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
return;
[self performPySelection:[self getSelectedPaths:YES]];
[py addSelectedToIgnoreList];
[py removeSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)markAll:(id)sender
{
[py markAll];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markInvert:(id)sender
{
[py markInvert];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markNone:(id)sender
{
[py markNone];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:YES]];
[py toggleSelectedMark];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)markToggle:(id)sender
{
OVNode *node = [matches itemAtRow:[matches clickedRow]];
[self performPySelection:[NSArray arrayWithObject:p2a([node indexPath])]];
[py toggleSelectedMark];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
}
- (IBAction)openSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py openSelected];
}
- (IBAction)refresh:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return;
[py removeMarked];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeSelected:(id)sender
{
NSArray *nodeList = [self getSelected:YES];
if (![nodeList count])
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
return;
[self performPySelection:[self getSelectedPaths:YES]];
[py removeSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)renameSelected:(id)sender
{
int col = [matches columnWithIdentifier:@"0"];
int row = [matches selectedRow];
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
}
- (IBAction)resetColumnsToDefault:(id)sender - (IBAction)resetColumnsToDefault:(id)sender
{ {
NSMutableArray *columnsOrder = [NSMutableArray array]; NSMutableArray *columnsOrder = [NSMutableArray array];
@@ -152,12 +38,6 @@ http://www.hardcoded.net/licenses/hs_license
[self restoreColumnsPosition:columnsOrder widths:columnsWidth]; [self restoreColumnsPosition:columnsOrder widths:columnsWidth];
} }
- (IBAction)revealSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py revealSelected];
}
- (IBAction)startDuplicateScan:(id)sender - (IBAction)startDuplicateScan:(id)sender
{ {
if ([matches numberOfRows] > 0) if ([matches numberOfRows] > 0)
@@ -190,15 +70,6 @@ http://www.hardcoded.net/licenses/hs_license
} }
- (IBAction)toggleDelta:(id)sender
{
if ([deltaSwitch selectedSegment] == 1)
[deltaSwitch setSelectedSegment:0];
else
[deltaSwitch setSelectedSegment:1];
[self changeDelta:sender];
}
/* Public */ /* Public */
- (void)initResultColumns - (void)initResultColumns
{ {
@@ -216,29 +87,4 @@ http://www.hardcoded.net/licenses/hs_license
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Words Used" width:120 refCol:refCol]]; [_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Words Used" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]]; [_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
} }
/* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
OVNode *node = item;
if ([[tableColumn identifier] isEqual:@"mark"])
{
[cell setEnabled: [node isMarkable]];
}
if ([cell isKindOfClass:[NSTextFieldCell class]])
{
// Determine if the text color will be blue due to directory being reference.
NSTextFieldCell *textCell = cell;
if ([node isMarkable])
[textCell setTextColor:[NSColor blackColor]];
else
[textCell setTextColor:[NSColor blueColor]];
if ((_displayDelta) && (_powerMode || ([node level] > 1)))
{
int i = [[tableColumn identifier] intValue];
if ([_deltaColumns containsIndex:i])
[textCell setTextColor:[NSColor orangeColor]];
}
}
}
@end @end

View File

@@ -16,16 +16,16 @@ from core_se import fs, data
class PyDupeGuru(PyDupeGuruBase): class PyDupeGuru(PyDupeGuruBase):
def init(self): def init(self):
self = super(PyDupeGuru,self).init() self = super(PyDupeGuru,self).init()
self.app = DupeGuru() self.py = DupeGuru()
return self return self
#---Properties #---Properties
def setMinMatchPercentage_(self,percentage): def setMinMatchPercentage_(self,percentage):
self.app.scanner.min_match_percentage = int(percentage) self.py.scanner.min_match_percentage = int(percentage)
def setScanType_(self,scan_type): def setScanType_(self,scan_type):
try: try:
self.app.scanner.scan_type = [ self.py.scanner.scan_type = [
scanner.SCAN_TYPE_FILENAME, scanner.SCAN_TYPE_FILENAME,
scanner.SCAN_TYPE_CONTENT scanner.SCAN_TYPE_CONTENT
][scan_type] ][scan_type]
@@ -33,14 +33,14 @@ class PyDupeGuru(PyDupeGuruBase):
pass pass
def setWordWeighting_(self,words_are_weighted): def setWordWeighting_(self,words_are_weighted):
self.app.scanner.word_weighting = words_are_weighted self.py.scanner.word_weighting = words_are_weighted
def setMatchSimilarWords_(self,match_similar_words): def setMatchSimilarWords_(self,match_similar_words):
self.app.scanner.match_similar_words = match_similar_words self.py.scanner.match_similar_words = match_similar_words
@signature('v@:i') @signature('v@:i')
def setSizeThreshold_(self, size_threshold): def setSizeThreshold_(self, size_threshold):
self.app.scanner.size_threshold = size_threshold self.py.scanner.size_threshold = size_threshold
#---Registration #---Registration
def appName(self): def appName(self):

View File

@@ -20,7 +20,15 @@
CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; }; CE45579B0AE3BC2B005A9546 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; }; CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; }; CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; };
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; };
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */; };
CE76FDC6111EE37C006618EA /* NSTableViewAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC3111EE37C006618EA /* NSTableViewAdditions.m */; };
CE76FDCF111EE38E006618EA /* HSGUIController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC9111EE38E006618EA /* HSGUIController.m */; };
CE76FDD4111EE3A7006618EA /* DirectoryOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */; };
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; };
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDF6111EE561006618EA /* NSEventAdditions.m */; };
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; }; CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; };
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */; };
CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */; }; CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */; };
CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7EA120FE675C80004E467 /* DetailsPanel.m */; }; CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7EA120FE675C80004E467 /* DetailsPanel.m */; };
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; }; CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
@@ -76,7 +84,26 @@
CE45579A0AE3BC2B005A9546 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; }; CE45579A0AE3BC2B005A9546 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; }; CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; };
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; }; CE6E7407111C997500C350E3 /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
CE76FDBE111EE37C006618EA /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
CE76FDBF111EE37C006618EA /* HSOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutlineView.m; sourceTree = "<group>"; };
CE76FDC0111EE37C006618EA /* NSIndexPathAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSIndexPathAdditions.h; sourceTree = "<group>"; };
CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSIndexPathAdditions.m; sourceTree = "<group>"; };
CE76FDC2111EE37C006618EA /* NSTableViewAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSTableViewAdditions.h; sourceTree = "<group>"; };
CE76FDC3111EE37C006618EA /* NSTableViewAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSTableViewAdditions.m; sourceTree = "<group>"; };
CE76FDC8111EE38E006618EA /* HSGUIController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSGUIController.h; sourceTree = "<group>"; };
CE76FDC9111EE38E006618EA /* HSGUIController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSGUIController.m; sourceTree = "<group>"; };
CE76FDCD111EE38E006618EA /* PyGUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyGUI.h; sourceTree = "<group>"; };
CE76FDCE111EE38E006618EA /* PyOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyOutline.h; sourceTree = "<group>"; };
CE76FDD1111EE3A7006618EA /* DirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DirectoryOutline.h; path = ../base/DirectoryOutline.h; sourceTree = SOURCE_ROOT; };
CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryOutline.m; path = ../base/DirectoryOutline.m; sourceTree = SOURCE_ROOT; };
CE76FDD3111EE3A7006618EA /* PyDirectoryOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDirectoryOutline.h; path = ../base/PyDirectoryOutline.h; sourceTree = SOURCE_ROOT; };
CE76FDDD111EE42F006618EA /* HSOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutline.h; sourceTree = "<group>"; };
CE76FDDE111EE42F006618EA /* HSOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSOutline.m; sourceTree = "<group>"; };
CE76FDF5111EE561006618EA /* NSEventAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSEventAdditions.h; path = ../../cocoalib/NSEventAdditions.h; sourceTree = SOURCE_ROOT; };
CE76FDF6111EE561006618EA /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
CEAC6810109B0B7E00B43C85 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Preferences.xib; path = xib/Preferences.xib; sourceTree = "<group>"; }; CEAC6810109B0B7E00B43C85 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Preferences.xib; path = xib/Preferences.xib; sourceTree = "<group>"; };
CEBE4D72111F0EE1009AAC6D /* HSWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSWindowController.h; sourceTree = "<group>"; };
CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSWindowController.m; sourceTree = "<group>"; };
CEDD92D60FDD01640031C7B7 /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; }; CEDD92D60FDD01640031C7B7 /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; }; CEDD92D70FDD01640031C7B7 /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
CEE7EA110FE675C80004E467 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = ../base/DetailsPanel.h; sourceTree = SOURCE_ROOT; }; CEE7EA110FE675C80004E467 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = ../base/DetailsPanel.h; sourceTree = SOURCE_ROOT; };
@@ -216,6 +243,44 @@
path = ../../cocoalib/xib; path = ../../cocoalib/xib;
sourceTree = SOURCE_ROOT; sourceTree = SOURCE_ROOT;
}; };
CE76FDBD111EE37C006618EA /* views */ = {
isa = PBXGroup;
children = (
CE76FDBE111EE37C006618EA /* HSOutlineView.h */,
CE76FDBF111EE37C006618EA /* HSOutlineView.m */,
CE76FDC0111EE37C006618EA /* NSIndexPathAdditions.h */,
CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */,
CE76FDC2111EE37C006618EA /* NSTableViewAdditions.h */,
CE76FDC3111EE37C006618EA /* NSTableViewAdditions.m */,
);
name = views;
path = ../../cocoalib/views;
sourceTree = SOURCE_ROOT;
};
CE76FDC7111EE38E006618EA /* controllers */ = {
isa = PBXGroup;
children = (
CEBE4D72111F0EE1009AAC6D /* HSWindowController.h */,
CEBE4D73111F0EE1009AAC6D /* HSWindowController.m */,
CE76FDDD111EE42F006618EA /* HSOutline.h */,
CE76FDDE111EE42F006618EA /* HSOutline.m */,
CE76FDC8111EE38E006618EA /* HSGUIController.h */,
CE76FDC9111EE38E006618EA /* HSGUIController.m */,
);
name = controllers;
path = ../../cocoalib/controllers;
sourceTree = SOURCE_ROOT;
};
CE76FDCC111EE38E006618EA /* proxies */ = {
isa = PBXGroup;
children = (
CE76FDCD111EE38E006618EA /* PyGUI.h */,
CE76FDCE111EE38E006618EA /* PyOutline.h */,
);
name = proxies;
path = ../../cocoalib/proxies;
sourceTree = SOURCE_ROOT;
};
CEDD92D50FDD01640031C7B7 /* brsinglelineformatter */ = { CEDD92D50FDD01640031C7B7 /* brsinglelineformatter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -250,6 +315,11 @@
CEFC7F890FC9513600CD5728 /* cocoalib */ = { CEFC7F890FC9513600CD5728 /* cocoalib */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE76FDF5111EE561006618EA /* NSEventAdditions.h */,
CE76FDF6111EE561006618EA /* NSEventAdditions.m */,
CE76FDC7111EE38E006618EA /* controllers */,
CE76FDCC111EE38E006618EA /* proxies */,
CE76FDBD111EE37C006618EA /* views */,
CE19BC5F11199231007CCEB0 /* xib */, CE19BC5F11199231007CCEB0 /* xib */,
CEDD92D50FDD01640031C7B7 /* brsinglelineformatter */, CEDD92D50FDD01640031C7B7 /* brsinglelineformatter */,
CEFC7F8A0FC9517500CD5728 /* Dialogs.h */, CEFC7F8A0FC9517500CD5728 /* Dialogs.h */,
@@ -277,6 +347,9 @@
CEFC7FB00FC9518F00CD5728 /* dgbase */ = { CEFC7FB00FC9518F00CD5728 /* dgbase */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
CE76FDD1111EE3A7006618EA /* DirectoryOutline.h */,
CE76FDD2111EE3A7006618EA /* DirectoryOutline.m */,
CE76FDD3111EE3A7006618EA /* PyDirectoryOutline.h */,
CEFC7FB10FC951A700CD5728 /* AppDelegate.h */, CEFC7FB10FC951A700CD5728 /* AppDelegate.h */,
CEFC7FB20FC951A700CD5728 /* AppDelegate.m */, CEFC7FB20FC951A700CD5728 /* AppDelegate.m */,
CEFC7FB30FC951A700CD5728 /* Consts.h */, CEFC7FB30FC951A700CD5728 /* Consts.h */,
@@ -379,6 +452,14 @@
CEFC7FBB0FC951A700CD5728 /* ResultWindow.m in Sources */, CEFC7FBB0FC951A700CD5728 /* ResultWindow.m in Sources */,
CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */, CEDD92DA0FDD01640031C7B7 /* BRSingleLineFormatter.m in Sources */,
CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */, CEE7EA130FE675C80004E467 /* DetailsPanel.m in Sources */,
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */,
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */,
CE76FDC6111EE37C006618EA /* NSTableViewAdditions.m in Sources */,
CE76FDCF111EE38E006618EA /* HSGUIController.m in Sources */,
CE76FDD4111EE3A7006618EA /* DirectoryOutline.m in Sources */,
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */,
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */,
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -84,6 +84,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
def _do_load(self, j): def _do_load(self, j):
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml')) self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
self.notify('directories_changed')
j = j.start_subjob([1, 9]) j = j.start_subjob([1, 9])
self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j) self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j)
files = flatten(g[:] for g in self.results.groups) files = flatten(g[:] for g in self.results.groups)
@@ -103,10 +104,23 @@ class DupeGuru(RegistrableApplication, Broadcaster):
path = Path(str_path) path = Path(str_path)
return fs.get_file(path, self.directories.fileclasses) return fs.get_file(path, self.directories.fileclasses)
def _job_completed(self, jobid):
# Must be called by subclasses when they detect that an async job is completed.
if jobid in (JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_DELETE):
self.notify('results_changed')
@staticmethod
def _open_path(path):
raise NotImplementedError()
@staticmethod @staticmethod
def _recycle_dupe(dupe): def _recycle_dupe(dupe):
raise NotImplementedError() raise NotImplementedError()
@staticmethod
def _reveal_path(path):
raise NotImplementedError()
def _select_dupes(self, dupes): def _select_dupes(self, dupes):
if dupes == self.selected_dupes: if dupes == self.selected_dupes:
return return
@@ -120,17 +134,21 @@ class DupeGuru(RegistrableApplication, Broadcaster):
def add_directory(self, d): def add_directory(self, d):
try: try:
self.directories.add_path(Path(d)) self.directories.add_path(Path(d))
self.notify('directories_changed')
return 0 return 0
except directories.AlreadyThereError: except directories.AlreadyThereError:
return 1 return 1
except directories.InvalidPathError: except directories.InvalidPathError:
return 2 return 2
def add_to_ignore_list(self, dupe): def add_selected_to_ignore_list(self):
g = self.results.get_group_of_duplicate(dupe) dupes = self.without_ref(self.selected_dupes)
for other in g: for dupe in dupes:
if other is not dupe: g = self.results.get_group_of_duplicate(dupe)
self.scanner.ignore_list.Ignore(unicode(other.path), unicode(dupe.path)) for other in g:
if other is not dupe:
self.scanner.ignore_list.Ignore(unicode(other.path), unicode(dupe.path))
self.remove_duplicates(dupes)
def apply_filter(self, filter): def apply_filter(self, filter):
self.results.apply_filter(None) self.results.apply_filter(None)
@@ -138,6 +156,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
filter = escape(filter, '()[]\\.|+?^') filter = escape(filter, '()[]\\.|+?^')
filter = escape(filter, '*', '.') filter = escape(filter, '*', '.')
self.results.apply_filter(filter) self.results.apply_filter(filter)
self.notify('results_changed')
def clean_empty_dirs(self, path): def clean_empty_dirs(self, path):
if self.options['clean_empty_dirs']: if self.options['clean_empty_dirs']:
@@ -212,13 +231,72 @@ class DupeGuru(RegistrableApplication, Broadcaster):
p = op.join(self.appdata, 'ignore_list.xml') p = op.join(self.appdata, 'ignore_list.xml')
self.scanner.ignore_list.load_from_xml(p) self.scanner.ignore_list.load_from_xml(p)
def make_reference(self, duplicates): def make_selected_reference(self):
dupes = self.without_ref(self.selected_dupes)
changed_groups = set() changed_groups = set()
for dupe in duplicates: for dupe in dupes:
g = self.results.get_group_of_duplicate(dupe) g = self.results.get_group_of_duplicate(dupe)
if g not in changed_groups: if g not in changed_groups:
self.results.make_ref(dupe) self.results.make_ref(dupe)
changed_groups.add(g) changed_groups.add(g)
self.notify('results_switched')
def mark_all(self):
self.results.mark_all()
self.notify('marking_changed')
def mark_none(self):
self.results.mark_none()
self.notify('marking_changed')
def mark_invert(self):
self.results.mark_invert()
self.notify('marking_changed')
def mark_dupe(self, dupe, marked):
if marked:
self.results.mark(dupe)
else:
self.results.unmark(dupe)
self.notify('marking_changed')
def open_selected(self):
if self.selected_dupes:
self._open_path(self.selected_dupes[0].path)
def purge_ignore_list(self):
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
def remove_directory(self,index):
try:
del self.directories[index]
self.notify('directories_changed')
except IndexError:
pass
def remove_duplicates(self, duplicates):
self.results.remove_duplicates(self.without_ref(duplicates))
self.notify('results_changed')
def remove_marked(self):
self.results.perform_on_marked(lambda x:True, True)
self.notify('results_changed')
def remove_selected(self):
self.remove_duplicates(self.selected_dupes)
def rename_selected(self, newname):
try:
d = self.selected_dupes[0]
d.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_dupes:
self._reveal_path(self.selected_dupes[0].path)
def save(self): def save(self):
if not op.exists(self.appdata): if not op.exists(self.appdata):
@@ -248,6 +326,14 @@ class DupeGuru(RegistrableApplication, Broadcaster):
self.results.groups = [] self.results.groups = []
self._start_job(JOB_SCAN, do) self._start_job(JOB_SCAN, do)
def toggle_selected_mark_state(self):
for dupe in self.selected_dupes:
self.results.mark_toggle(dupe)
self.notify('marking_changed')
def without_ref(self, dupes):
return [dupe for dupe in dupes if self.results.get_group_of_duplicate(dupe).ref is not dupe]
#--- Properties #--- Properties
@property @property
def stat_line(self): def stat_line(self):

View File

@@ -14,7 +14,6 @@ from hsutil.cocoa import install_exception_hook
from hsutil.cocoa.objcmin import (NSNotificationCenter, NSUserDefaults, from hsutil.cocoa.objcmin import (NSNotificationCenter, NSUserDefaults,
NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSUserDomainMask, NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSUserDomainMask,
NSWorkspace, NSWorkspaceRecycleOperation) NSWorkspace, NSWorkspaceRecycleOperation)
from hsutil.misc import stripnone
from hsutil.reg import RegistrationRequired from hsutil.reg import RegistrationRequired
from . import app, fs from . import app, fs
@@ -46,9 +45,12 @@ class DupeGuru(app.DupeGuru):
appdata = op.join(appsupport, appdata_subdir) appdata = op.join(appsupport, appdata_subdir)
app.DupeGuru.__init__(self, data_module, appdata, appid) app.DupeGuru.__init__(self, data_module, appdata, appid)
self.progress = cocoa.ThreadedJobPerformer() self.progress = cocoa.ThreadedJobPerformer()
self.display_delta_values = False
#--- Override #--- Override
@staticmethod
def _open_path(path):
NSWorkspace.sharedWorkspace().openFile_(unicode(path))
@staticmethod @staticmethod
def _recycle_dupe(dupe): def _recycle_dupe(dupe):
# local import because first appkit import takes a lot of memory. we want to avoid it. # local import because first appkit import takes a lot of memory. we want to avoid it.
@@ -57,6 +59,10 @@ class DupeGuru(app.DupeGuru):
result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_( result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_(
NSWorkspaceRecycleOperation, directory, '', [filename], None) NSWorkspaceRecycleOperation, directory, '', [filename], None)
@staticmethod
def _reveal_path(path):
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(unicode(path), '')
def _start_job(self, jobid, func): def _start_job(self, jobid, func):
try: try:
j = self.progress.create_job() j = self.progress.create_job()
@@ -67,71 +73,9 @@ class DupeGuru(app.DupeGuru):
ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid} ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid}
NSNotificationCenter.defaultCenter().postNotificationName_object_userInfo_('JobStarted', self, ud) NSNotificationCenter.defaultCenter().postNotificationName_object_userInfo_('JobStarted', self, ud)
#---Helpers
def GetObjects(self,node_path):
#returns a tuple g,d
try:
g = self.results.groups[node_path[0]]
if len(node_path) == 2:
return (g,self.results.groups[node_path[0]].dupes[node_path[1]])
else:
return (g,None)
except IndexError:
return (None,None)
def get_folder_path(self, node_path, curr_path=None):
if not node_path:
return curr_path
current_index = node_path[0]
if curr_path is None:
curr_path = self.directories[current_index]
else:
curr_path = self.directories.get_subfolders(curr_path)[current_index]
return self.get_folder_path(node_path[1:], curr_path)
#---Public #---Public
def AddSelectedToIgnoreList(self):
for dupe in self.selected_dupes:
self.add_to_ignore_list(dupe)
copy_or_move_marked = demo_method(app.DupeGuru.copy_or_move_marked) copy_or_move_marked = demo_method(app.DupeGuru.copy_or_move_marked)
delete_marked = demo_method(app.DupeGuru.delete_marked) delete_marked = demo_method(app.DupeGuru.delete_marked)
def MakeSelectedReference(self):
self.make_reference(self.selected_dupes)
def OpenSelected(self):
# local import because first appkit import takes a lot of memory. we want to avoid it.
if self.selected_dupes:
path = unicode(self.selected_dupes[0].path)
NSWorkspace.sharedWorkspace().openFile_(path)
def PurgeIgnoreList(self):
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
def RemoveDirectory(self,index):
try:
del self.directories[index]
except IndexError:
pass
def RemoveSelected(self):
self.results.remove_duplicates(self.selected_dupes)
def RenameSelected(self, newname):
try:
d = self.selected_dupes[0]
d.rename(newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def RevealSelected(self):
# local import because first appkit import takes a lot of memory. we want to avoid it.
if self.selected_dupes:
path = unicode(self.selected_dupes[0].path)
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(path,'')
def start_scanning(self): def start_scanning(self):
self._select_dupes([]) self._select_dupes([])
@@ -143,131 +87,3 @@ class DupeGuru(app.DupeGuru):
except app.AllFilesAreRefError: except app.AllFilesAreRefError:
return 1 return 1
def selected_result_node_paths(self):
def get_path(dupe):
try:
group = self.results.get_group_of_duplicate(dupe)
groupindex = self.results.groups.index(group)
if dupe is group.ref:
return [groupindex]
dupeindex = group.dupes.index(dupe)
return [groupindex, dupeindex]
except ValueError: # dupe not in there
return None
dupes = self.selected_dupes
return stripnone(get_path(dupe) for dupe in dupes)
def selected_powermarker_node_paths(self):
def get_path(dupe):
try:
dupeindex = self.results.dupes.index(dupe)
return [dupeindex]
except ValueError: # dupe not in there
return None
dupes = self.selected_dupes
return stripnone(get_path(dupe) for dupe in dupes)
def SelectResultNodePaths(self,node_paths):
def extract_dupe(t):
g,d = t
if d is not None:
return d
else:
if g is not None:
return g.ref
selected = [extract_dupe(self.GetObjects(p)) for p in node_paths]
self._select_dupes([dupe for dupe in selected if dupe is not None])
def SelectPowerMarkerNodePaths(self,node_paths):
rows = [p[0] for p in node_paths]
dupes = [self.results.dupes[row] for row in rows if row in xrange(len(self.results.dupes))]
self._select_dupes(dupes)
def SetDirectoryState(self, node_path, state):
p = self.get_folder_path(node_path)
self.directories.set_state(p, state)
def sort_dupes(self,key,asc):
self.results.sort_dupes(key,asc,self.display_delta_values)
def sort_groups(self,key,asc):
self.results.sort_groups(key,asc)
def ToggleSelectedMarkState(self):
for dupe in self.selected_dupes:
self.results.mark_toggle(dupe)
#---Data
def GetOutlineViewMaxLevel(self, tag):
if tag == 0:
return 2
elif tag == 1:
return 0
elif tag == 2:
return 1
def GetOutlineViewChildCounts(self, tag, node_path):
if self.progress._job_running:
return []
if tag == 0: #Normal results
assert not node_path # no other value is possible
return [len(g.dupes) for g in self.results.groups]
elif tag == 1: #Directories
try:
if node_path:
path = self.get_folder_path(node_path)
subfolders = self.directories.get_subfolders(path)
else:
subfolders = self.directories
return [len(self.directories.get_subfolders(path)) for path in subfolders]
except IndexError: # node_path out of range
return []
else: #Power Marker
assert not node_path # no other value is possible
return [0 for d in self.results.dupes]
def GetOutlineViewValues(self, tag, node_path):
if self.progress._job_running:
return
if not node_path:
return
if tag in (0,2): #Normal results / Power Marker
if tag == 0:
g, d = self.GetObjects(node_path)
if (d is None) and (g is not None):
d = g.ref
else:
d = self.results.dupes[node_path[0]]
g = self.results.get_group_of_duplicate(d)
result = self._get_display_info(d, g, self.display_delta_values)
return result
elif tag == 1: #Directories
try:
path = self.get_folder_path(node_path)
name = unicode(path) if len(node_path) == 1 else path[-1]
return [name, self.directories.get_state(path)]
except IndexError: # node_path out of range
return []
def GetOutlineViewMarked(self, tag, node_path):
# 0=unmarked 1=marked 2=unmarkable
if self.progress._job_running:
return
if not node_path:
return 2
if tag == 1: #Directories
return 2
if tag == 0: #Normal results
g, d = self.GetObjects(node_path)
else: #Power Marker
d = self.results.dupes[node_path[0]]
if (d is None) or (not self.results.is_markable(d)):
return 2
elif self.results.is_marked(d):
return 1
else:
return 0

View File

@@ -9,204 +9,126 @@
# Common interface for all editions' dg_cocoa unit. # Common interface for all editions' dg_cocoa unit.
from hsutil.cocoa.objcmin import NSObject from hsutil.cocoa.inter import signature, PyOutline, PyGUIObject, PyRegistrable
from hsutil.cocoa import signature
from hsutil.reg import InvalidCodeError
from .gui.details_panel import DetailsPanel from .gui.details_panel import DetailsPanel
from .gui.directory_tree import DirectoryTree
from .gui.result_tree import ResultTree
from .gui.stats_label import StatsLabel
# Fix py2app's problems on relative imports # Fix py2app's problems on relative imports
from core import app, app_cocoa, data, directories, engine, export, ignore, results, fs, scanner from core import app, app_cocoa, data, directories, engine, export, ignore, results, fs, scanner
from hsutil import conflict from hsutil import conflict
class PyApp(NSObject): class PyDupeGuruBase(PyRegistrable):
pass #fake class
class PyDupeGuruBase(PyApp):
#---Directories #---Directories
def addDirectory_(self, directory): def addDirectory_(self, directory):
return self.app.add_directory(directory) return self.py.add_directory(directory)
def removeDirectory_(self, index): def removeDirectory_(self, index):
self.app.RemoveDirectory(index) self.py.remove_directory(index)
def setDirectory_state_(self, node_path, state):
self.app.SetDirectoryState(node_path, state)
#---Results #---Results
def clearIgnoreList(self): def clearIgnoreList(self):
self.app.scanner.ignore_list.Clear() self.py.scanner.ignore_list.Clear()
def doScan(self): def doScan(self):
return self.app.start_scanning() return self.py.start_scanning()
def exportToXHTMLwithColumns_(self, column_ids): def exportToXHTMLwithColumns_(self, column_ids):
return self.app.export_to_xhtml(column_ids) return self.py.export_to_xhtml(column_ids)
def loadIgnoreList(self): def loadIgnoreList(self):
self.app.load_ignore_list() self.py.load_ignore_list()
def loadResults(self): def loadResults(self):
self.app.load() self.py.load()
def markAll(self): def markAll(self):
self.app.results.mark_all() self.py.mark_all()
def markNone(self): def markNone(self):
self.app.results.mark_none() self.py.mark_none()
def markInvert(self): def markInvert(self):
self.app.results.mark_invert() self.py.mark_invert()
def purgeIgnoreList(self): def purgeIgnoreList(self):
self.app.PurgeIgnoreList() self.py.purge_ignore_list()
def toggleSelectedMark(self): def toggleSelectedMark(self):
self.app.ToggleSelectedMarkState() self.py.toggle_selected_mark_state()
def saveIgnoreList(self): def saveIgnoreList(self):
self.app.save_ignore_list() self.py.save_ignore_list()
def saveResults(self): def saveResults(self):
self.app.save() self.py.save()
def selectedResultNodePaths(self):
return self.app.selected_result_node_paths()
def selectResultNodePaths_(self,node_paths):
self.app.SelectResultNodePaths(node_paths)
def selectedPowerMarkerNodePaths(self):
return self.app.selected_powermarker_node_paths()
def selectPowerMarkerNodePaths_(self,node_paths):
self.app.SelectPowerMarkerNodePaths(node_paths)
#---Actions #---Actions
def addSelectedToIgnoreList(self): def addSelectedToIgnoreList(self):
self.app.AddSelectedToIgnoreList() self.py.add_selected_to_ignore_list()
def deleteMarked(self): def deleteMarked(self):
self.app.delete_marked() self.py.delete_marked()
def applyFilter_(self, filter): def applyFilter_(self, filter):
self.app.apply_filter(filter) self.py.apply_filter(filter)
def makeSelectedReference(self): def makeSelectedReference(self):
self.app.MakeSelectedReference() self.py.make_selected_reference()
def copyOrMove_markedTo_recreatePath_(self, copy, destination, recreate_path): def copyOrMove_markedTo_recreatePath_(self, copy, destination, recreate_path):
self.app.copy_or_move_marked(copy, destination, recreate_path) self.py.copy_or_move_marked(copy, destination, recreate_path)
def openSelected(self): def openSelected(self):
self.app.OpenSelected() self.py.open_selected()
def removeMarked(self): def removeMarked(self):
self.app.results.perform_on_marked(lambda x:True, True) self.py.remove_marked()
def removeSelected(self):
self.app.RemoveSelected()
def renameSelected_(self,newname): def renameSelected_(self,newname):
return self.app.RenameSelected(newname) return self.py.rename_selected(newname)
def revealSelected(self): def revealSelected(self):
self.app.RevealSelected() self.py.reveal_selected()
#---Misc
def sortDupesBy_ascending_(self, key, asc):
self.app.sort_dupes(key, asc)
def sortGroupsBy_ascending_(self, key, asc):
self.app.sort_groups(key, asc)
#---Information #---Information
def getIgnoreListCount(self): def getIgnoreListCount(self):
return len(self.app.scanner.ignore_list) return len(self.py.scanner.ignore_list)
def getMarkCount(self): def getMarkCount(self):
return self.app.results.mark_count return self.py.results.mark_count
def getStatLine(self):
return self.app.stat_line
def getOperationalErrorCount(self): def getOperationalErrorCount(self):
return self.app.last_op_error_count return self.py.last_op_error_count
#---Data
@signature('i@:i')
def getOutlineViewMaxLevel_(self, tag):
return self.app.GetOutlineViewMaxLevel(tag)
@signature('@@:i@')
def getOutlineView_childCountsForPath_(self, tag, node_path):
return self.app.GetOutlineViewChildCounts(tag, node_path)
def getOutlineView_valuesForIndexes_(self, tag, node_path):
return self.app.GetOutlineViewValues(tag, node_path)
def getOutlineView_markedAtIndexes_(self, tag, node_path):
return self.app.GetOutlineViewMarked(tag, node_path)
def getTableViewCount_(self, tag):
return self.app.GetTableViewCount(tag)
def getTableViewMarkedIndexes_(self, tag):
return self.app.GetTableViewMarkedIndexes(tag)
def getTableView_valuesForRow_(self, tag, row):
return self.app.GetTableViewValues(tag, row)
#---Properties #---Properties
def setMixFileKind_(self, mix_file_kind): def setMixFileKind_(self, mix_file_kind):
self.app.scanner.mix_file_kind = mix_file_kind self.py.scanner.mix_file_kind = mix_file_kind
def setDisplayDeltaValues_(self, display_delta_values):
self.app.display_delta_values= display_delta_values
def setEscapeFilterRegexp_(self, escape_filter_regexp): def setEscapeFilterRegexp_(self, escape_filter_regexp):
self.app.options['escape_filter_regexp'] = escape_filter_regexp self.py.options['escape_filter_regexp'] = escape_filter_regexp
def setRemoveEmptyFolders_(self, remove_empty_folders): def setRemoveEmptyFolders_(self, remove_empty_folders):
self.app.options['clean_empty_dirs'] = remove_empty_folders self.py.options['clean_empty_dirs'] = remove_empty_folders
#---Worker #---Worker
def getJobProgress(self): def getJobProgress(self):
return self.app.progress.last_progress return self.py.progress.last_progress
def getJobDesc(self): def getJobDesc(self):
return self.app.progress.last_desc return self.py.progress.last_desc
def cancelJob(self): def cancelJob(self):
self.app.progress.job_cancelled = True self.py.progress.job_cancelled = True
#---Registration def jobCompleted_(self, jobid):
def demoLimitDescription(self): self.py._job_completed(jobid)
return self.app.DEMO_LIMIT_DESC
@signature('i@:')
def isRegistered(self):
return self.app.registered
def isCodeValid_withEmail_(self, code, email):
try:
self.app.validate_code(code, email)
return None
except InvalidCodeError as e:
return unicode(e)
def setRegisteredCode_andEmail_(self, code, email):
self.app.set_registration(code, email)
class PyDetailsPanel(NSObject): class PyDetailsPanel(PyGUIObject):
def initWithCocoa_pyParent_(self, cocoa, pyparent): py_class = DetailsPanel
super(PyDetailsPanel, self).init()
self.cocoa = cocoa
self.py = DetailsPanel(self, pyparent.app)
return self
@signature('i@:') @signature('i@:')
def numberOfRows(self): def numberOfRows(self):
return self.py.row_count() return self.py.row_count()
@@ -215,7 +137,62 @@ class PyDetailsPanel(NSObject):
def valueForColumn_row_(self, column, row): def valueForColumn_row_(self, column, row):
return self.py.row(row)[int(column)] return self.py.row(row)[int(column)]
# python --> cocoa
def refresh(self): class PyDirectoryOutline(PyOutline):
self.cocoa.refresh() py_class = DirectoryTree
def addDirectory_(self, path):
self.py.add_directory(path)
class PyResultOutline(PyOutline):
py_class = ResultTree
@signature('c@:')
def powerMarkerMode(self):
return self.py.power_marker
@signature('v@:c')
def setPowerMarkerMode_(self, value):
self.py.power_marker = value
@signature('c@:')
def deltaValuesMode(self):
return self.py.delta_values
@signature('v@:c')
def setDeltaValuesMode_(self, value):
self.py.delta_values = value
@signature('@@:@i')
def valueForPath_column_(self, path, column):
return self.py.get_node_value(path, column)
@signature('c@:@')
def renameSelected_(self, newname):
return self.py.rename_selected(newname)
@signature('v@:ic')
def sortBy_ascending_(self, key, asc):
self.py.sort(key, asc)
def markSelected(self):
self.py.app.toggle_selected_mark_state()
def removeSelected(self):
self.py.app.remove_selected()
def rootChildrenCounts(self):
return self.py.root_children_counts()
# python --> cocoa
def invalidate_markings(self):
self.cocoa.invalidateMarkings()
class PyStatsLabel(PyGUIObject):
py_class = StatsLabel
def display(self):
return self.py.display

32
core/gui/base.py Normal file
View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-06
# Copyright 2010 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 hsutil.notify import Listener
class GUIObject(Listener):
def __init__(self, view, app):
Listener.__init__(self, app)
self.view = view
self.app = app
def directories_changed(self):
pass
def dupes_selected(self):
pass
def marking_changed(self):
pass
def results_changed(self):
pass
def results_switched(self):
pass

View File

@@ -7,16 +7,17 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license # http://www.hardcoded.net/licenses/hs_license
from hsutil.notify import Listener from .base import GUIObject
class DetailsPanel(Listener): class DetailsPanel(GUIObject):
def __init__(self, view, app): def __init__(self, view, app):
Listener.__init__(self, app) GUIObject.__init__(self, view, app)
self.app = app
self.view = view
self._table = [] self._table = []
def connect(self):
GUIObject.connect(self)
self._refresh() self._refresh()
self.connect() self.view.refresh()
#--- Private #--- Private
def _refresh(self): def _refresh(self):

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-06
# Copyright 2010 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 hsgui.tree import Tree, Node
from ..directories import STATE_NORMAL, STATE_REFERENCE, STATE_EXCLUDED
from .base import GUIObject
STATE_ORDER = [STATE_NORMAL, STATE_REFERENCE, STATE_EXCLUDED]
# Lazily loads children
class DirectoryNode(Node):
def __init__(self, app, path, name):
Node.__init__(self, name)
self._app = app
self._directory_path = path
self._loaded = False
self._state = STATE_ORDER.index(self._app.directories.get_state(path))
def __len__(self):
if not self._loaded:
self._load()
return Node.__len__(self)
def _load(self):
self.clear()
subpaths = self._app.directories.get_subfolders(self._directory_path)
for path in subpaths:
self.append(DirectoryNode(self._app, path, path[-1]))
self._loaded = True
# The state propery is an index to the combobox
@property
def state(self):
return self._state
@state.setter
def state(self, value):
if value == self._state:
return
self._state = value
state = STATE_ORDER[value]
self._app.directories.set_state(self._directory_path, state)
class DirectoryTree(GUIObject, Tree):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
Tree.__init__(self)
def connect(self):
GUIObject.connect(self)
self._refresh()
self.view.refresh()
def _refresh(self):
self.clear()
for path in self.app.directories:
self.append(DirectoryNode(self.app, path, unicode(path)))
def add_directory(self, path):
self.app.add_directory(path)
#--- Event Handlers
def directories_changed(self):
self._refresh()
self.view.refresh()

159
core/gui/result_tree.py Normal file
View File

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-11
# Copyright 2010 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 operator import attrgetter
from hsgui.tree import Tree, Node
from .base import GUIObject
class DupeNode(Node):
def __init__(self, app, group, dupe):
Node.__init__(self, '')
self._app = app
self._group = group
self._dupe = dupe
self._data = None
self._data_delta = None
@property
def data(self):
if self._data is None:
self._data = self._app._get_display_info(self._dupe, self._group, False)
return self._data
@property
def data_delta(self):
if self._data_delta is None:
self._data_delta = self._app._get_display_info(self._dupe, self._group, True)
return self._data_delta
@property
def markable(self):
return self._app.results.is_markable(self._dupe)
@property
def marked(self):
return self._app.results.is_marked(self._dupe)
@marked.setter
def marked(self, value):
self._app.mark_dupe(self._dupe, value)
class ResultTree(GUIObject, Tree):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
Tree.__init__(self)
self._power_marker = False
self._delta_values = False
self._sort_descriptors = (0, True)
#--- Override
def connect(self):
GUIObject.connect(self)
self._refresh()
self.view.refresh()
def _select_nodes(self, nodes):
Tree._select_nodes(self, nodes)
self.app._select_dupes(map(attrgetter('_dupe'), nodes))
#--- Private
def _refresh(self):
self.clear()
if not self.power_marker:
for group in self.app.results.groups:
group_node = DupeNode(self.app, group, group.ref)
self.append(group_node)
for dupe in group.dupes:
group_node.append(DupeNode(self.app, group, dupe))
else:
for dupe in self.app.results.dupes:
group = self.app.results.get_group_of_duplicate(dupe)
self.append(DupeNode(self.app, group, dupe))
if self.app.selected_dupes:
to_find = set(self.app.selected_dupes)
nodes = list(self.findall(lambda n: n is not self and n._dupe in to_find))
self.selected_nodes = nodes
#--- Public
def get_node_value(self, path, column):
try:
node = self.get_node(path)
except IndexError:
return '---'
if self.delta_values:
return node.data_delta[column]
else:
return node.data[column]
def rename_selected(self, newname):
node = self.selected_node
node._data = None
node._data_delta = None
return self.app.rename_selected(newname)
def root_children_counts(self):
# This is a speed optimization for cases where there's a lot of results so that there is
# not thousands of children_count queries when expandAll is called.
return [len(node) for node in self]
def sort(self, key, asc):
if self.power_marker:
self.app.results.sort_dupes(key, asc, self.delta_values)
else:
self.app.results.sort_groups(key, asc)
self._sort_descriptors = (key, asc)
self._refresh()
self.view.refresh()
#--- 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
key, asc = self._sort_descriptors
self.sort(key, asc)
self._refresh()
self.view.refresh()
@property
def delta_values(self):
return self._delta_values
@delta_values.setter
def delta_values(self, value):
if value == self._delta_values:
return
self._delta_values = value
self._refresh()
self.view.refresh()
#--- Event Handlers
def marking_changed(self):
self.view.invalidate_markings()
def results_changed(self):
self._refresh()
self.view.refresh()
def results_switched(self):
# What we want to to here is that instead of restoring selected *dupes* after refresh, we
# restore selected *paths*.
paths = self.selected_paths
self._refresh()
self.selected_paths = paths
self.view.refresh()

23
core/gui/stats_label.py Normal file
View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-11
# Copyright 2010 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 .base import GUIObject
class StatsLabel(GUIObject):
def connect(self):
GUIObject.connect(self)
self.view.refresh()
@property
def display(self):
return self.app.stat_line
def results_changed(self):
self.view.refresh()
marking_changed = results_changed

View File

@@ -1,414 +0,0 @@
# Created By: Virgil Dupras
# Created On: 2006/11/11
# Copyright 2010 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 tempfile
import shutil
import logging
import os.path as op
from nose.tools import eq_
from hsutil.path import Path
from hsutil.testcase import TestCase
from hsutil.decorators import log_calls
from hsutil import io
from . import data
from .results_test import GetTestGroups
from .. import engine, fs
try:
from ..app_cocoa import DupeGuru as DupeGuruBase
except ImportError:
from nose.plugins.skip import SkipTest
raise SkipTest("These tests can only be run on OS X")
from ..gui.details_panel import DetailsPanel
class DupeGuru(DupeGuruBase):
def __init__(self):
DupeGuruBase.__init__(self, data, '/tmp', appid=4)
def _start_job(self, jobid, func):
func(nulljob)
def r2np(rows):
#Transforms a list of rows [1,2,3] into a list of node paths [[1],[2],[3]]
return [[i] for i in rows]
class CallLogger(object):
"""This is a dummy object that logs all calls made to it.
It is used to simulate the GUI layer.
"""
def __init__(self):
self.calls = []
def __getattr__(self, func_name):
def func(*args, **kw):
self.calls.append(func_name)
return func
def clear_calls(self):
del self.calls[:]
class TCDupeGuru(TestCase):
def setUp(self):
self.app = DupeGuru()
self.dpanel_gui = CallLogger()
self.dpanel = DetailsPanel(self.dpanel_gui, self.app)
self.objects,self.matches,self.groups = GetTestGroups()
self.app.results.groups = self.groups
tmppath = self.tmppath()
io.mkdir(tmppath + 'foo')
io.mkdir(tmppath + 'bar')
self.app.directories.add_path(tmppath)
def check_gui_calls(self, gui, expected, verify_order=False):
"""Checks that the expected calls have been made to 'gui', then clears the log.
`expected` is an iterable of strings representing method names.
If `verify_order` is True, the order of the calls matters.
"""
if verify_order:
eq_(gui.calls, expected)
else:
eq_(set(gui.calls), set(expected))
gui.clear_calls()
def check_gui_calls_partial(self, gui, expected=None, not_expected=None):
"""Checks that the expected calls have been made to 'gui', then clears the log.
`expected` is an iterable of strings representing method names. Order doesn't matter.
Moreover, if calls have been made that are not in expected, no failure occur.
`not_expected` can be used for a more explicit check (rather than calling `check_gui_calls`
with an empty `expected`) to assert that calls have *not* been made.
"""
calls = set(gui.calls)
if expected is not None:
expected = set(expected)
not_called = expected - calls
assert not not_called, u"These calls haven't been made: {0}".format(not_called)
if not_expected is not None:
not_expected = set(not_expected)
called = not_expected & calls
assert not called, u"These calls shouldn't have been made: {0}".format(called)
gui.clear_calls()
def clear_gui_calls(self):
for attr in dir(self):
if attr.endswith('_gui'):
gui = getattr(self, attr)
if hasattr(gui, 'calls'): # We might have test methods ending with '_gui'
gui.clear_calls()
def test_GetObjects(self):
app = self.app
objects = self.objects
groups = self.groups
g,d = app.GetObjects([0])
self.assert_(g is groups[0])
self.assert_(d is None)
g,d = app.GetObjects([0,0])
self.assert_(g is groups[0])
self.assert_(d is objects[1])
g,d = app.GetObjects([1,0])
self.assert_(g is groups[1])
self.assert_(d is objects[4])
def test_GetObjects_after_sort(self):
app = self.app
objects = self.objects
groups = self.groups[:] #To keep the old order in memory
app.sort_groups(0,False) #0 = Filename
#Now, the group order is supposed to be reversed
g,d = app.GetObjects([0,0])
self.assert_(g is groups[1])
self.assert_(d is objects[4])
def test_GetObjects_out_of_range(self):
app = self.app
self.assertEqual((None,None),app.GetObjects([2]))
self.assertEqual((None,None),app.GetObjects([]))
self.assertEqual((None,None),app.GetObjects([1,2]))
def test_selected_result_node_paths(self):
# app.selected_dupes is correctly converted into node paths
app = self.app
objects = self.objects
paths = [[0, 0], [0, 1], [1]]
app.SelectResultNodePaths(paths)
eq_(app.selected_result_node_paths(), paths)
def test_selected_result_node_paths_after_deletion(self):
# cases where the selected dupes aren't there are correctly handled
app = self.app
objects = self.objects
paths = [[0, 0], [0, 1], [1]]
app.SelectResultNodePaths(paths)
app.RemoveSelected()
# The first 2 dupes have been removed. The 3rd one is a ref. it stays there, in first pos.
eq_(app.selected_result_node_paths(), [[0]]) # no exception
def test_selectResultNodePaths(self):
app = self.app
objects = self.objects
app.SelectResultNodePaths([[0,0],[0,1]])
self.assertEqual(2,len(app.selected_dupes))
self.assert_(app.selected_dupes[0] is objects[1])
self.assert_(app.selected_dupes[1] is objects[2])
def test_selectResultNodePaths_with_ref(self):
app = self.app
objects = self.objects
app.SelectResultNodePaths([[0,0],[0,1],[1]])
self.assertEqual(3,len(app.selected_dupes))
self.assert_(app.selected_dupes[0] is objects[1])
self.assert_(app.selected_dupes[1] is objects[2])
self.assert_(app.selected_dupes[2] is self.groups[1].ref)
def test_selectResultNodePaths_empty(self):
self.app.SelectResultNodePaths([])
self.assertEqual(0,len(self.app.selected_dupes))
def test_selectResultNodePaths_after_sort(self):
app = self.app
objects = self.objects
groups = self.groups[:] #To keep the old order in memory
app.sort_groups(0,False) #0 = Filename
#Now, the group order is supposed to be reversed
app.SelectResultNodePaths([[0,0],[1],[1,0]])
self.assertEqual(3,len(app.selected_dupes))
self.assert_(app.selected_dupes[0] is objects[4])
self.assert_(app.selected_dupes[1] is groups[0].ref)
self.assert_(app.selected_dupes[2] is objects[1])
def test_selectResultNodePaths_out_of_range(self):
app = self.app
app.SelectResultNodePaths([[0,0],[0,1],[1],[1,1],[2]])
self.assertEqual(3,len(app.selected_dupes))
def test_selected_powermarker_node_paths(self):
# app.selected_dupes is correctly converted into paths
app = self.app
objects = self.objects
paths = r2np([0, 1, 2])
app.SelectPowerMarkerNodePaths(paths)
eq_(app.selected_powermarker_node_paths(), paths)
def test_selected_powermarker_node_paths_after_deletion(self):
# cases where the selected dupes aren't there are correctly handled
app = self.app
objects = self.objects
paths = r2np([0, 1, 2])
app.SelectPowerMarkerNodePaths(paths)
app.RemoveSelected()
eq_(app.selected_powermarker_node_paths(), []) # no exception
def test_selectPowerMarkerRows(self):
app = self.app
objects = self.objects
app.SelectPowerMarkerNodePaths(r2np([0,1,2]))
self.assertEqual(3,len(app.selected_dupes))
self.assert_(app.selected_dupes[0] is objects[1])
self.assert_(app.selected_dupes[1] is objects[2])
self.assert_(app.selected_dupes[2] is objects[4])
def test_selectPowerMarkerRows_empty(self):
self.app.SelectPowerMarkerNodePaths([])
self.assertEqual(0,len(self.app.selected_dupes))
def test_selectPowerMarkerRows_after_sort(self):
app = self.app
objects = self.objects
app.sort_dupes(0,False) #0 = Filename
app.SelectPowerMarkerNodePaths(r2np([0,1,2]))
self.assertEqual(3,len(app.selected_dupes))
self.assert_(app.selected_dupes[0] is objects[4])
self.assert_(app.selected_dupes[1] is objects[2])
self.assert_(app.selected_dupes[2] is objects[1])
def test_selectPowerMarkerRows_out_of_range(self):
app = self.app
app.SelectPowerMarkerNodePaths(r2np([0,1,2,3]))
self.assertEqual(3,len(app.selected_dupes))
def test_toggleSelectedMark(self):
app = self.app
objects = self.objects
app.ToggleSelectedMarkState()
self.assertEqual(0,app.results.mark_count)
app.SelectPowerMarkerNodePaths(r2np([0,2]))
app.ToggleSelectedMarkState()
self.assertEqual(2,app.results.mark_count)
self.assert_(not app.results.is_marked(objects[0]))
self.assert_(app.results.is_marked(objects[1]))
self.assert_(not app.results.is_marked(objects[2]))
self.assert_(not app.results.is_marked(objects[3]))
self.assert_(app.results.is_marked(objects[4]))
def test_refreshDetailsWithSelected(self):
self.app.SelectPowerMarkerNodePaths(r2np([0,2]))
eq_(self.dpanel.row(0), ('Filename', 'bar bleh', 'foo bar'))
self.check_gui_calls(self.dpanel_gui, ['refresh'])
self.app.SelectPowerMarkerNodePaths([])
eq_(self.dpanel.row(0), ('Filename', '---', '---'))
self.check_gui_calls(self.dpanel_gui, ['refresh'])
def test_makeSelectedReference(self):
app = self.app
objects = self.objects
groups = self.groups
app.SelectPowerMarkerNodePaths(r2np([0,2]))
app.MakeSelectedReference()
self.assert_(groups[0].ref is objects[1])
self.assert_(groups[1].ref is objects[4])
def test_makeSelectedReference_by_selecting_two_dupes_in_the_same_group(self):
app = self.app
objects = self.objects
groups = self.groups
app.SelectPowerMarkerNodePaths(r2np([0,1,2]))
#Only 0 and 2 must go ref, not 1 because it is a part of the same group
app.MakeSelectedReference()
self.assert_(groups[0].ref is objects[1])
self.assert_(groups[1].ref is objects[4])
def test_removeSelected(self):
app = self.app
app.SelectPowerMarkerNodePaths(r2np([0,2]))
app.RemoveSelected()
self.assertEqual(1,len(app.results.dupes))
app.RemoveSelected()
self.assertEqual(1,len(app.results.dupes))
app.SelectPowerMarkerNodePaths(r2np([0,2]))
app.RemoveSelected()
self.assertEqual(0,len(app.results.dupes))
def test_addDirectory_simple(self):
# There's already a directory in self.app, so adding another once makes 2 of em
app = self.app
eq_(app.add_directory(self.datadirpath()), 0)
eq_(len(app.directories), 2)
def test_addDirectory_already_there(self):
app = self.app
self.assertEqual(0,app.add_directory(self.datadirpath()))
self.assertEqual(1,app.add_directory(self.datadirpath()))
def test_addDirectory_does_not_exist(self):
app = self.app
self.assertEqual(2,app.add_directory('/does_not_exist'))
def test_ignore(self):
app = self.app
app.SelectPowerMarkerNodePaths(r2np([2])) #The dupe of the second, 2 sized group
app.AddSelectedToIgnoreList()
self.assertEqual(1,len(app.scanner.ignore_list))
app.SelectPowerMarkerNodePaths(r2np([0])) #first dupe of the 3 dupes group
app.AddSelectedToIgnoreList()
#BOTH the ref and the other dupe should have been added
self.assertEqual(3,len(app.scanner.ignore_list))
def test_purgeIgnoreList(self):
app = self.app
p1 = self.filepath('zerofile')
p2 = self.filepath('zerofill')
dne = '/does_not_exist'
app.scanner.ignore_list.Ignore(dne,p1)
app.scanner.ignore_list.Ignore(p2,dne)
app.scanner.ignore_list.Ignore(p1,p2)
app.PurgeIgnoreList()
self.assertEqual(1,len(app.scanner.ignore_list))
self.assert_(app.scanner.ignore_list.AreIgnored(p1,p2))
self.assert_(not app.scanner.ignore_list.AreIgnored(dne,p1))
def test_only_unicode_is_added_to_ignore_list(self):
def FakeIgnore(first,second):
if not isinstance(first,unicode):
self.fail()
if not isinstance(second,unicode):
self.fail()
app = self.app
app.scanner.ignore_list.Ignore = FakeIgnore
app.SelectPowerMarkerNodePaths(r2np([2])) #The dupe of the second, 2 sized group
app.AddSelectedToIgnoreList()
def test_GetOutlineViewChildCounts_out_of_range(self):
# Out of range requests don't crash and return an empty value
app = self.app
# [0, 2] is out of range
eq_(app.GetOutlineViewChildCounts(1, [0, 2]), []) # no crash
def test_GetOutlineViewValues_out_of_range(self):
# Out of range requests don't crash and return an empty value
app = self.app
# [0, 2] is out of range
# Directories
eq_(app.GetOutlineViewValues(1, [0, 2]), []) # no crash
# Normal results
app.GetOutlineViewValues(0, [42, 0]) # no crash
class TCDupeGuru_renameSelected(TestCase):
def setUp(self):
p = self.tmppath()
fp = open(unicode(p + 'foo bar 1'),mode='w')
fp.close()
fp = open(unicode(p + 'foo bar 2'),mode='w')
fp.close()
fp = open(unicode(p + 'foo bar 3'),mode='w')
fp.close()
files = fs.get_files(p)
matches = engine.getmatches(files)
groups = engine.get_groups(matches)
g = groups[0]
g.prioritize(lambda x:x.name)
app = DupeGuru()
app.results.groups = groups
self.app = app
self.groups = groups
self.p = p
self.files = files
def test_simple(self):
app = self.app
g = self.groups[0]
app.SelectPowerMarkerNodePaths(r2np([0]))
assert app.RenameSelected('renamed')
names = io.listdir(self.p)
assert 'renamed' in names
assert 'foo bar 2' not in names
eq_(g.dupes[0].name, 'renamed')
def test_none_selected(self):
app = self.app
g = self.groups[0]
app.SelectPowerMarkerNodePaths([])
self.mock(logging, 'warning', log_calls(lambda msg: None))
assert not app.RenameSelected('renamed')
msg = logging.warning.calls[0]['msg']
eq_('dupeGuru Warning: list index out of range', msg)
names = io.listdir(self.p)
assert 'renamed' not in names
assert 'foo bar 2' in names
eq_(g.dupes[0].name, 'foo bar 2')
def test_name_already_exists(self):
app = self.app
g = self.groups[0]
app.SelectPowerMarkerNodePaths(r2np([0]))
self.mock(logging, 'warning', log_calls(lambda msg: None))
assert not app.RenameSelected('foo bar 1')
msg = logging.warning.calls[0]['msg']
assert msg.startswith('dupeGuru Warning: \'foo bar 1\' already exists in')
names = io.listdir(self.p)
assert 'foo bar 1' in names
assert 'foo bar 2' in names
eq_(g.dupes[0].name, 'foo bar 2')

View File

@@ -7,6 +7,9 @@
# http://www.hardcoded.net/licenses/hs_license # http://www.hardcoded.net/licenses/hs_license
import os import os
import logging
from nose.tools import eq_
from hsutil.testcase import TestCase from hsutil.testcase import TestCase
from hsutil import io from hsutil import io
@@ -16,8 +19,12 @@ import hsutil.files
from hsutil.job import nulljob from hsutil.job import nulljob
from . import data from . import data
from .. import app, fs from .results_test import GetTestGroups
from .. import app, fs, engine
from ..app import DupeGuru as DupeGuruBase from ..app import DupeGuru as DupeGuruBase
from ..gui.details_panel import DetailsPanel
from ..gui.directory_tree import DirectoryTree
from ..gui.result_tree import ResultTree
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
def __init__(self): def __init__(self):
@@ -27,6 +34,23 @@ class DupeGuru(DupeGuruBase):
func(nulljob) func(nulljob)
class CallLogger(object):
"""This is a dummy object that logs all calls made to it.
It is used to simulate the GUI layer.
"""
def __init__(self):
self.calls = []
def __getattr__(self, func_name):
def func(*args, **kw):
self.calls.append(func_name)
return func
def clear_calls(self):
del self.calls[:]
class TCDupeGuru(TestCase): class TCDupeGuru(TestCase):
cls_tested_module = app cls_tested_module = app
def test_apply_filter_calls_results_apply_filter(self): def test_apply_filter_calls_results_apply_filter(self):
@@ -133,3 +157,330 @@ class TCDupeGuru_clean_empty_dirs(TestCase):
self.assertEqual(Path('not-empty/empty'), calls[1]['path']) self.assertEqual(Path('not-empty/empty'), calls[1]['path'])
self.assertEqual(Path('not-empty'), calls[2]['path']) self.assertEqual(Path('not-empty'), calls[2]['path'])
class TCDupeGuruWithResults(TestCase):
def setUp(self):
self.app = DupeGuru()
self.objects,self.matches,self.groups = GetTestGroups()
self.app.results.groups = self.groups
self.dpanel_gui = CallLogger()
self.dpanel = DetailsPanel(self.dpanel_gui, self.app)
self.dtree_gui = CallLogger()
self.dtree = DirectoryTree(self.dtree_gui, self.app)
self.rtree_gui = CallLogger()
self.rtree = ResultTree(self.rtree_gui, self.app)
self.dpanel.connect()
self.dtree.connect()
self.rtree.connect()
tmppath = self.tmppath()
io.mkdir(tmppath + 'foo')
io.mkdir(tmppath + 'bar')
self.app.directories.add_path(tmppath)
def check_gui_calls(self, gui, expected, verify_order=False):
"""Checks that the expected calls have been made to 'gui', then clears the log.
`expected` is an iterable of strings representing method names.
If `verify_order` is True, the order of the calls matters.
"""
if verify_order:
eq_(gui.calls, expected)
else:
eq_(set(gui.calls), set(expected))
gui.clear_calls()
def check_gui_calls_partial(self, gui, expected=None, not_expected=None):
"""Checks that the expected calls have been made to 'gui', then clears the log.
`expected` is an iterable of strings representing method names. Order doesn't matter.
Moreover, if calls have been made that are not in expected, no failure occur.
`not_expected` can be used for a more explicit check (rather than calling `check_gui_calls`
with an empty `expected`) to assert that calls have *not* been made.
"""
calls = set(gui.calls)
if expected is not None:
expected = set(expected)
not_called = expected - calls
assert not not_called, u"These calls haven't been made: {0}".format(not_called)
if not_expected is not None:
not_expected = set(not_expected)
called = not_expected & calls
assert not called, u"These calls shouldn't have been made: {0}".format(called)
gui.clear_calls()
def clear_gui_calls(self):
for attr in dir(self):
if attr.endswith('_gui'):
gui = getattr(self, attr)
if hasattr(gui, 'calls'): # We might have test methods ending with '_gui'
gui.clear_calls()
def test_GetObjects(self):
objects = self.objects
groups = self.groups
n = self.rtree.get_node([0])
assert n._group is groups[0]
assert n._dupe is objects[0]
n = self.rtree.get_node([0, 0])
assert n._group is groups[0]
assert n._dupe is objects[1]
n = self.rtree.get_node([1, 0])
assert n._group is groups[1]
assert n._dupe is objects[4]
def test_GetObjects_after_sort(self):
objects = self.objects
groups = self.groups[:] # we need an un-sorted reference
self.rtree.sort(0, False) #0 = Filename
n = self.rtree.get_node([0, 0])
assert n._group is groups[1]
assert n._dupe is objects[4]
def test_selected_result_node_paths(self):
# app.selected_dupes is correctly converted into node paths
paths = [[0, 0], [0, 1], [1]]
self.rtree.selected_paths = paths
eq_(self.rtree.selected_paths, paths)
def test_selected_result_node_paths_after_deletion(self):
# cases where the selected dupes aren't there are correctly handled
paths = [[0, 0], [0, 1], [1]]
self.rtree.selected_paths = paths
self.app.remove_selected()
# The first 2 dupes have been removed. The 3rd one is a ref. it stays there, in first pos.
eq_(self.rtree.selected_paths, [[0]]) # no exception
def test_selectResultNodePaths(self):
app = self.app
objects = self.objects
self.rtree.selected_paths = [[0, 0], [0, 1]]
eq_(len(app.selected_dupes), 2)
assert app.selected_dupes[0] is objects[1]
assert app.selected_dupes[1] is objects[2]
def test_selectResultNodePaths_with_ref(self):
app = self.app
objects = self.objects
self.rtree.selected_paths = [[0, 0], [0, 1], [1]]
eq_(len(app.selected_dupes), 3)
assert app.selected_dupes[0] is objects[1]
assert app.selected_dupes[1] is objects[2]
assert app.selected_dupes[2] is self.groups[1].ref
def test_selectResultNodePaths_after_sort(self):
app = self.app
objects = self.objects
groups = self.groups[:] #To keep the old order in memory
self.rtree.sort(0, False) #0 = Filename
#Now, the group order is supposed to be reversed
self.rtree.selected_paths = [[0, 0], [1], [1, 0]]
eq_(len(app.selected_dupes), 3)
assert app.selected_dupes[0] is objects[4]
assert app.selected_dupes[1] is groups[0].ref
assert app.selected_dupes[2] is objects[1]
def test_selected_powermarker_node_paths(self):
# app.selected_dupes is correctly converted into paths
app = self.app
objects = self.objects
self.rtree.power_marker = True
self.rtree.selected_paths = [[0], [1], [2]]
self.rtree.power_marker = False
eq_(self.rtree.selected_paths, [[0, 0], [0, 1], [1, 0]])
def test_selected_powermarker_node_paths_after_deletion(self):
# cases where the selected dupes aren't there are correctly handled
app = self.app
objects = self.objects
self.rtree.power_marker = True
self.rtree.selected_paths = [[0], [1], [2]]
app.remove_selected()
eq_(self.rtree.selected_paths, []) # no exception
def test_selectPowerMarkerRows(self):
app = self.app
objects = self.objects
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
eq_(len(app.selected_dupes), 3)
assert app.selected_dupes[0] is objects[1]
assert app.selected_dupes[1] is objects[2]
assert app.selected_dupes[2] is objects[4]
def test_selectPowerMarkerRows_empty(self):
self.rtree.selected_paths = []
eq_(len(self.app.selected_dupes), 0)
def test_selectPowerMarkerRows_after_sort(self):
app = self.app
objects = self.objects
self.rtree.power_marker = True
self.rtree.sort(0, False) #0 = Filename
self.rtree.selected_paths = [[0], [1], [2]]
eq_(len(app.selected_dupes), 3)
assert app.selected_dupes[0] is objects[4]
assert app.selected_dupes[1] is objects[2]
assert app.selected_dupes[2] is objects[1]
def test_toggleSelectedMark(self):
app = self.app
objects = self.objects
app.toggle_selected_mark_state()
eq_(app.results.mark_count, 0)
self.rtree.selected_paths = [[0, 0], [1, 0]]
app.toggle_selected_mark_state()
eq_(app.results.mark_count, 2)
assert not app.results.is_marked(objects[0])
assert app.results.is_marked(objects[1])
assert not app.results.is_marked(objects[2])
assert not app.results.is_marked(objects[3])
assert app.results.is_marked(objects[4])
def test_refreshDetailsWithSelected(self):
self.rtree.selected_paths = [[0, 0], [1, 0]]
eq_(self.dpanel.row(0), ('Filename', 'bar bleh', 'foo bar'))
self.check_gui_calls(self.dpanel_gui, ['refresh'])
self.rtree.selected_paths = []
eq_(self.dpanel.row(0), ('Filename', '---', '---'))
self.check_gui_calls(self.dpanel_gui, ['refresh'])
def test_makeSelectedReference(self):
app = self.app
objects = self.objects
groups = self.groups
self.rtree.selected_paths = [[0, 0], [1, 0]]
app.make_selected_reference()
assert groups[0].ref is objects[1]
assert groups[1].ref is objects[4]
def test_makeSelectedReference_by_selecting_two_dupes_in_the_same_group(self):
app = self.app
objects = self.objects
groups = self.groups
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
#Only [0, 0] and [1, 0] must go ref, not [0, 1] because it is a part of the same group
app.make_selected_reference()
assert groups[0].ref is objects[1]
assert groups[1].ref is objects[4]
def test_removeSelected(self):
app = self.app
self.rtree.selected_paths = [[0, 0], [1, 0]]
app.remove_selected()
eq_(len(app.results.dupes), 1)
app.remove_selected()
eq_(len(app.results.dupes), 1)
self.rtree.selected_path = [0, 0]
app.remove_selected()
eq_(len(app.results.dupes), 0)
def test_addDirectory_simple(self):
# There's already a directory in self.app, so adding another once makes 2 of em
app = self.app
eq_(app.add_directory(self.datadirpath()), 0)
eq_(len(app.directories), 2)
def test_addDirectory_already_there(self):
app = self.app
self.assertEqual(0,app.add_directory(self.datadirpath()))
self.assertEqual(1,app.add_directory(self.datadirpath()))
def test_addDirectory_does_not_exist(self):
app = self.app
self.assertEqual(2,app.add_directory('/does_not_exist'))
def test_ignore(self):
app = self.app
self.rtree.selected_path = [1, 0] #The dupe of the second, 2 sized group
app.add_selected_to_ignore_list()
eq_(len(app.scanner.ignore_list), 1)
self.rtree.selected_path = [0, 0] #first dupe of the 3 dupes group
app.add_selected_to_ignore_list()
#BOTH the ref and the other dupe should have been added
eq_(len(app.scanner.ignore_list), 3)
def test_purgeIgnoreList(self):
app = self.app
p1 = self.filepath('zerofile')
p2 = self.filepath('zerofill')
dne = '/does_not_exist'
app.scanner.ignore_list.Ignore(dne,p1)
app.scanner.ignore_list.Ignore(p2,dne)
app.scanner.ignore_list.Ignore(p1,p2)
app.purge_ignore_list()
self.assertEqual(1,len(app.scanner.ignore_list))
self.assert_(app.scanner.ignore_list.AreIgnored(p1,p2))
self.assert_(not app.scanner.ignore_list.AreIgnored(dne,p1))
def test_only_unicode_is_added_to_ignore_list(self):
def FakeIgnore(first,second):
if not isinstance(first,unicode):
self.fail()
if not isinstance(second,unicode):
self.fail()
app = self.app
app.scanner.ignore_list.Ignore = FakeIgnore
self.rtree.selected_path = [1, 0]
app.add_selected_to_ignore_list()
class TCDupeGuru_renameSelected(TestCase):
def setUp(self):
p = self.tmppath()
fp = open(unicode(p + 'foo bar 1'),mode='w')
fp.close()
fp = open(unicode(p + 'foo bar 2'),mode='w')
fp.close()
fp = open(unicode(p + 'foo bar 3'),mode='w')
fp.close()
files = fs.get_files(p)
matches = engine.getmatches(files)
groups = engine.get_groups(matches)
g = groups[0]
g.prioritize(lambda x:x.name)
app = DupeGuru()
app.results.groups = groups
self.app = app
self.groups = groups
self.p = p
self.files = files
self.rtree_gui = CallLogger()
self.rtree = ResultTree(self.rtree_gui, self.app)
self.rtree.connect()
def test_simple(self):
app = self.app
g = self.groups[0]
self.rtree.selected_path = [0, 0]
assert app.rename_selected('renamed')
names = io.listdir(self.p)
assert 'renamed' in names
assert 'foo bar 2' not in names
eq_(g.dupes[0].name, 'renamed')
def test_none_selected(self):
app = self.app
g = self.groups[0]
self.rtree.selected_paths = []
self.mock(logging, 'warning', log_calls(lambda msg: None))
assert not app.rename_selected('renamed')
msg = logging.warning.calls[0]['msg']
eq_('dupeGuru Warning: list index out of range', msg)
names = io.listdir(self.p)
assert 'renamed' not in names
assert 'foo bar 2' in names
eq_(g.dupes[0].name, 'foo bar 2')
def test_name_already_exists(self):
app = self.app
g = self.groups[0]
self.rtree.selected_path = [0, 0]
self.mock(logging, 'warning', log_calls(lambda msg: None))
assert not app.rename_selected('foo bar 1')
msg = logging.warning.calls[0]['msg']
assert msg.startswith('dupeGuru Warning: \'foo bar 1\' already exists in')
names = io.listdir(self.p)
assert 'foo bar 1' in names
assert 'foo bar 2' in names
eq_(g.dupes[0].name, 'foo bar 2')

View File

@@ -150,7 +150,7 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
CGImageSourceRef source; CGImageSourceRef source;
CGImageRef image; CGImageRef image;
size_t width, height; size_t width, height;
int block_count, block_width, block_height, block_xcount, block_ycount, i; int block_count, block_width, block_height, i;
width = 0; width = 0;
height = 0; height = 0;
@@ -192,23 +192,24 @@ static PyObject* block_osx_getblocks(PyObject *self, PyObject *args)
block_width = max(width/block_count, 1); block_width = max(width/block_count, 1);
block_height = max(height/block_count, 1); block_height = max(height/block_count, 1);
/* block_count might have changed */
block_xcount = (width / block_width);
block_ycount = (height / block_height);
result = PyList_New(block_xcount * block_ycount); result = PyList_New(block_count * block_count);
if (result == NULL) { if (result == NULL) {
return NULL; return NULL;
} }
for(i=0; i<block_ycount; i++) for(i=0; i<block_count; i++) {
{ int j, top;
int j; top = min(i*block_height, height-block_height);
for(j=0; j<block_xcount; j++) for(j=0; j<block_count; j++) {
{ int left;
PyObject *block = getblock(bitmapData, width, height, j*block_width, i*block_height, left = min(j*block_width, width-block_width);
block_width, block_height); PyObject *block = getblock(bitmapData, width, height, left, top, block_width, block_height);
PyList_SET_ITEM(result, i*block_ycount+j, block); if (block == NULL) {
Py_DECREF(result);
return NULL;
}
PyList_SET_ITEM(result, i*block_count+j, block);
} }
} }

View File

@@ -1,3 +1,9 @@
- date: 2010-02-13
version: 5.7.2
description: |
* Fixed a crash upon quitting when support folder is not present. (#83)
* Fixed a crash during sorting. (#85)
* Fixed selection glitches, especially while renaming. (#93)
- date: 2010-01-19 - date: 2010-01-19
version: 5.7.1 version: 5.7.1
description: | description: |

View File

@@ -1,3 +1,8 @@
- date: 2010-02-11
version: 1.8.3
description: |
* Fixed a crash when reading certain pictures. [Mac OS X] (#94)
* Fixed selection glitches, especially while renaming. (#93)
- date: 2010-02-06 - date: 2010-02-06
version: 1.8.2 version: 1.8.2
description: | description: |
@@ -79,159 +84,145 @@
* Converted the Windows GUI to Qt, thus enabling multiprocessing and making the scanning process * Converted the Windows GUI to Qt, thus enabling multiprocessing and making the scanning process
faster. faster.
- date: 2009-03-24 - date: 2009-03-24
description: "* **Improved** scanning speed, mainly on OS X where all cores of the\
\ CPU are now used.\r\n* **Fixed** an occasional crash caused by permission issues.\r\
\n* **Fixed** a bug where the \"X discarded\" notice would show a too large number\
\ of discarded duplicates."
version: 1.6.0 version: 1.6.0
description: |
* **Improved** scanning speed, mainly on OS X where all cores of the CPU are now used.
* **Fixed** an occasional crash caused by permission issues.
* **Fixed** a bug where the "X discarded" notice would show a too large number of discarded duplicates.
- date: 2008-09-10 - date: 2008-09-10
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> a notice in the status bar when\
\ matches were discarded during the scan.</li>\n\t\t\t\t\t\t<li><b>Improved</b>\
\ duplicate prioritization (smartly chooses which file you will keep).</li>\n\t\
\t\t\t\t\t<li><b>Improved</b> scan progress feedback.</li>\n\t\t\t\t\t\t<li><b>Improved</b>\
\ responsiveness of the user interface for certain actions.</li>\n\t\t \
\ </ul>"
version: 1.5.0 version: 1.5.0
description: |
* **Added** a notice in the status bar when matches were discarded during the scan.
* **Improved** duplicate prioritization (smartly chooses which file you will keep).
* **Improved** scan progress feedback.
* **Improved** responsiveness of the user interface for certain actions.
- date: 2008-07-28 - date: 2008-07-28
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> iPhoto compatibility on Mac\
\ OS X.</li>\n\t\t\t\t\t\t<li><b>Improved</b> the speed of results loading and\
\ saving.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a crash sometimes occurring during\
\ duplicate deletion.</li>\n\t\t </ul>"
version: 1.4.2 version: 1.4.2
description: |
* **Improved** iPhoto compatibility on Mac OS X.
* **Improved** the speed of results loading and saving.
* **Fixed** a crash sometimes occurring during duplicate deletion.
- date: 2008-04-12 - date: 2008-04-12
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> iPhoto Library loading feedback\
\ on Mac OS X.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> the directory selection dialog.\
\ Bundles can be selected again on Mac OS X.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ \"Clear Ignore List\" crash in Windows.</li>\n\t\t </ul>"
version: 1.4.1 version: 1.4.1
description: |
* **Improved** iPhoto Library loading feedback on Mac OS X.
* **Fixed** the directory selection dialog. Bundles can be selected again on Mac OS X.
* **Fixed** "Clear Ignore List" crash in Windows.
- date: 2008-02-20 - date: 2008-02-20
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> iPhoto Library support on Mac OS\
\ X.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> occasional crashes when scanning corrupted\
\ pictures.</li>\n\t\t </ul>"
version: 1.4.0
- date: 2008-02-20
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> iPhoto Library support on Mac OS\
\ X.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> occasional crashes when scanning corrupted\
\ pictures.</li>\n\t\t </ul>"
version: 1.4.0 version: 1.4.0
description: |
* **Added** iPhoto Library support on Mac OS X.
* **Fixed** occasional crashes when scanning corrupted pictures.
- date: 2008-01-12 - date: 2008-01-12
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> scan, delete and move speed\
\ in situations where there were a lot of duplicates.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ occasional crashes when moving a lot of files at once.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ an issue sometimes preventing the application from starting at all.</li>\n\t\
\t </ul>"
version: 1.3.4 version: 1.3.4
description: |
* **Improved** scan, delete and move speed in situations where there were a lot of duplicates.
* **Fixed** occasional crashes when moving a lot of files at once.
* **Fixed** an issue sometimes preventing the application from starting at all.
- date: 2007-12-03 - date: 2007-12-03
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> the handling of low memory situations.</li>\n\
\t\t\t\t\t\t<li><b>Improved</b> the directory panel. The \"Remove\" button changes\
\ to \"Put Back\" when an excluded directory is selected.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ the directory selection dialog. iPhoto '08 library files can now be selected.</li>\n\
\t\t </ul>"
version: 1.3.3 version: 1.3.3
description: |
* **Improved** the handling of low memory situations.
* **Improved** the directory panel. The "Remove" button changes to "Put Back" when an excluded directory is selected.
* **Fixed** the directory selection dialog. iPhoto '08 library files can now be selected.
- date: 2007-11-24 - date: 2007-11-24
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> the \"Remove empty folders\" option.</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> results load/save issues.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ occasional status bar inaccuracies when the results are filtered.</li>\n\t\t\
\ </ul>"
version: 1.3.2 version: 1.3.2
description: |
* **Added** the "Remove empty folders" option.
* **Fixed** results load/save issues.
* **Fixed** occasional status bar inaccuracies when the results are filtered.
- date: 2007-10-21 - date: 2007-10-21
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> results loading speed.</li>\n\
\t\t\t\t\t\t<li><b>Improved</b> details panel's picture loading (made it asynchronous).</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> a bug where the stats line at the bottom would sometimes\
\ go confused while having a filter active.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ a bug under Windows where some duplicate markings would be lost.</li>\n\t\t\
\ </ul>"
version: 1.3.1 version: 1.3.1
description: |
* **Improved** results loading speed.
* **Improved** details panel's picture loading (made it asynchronous).
* **Fixed** a bug where the stats line at the bottom would sometimes go confused while having a filter active.
* **Fixed** a bug under Windows where some duplicate markings would be lost.
- date: 2007-09-22 - date: 2007-09-22
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> post scan filtering.</li>\n\t\t\
\t\t\t\t<li><b>Fixed</b> issues with the rename feature under Windows</li>\n\t\
\t\t\t\t\t<li><b>Fixed</b> some user interface annoyances under Windows</li>\n\
\t\t </ul>"
version: 1.3.0 version: 1.3.0
description: |
* **Added** post scan filtering.
* **Fixed** issues with the rename feature under Windows.
* **Fixed** some user interface annoyances under Windows.
- date: 2007-05-19 - date: 2007-05-19
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> UI responsiveness (using threads)\
\ under Mac OS X.</li>\n\t\t\t\t\t\t<li><b>Improved</b> result load/save speed\
\ and memory usage.</li>\n\t\t </ul>"
version: 1.2.1 version: 1.2.1
description: |
* **Improved** UI responsiveness (using threads) under Mac OS X.
* **Improved** result load/save speed and memory usage.
- date: 2007-03-17 - date: 2007-03-17
description: "<ul>\n\t\t\t\t\t\t<li><b>Changed</b> the picture decoding libraries\
\ for both Mac OS X and Windows. The Mac OS X version uses the Core Graphics library\
\ and the Windows version uses the .NET framework imaging capabilities. This results\
\ in much faster scans. As a bonus, the Mac OS X version of dupeGuru PE now supports\
\ RAW images.</li>\n\t\t </ul>"
version: 1.2.0 version: 1.2.0
description: |
* **Changed** the picture decoding libraries for both Mac OS X and Windows. The Mac OS X version
uses the Core Graphics library and the Windows version uses the .NET framework imaging
capabilities. This results in much faster scans. As a bonus, the Mac OS X version of dupeGuru
PE now supports RAW images.
- date: 2007-02-11 - date: 2007-02-11
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> Re-orderable columns. In fact,\
\ I re-added the feature which was lost in the C# conversion in 2.4.0 (Windows).</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> a bug with all the Delete/Move/Copy actions with\
\ certain kinds of files.</li>\n\t\t </ul>"
version: 1.1.6 version: 1.1.6
description: |
* **Added** Re-orderable columns. In fact, I re-added the feature which was lost in the C#
conversion in 1.1.0 (Windows).
* **Fixed** a bug with all the Delete/Move/Copy actions with certain kinds of files.
- date: 2007-01-11 - date: 2007-01-11
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with the Move action.</li>\n\
\t\t </ul>"
version: 1.1.5 version: 1.1.5
description: |
* **Fixed** a bug with the Move action.
- date: 2007-01-09 - date: 2007-01-09
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a \"ghosting\" bug. Dupes deleted\
\ by dupeGuru would sometimes come back in subsequent scans (Windows).</li>\n\t\
\t\t\t\t\t<li><b>Fixed</b> bugs sometimes making dupeGuru crash when marking a\
\ dupe (Windows).</li>\n\t\t\t\t\t\t<li><b>Fixed</b> some minor visual glitches\
\ (Windows).</li>\n\t\t </ul>"
version: 1.1.4 version: 1.1.4
description: |
* **Fixed** a "ghosting" bug. Dupes deleted by dupeGuru would sometimes come back in subsequent scans (Windows).
* **Fixed** bugs sometimes making dupeGuru crash when marking a dupe (Windows).
* **Fixed** some minor visual glitches (Windows).
- date: 2006-12-23 - date: 2006-12-23
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> the caching system. This makes\
\ duplicate scans significantly faster.</li>\n\t\t\t\t\t\t<li><b>Improved</b>\
\ the rename file dialog to exclude the extension from the original selection\
\ (so when you start typing your new filename, it doesn't overwrite it) (Windows).</li>\n\
\t\t\t\t\t\t<li><b>Changed</b> some menu key shortcuts that created conflicts\
\ (Windows).</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug preventing files from \"\
reference\" directories to be displayed in blue in the results (Windows).</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> a bug preventing some files to be sent to the recycle\
\ bin (Windows).</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with the \"Remove\"\
\ button of the directories panel (Windows).</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ a bug in the packaging preventing certain Windows configurations to start dupeGuru\
\ at all.</li>\n\t\t </ul>"
version: 1.1.3 version: 1.1.3
description: |
* **Improved** the caching system. This makes duplicate scans significantly faster.
* **Improved** the rename file dialog to exclude the extension from the original selection (so
when you start typing your new filename, it doesn't overwrite it) (Windows).
* **Changed** some menu key shortcuts that created conflicts (Windows).
* **Fixed** a bug preventing files from "reference" directories to be displayed in blue in the results (Windows).
* **Fixed** a bug preventing some files to be sent to the recycle bin (Windows).
* **Fixed** a bug with the "Remove" button of the directories panel (Windows).
* **Fixed** a bug in the packaging preventing certain Windows configurations to start dupeGuru at all.
- date: 2006-11-18 - date: 2006-11-18
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with directory states.</li>\n\
\t\t </ul>"
version: 1.1.2 version: 1.1.2
description: |
* **Fixed** a bug with directory states.
- date: 2006-11-17 - date: 2006-11-17
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug causing the ignore list not\
\ to be saved.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with selection under Power\
\ Marker mode.</li>\n\t\t </ul>"
version: 1.1.1 version: 1.1.1
description: |
* **Fixed** a bug causing the ignore list not to be saved.
* **Fixed** a bug with selection under Power Marker mode.
- date: 2006-11-15 - date: 2006-11-15
description: "<ul>\n\t\t\t\t\t\t<li><b>Changed</b> the Windows interface. It is\
\ now .NET based.</li>\n\t\t\t\t\t\t<li><b>Added</b> an auto-update feature to\
\ the windows version.</li>\n\t\t\t\t\t\t<li><b>Changed</b> the way power marking\
\ works. It is now a mode instead of a separate window.</li>\n\t\t\t\t\t\t<li><b>Changed</b>\
\ the \"Size (MB)\" column for a \"Size (KB)\" column. The values are now \"ceiled\"\
\ instead of rounded. Therefore, a size \"0\" is now really 0 bytes, not just\
\ a value too small to be rounded up. It is also the case for delta values.</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> a bug sometimes making delete and move operations\
\ stall.</li>\n\t\t </ul>"
version: 1.1.0 version: 1.1.0
description: |
* **Changed** the Windows interface. It is now .NET based.
* **Added** an auto-update feature to the windows version.
* **Changed** the way power marking works. It is now a mode instead of a separate window.
* **Changed** the "Size (MB)" column for a "Size (KB)" column. The values are now "ceiled"
instead of rounded. Therefore, a size "0" is now really 0 bytes, not just a value too small to
be rounded up. It is also the case for delta values.
* **Fixed** a bug sometimes making delete and move operations stall.
- date: 2006-10-12 - date: 2006-10-12
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> an auto-update feature in the Mac\
\ OS X version (with Sparkle).</li>\n\t\t \t<li><b>Fixed</b> a bug\
\ sometimes causing inaccuracies of the Match %.</li>\n\t\t </ul>"
version: 1.0.5 version: 1.0.5
description: |
* **Added** an auto-update feature in the Mac OS X version (with Sparkle).
* **Fixed** a bug sometimes causing inaccuracies of the Match %.
- date: 2006-09-21 - date: 2006-09-21
description: "<ul>\n\t\t \t<li><b>Fixed</b> a bug with the cache system.</li>\n\
\t\t </ul>"
version: 1.0.4 version: 1.0.4
description: |
* **Fixed** a bug with the cache system.
- date: 2006-09-15 - date: 2006-09-15
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> the ability to search for scaled\
\ duplicates.</li>\n\t\t\t\t\t\t<li><b>Added</b> a cache system for faster scans.</li>\n\
\t\t \t<li><b>Improved</b> speed of the scanning engine.</li>\n\t\t\
\ </ul>"
version: 1.0.3 version: 1.0.3
description: |
* **Added** the ability to search for scaled duplicates.
* **Added** a cache system for faster scans.
* **Improved** speed of the scanning engine.
- date: 2006-09-11 - date: 2006-09-11
description: "<ul>\n\t\t \t<li><b>Improved</b> speed of the scanning\
\ engine.</li>\n\t\t\t\t\t\t<li><b>Improved</b> the display of pictures in the\
\ details panel (Windows).</li>\n\t\t </ul>"
version: 1.0.2 version: 1.0.2
description: |
* **Improved** speed of the scanning engine.
* **Improved** the display of pictures in the details panel (Windows).
- date: 2006-09-08 - date: 2006-09-08
description: "<ul>\n\t\t \t<li>Initial release.</li>\n\t\t \
\ </ul>"
version: 1.0.0 version: 1.0.0
description: |
* Initial release.

View File

@@ -1,3 +1,10 @@
- date: 2010-02-10
version: 2.9.2
description: |
* dupeGuru is now 64-bit on Mac OS X!
* Fixed a crash upon quitting when support folder is not present. (#83)
* Fixed a crash during sorting. (#85)
* Fixed selection glitches, especially while renaming. (#93)
- date: 2010-01-13 - date: 2010-01-13
version: 2.9.1 version: 2.9.1
description: | description: |

View File

@@ -10,10 +10,59 @@
import sys import sys
import os import os
import os.path as op import os.path as op
import shutil
import yaml import yaml
from hsutil.build import build_dmg, add_to_pythonpath from hsutil.build import build_dmg, add_to_pythonpath, print_and_do
def package_cocoa(edition):
app_path = {
'se': 'cocoa/se/build/release/dupeGuru.app',
'me': 'cocoa/me/build/release/dupeGuru ME.app',
'pe': 'cocoa/pe/build/release/dupeGuru PE.app',
}[edition]
build_dmg(app_path, '.')
def package_qt(edition):
# 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
if sys.platform != "win32":
print "Qt packaging only works under Windows."
return
add_to_pythonpath('.')
add_to_pythonpath('qt')
add_to_pythonpath(op.join('qt', edition))
os.chdir(op.join('qt', edition))
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 dg{0}.spec".format(edition))
os.remove('verinfo_tmp')
print_and_do("del dist\\*90.dll") # They're in vcredist, no need to include them
print_and_do("del dist\\POWRPROF.dll") # no need of that crap
print_and_do("del dist\\SHLWAPI.dll") # no need of that crap
print_and_do("xcopy /Y /S /I ..\\..\\help_me\\dupeguru_me_help dist\\help")
# AdvancedInstaller.com has to be in your PATH
# this is so we don'a have to re-commit installer.aip at every version change
shutil.copy('installer.aip', 'installer_tmp.aip')
print_and_do('AdvancedInstaller.com /edit installer_tmp.aip /SetVersion %s' % version)
print_and_do('AdvancedInstaller.com /build installer_tmp.aip -force')
os.remove('installer_tmp.aip')
os.chdir(op.join('..', '..'))
def main(): def main():
conf = yaml.load(open('conf.yaml')) conf = yaml.load(open('conf.yaml'))
@@ -25,21 +74,9 @@ def main():
return return
print "Packaging dupeGuru {0} with UI {1}".format(edition.upper(), ui) print "Packaging dupeGuru {0} with UI {1}".format(edition.upper(), ui)
if ui == 'cocoa': if ui == 'cocoa':
app_path = { package_cocoa(edition)
'se': 'cocoa/se/build/release/dupeGuru.app',
'me': 'cocoa/me/build/release/dupeGuru ME.app',
'pe': 'cocoa/pe/build/release/dupeGuru PE.app',
}[edition]
build_dmg(app_path, '.')
elif ui == 'qt': elif ui == 'qt':
if sys.platform != "win32": package_qt(edition)
print "Qt packaging only works under Windows."
return
add_to_pythonpath('.')
add_to_pythonpath('qt')
os.chdir(op.join('qt', edition))
os.system('python build.py')
os.chdir(op.join('..', '..'))
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -12,13 +12,12 @@ import logging
import os import os
import os.path as op import os.path as op
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox
from hsutil import job from hsutil import job
from hsutil.reg import RegistrationRequired 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 core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
from qtlib.about_box import AboutBox from qtlib.about_box import AboutBox
@@ -117,10 +116,19 @@ class DupeGuru(DupeGuruBase, QObject):
raise NotImplementedError() raise NotImplementedError()
#--- Override #--- Override
@staticmethod
def _open_path(path):
url = QUrl.fromLocalFile(unicode(path))
QDesktopServices.openUrl(url)
@staticmethod @staticmethod
def _recycle_dupe(dupe): def _recycle_dupe(dupe):
platform.recycle_file(dupe.path) platform.recycle_file(dupe.path)
@staticmethod
def _reveal_path(path):
DupeGuru._open_path(path[:-1])
def _start_job(self, jobid, func): def _start_job(self, jobid, func):
title = JOBID2TITLE[jobid] title = JOBID2TITLE[jobid]
try: try:
@@ -130,18 +138,14 @@ class DupeGuru(DupeGuruBase, QObject):
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." 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) QMessageBox.information(self.main_window, 'Action in progress', msg)
#--- Public def add_selected_to_ignore_list(self):
def add_dupes_to_ignore_list(self, duplicates): dupes = self.without_ref(self.selected_dupes)
for dupe in duplicates: if not dupes:
self.add_to_ignore_list(dupe) return
self.remove_duplicates(duplicates) title = "Add to Ignore List"
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes))
def apply_filter(self, filter): if self.main_window._confirm(title, msg):
DupeGuruBase.apply_filter(self, filter) DupeGuruBase.add_selected_to_ignore_list(self)
self.emit(SIGNAL('resultsChanged()'))
def askForRegCode(self):
self.reg.ask_for_code()
@demo_method @demo_method
def copy_or_move_marked(self, copy): def copy_or_move_marked(self, copy):
@@ -156,57 +160,22 @@ class DupeGuru(DupeGuruBase, QObject):
delete_marked = demo_method(DupeGuruBase.delete_marked) delete_marked = demo_method(DupeGuruBase.delete_marked)
def make_reference(self, duplicates): def remove_selected(self):
DupeGuruBase.make_reference(self, duplicates) dupes = self.without_ref(self.selected_dupes)
self.emit(SIGNAL('resultsChanged()')) if not dupes:
return
title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes))
if self.main_window._confirm(title, msg):
DupeGuruBase.remove_selected(self)
def mark_all(self): #--- Public
self.results.mark_all() def askForRegCode(self):
self.emit(SIGNAL('dupeMarkingChanged()')) self.reg.ask_for_code()
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): def openDebugLog(self):
debugLogPath = op.join(self.appdata, 'debug.log') debugLogPath = op.join(self.appdata, 'debug.log')
url = QUrl.fromLocalFile(debugLogPath) self._open_path(debugLogPath)
QDesktopServices.openUrl(url)
def open_selected(self):
if not self.selected_dupes:
return
url = QUrl.fromLocalFile(unicode(self.selected_dupes[0].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 not self.selected_dupes:
return
url = QUrl.fromLocalFile(unicode(self.selected_dupe[0].path[:-1]))
QDesktopServices.openUrl(url)
def select_duplicate(self, dupe):
self._select_dupes([dupe])
def show_about_box(self): def show_about_box(self):
self.about_box.show() self.about_box.show()
@@ -229,11 +198,6 @@ class DupeGuru(DupeGuruBase, QObject):
self.prefs.save() self.prefs.save()
self._update_options() self._update_options()
def toggle_marking_for_dupes(self, dupes):
for dupe in dupes:
self.results.mark_toggle(dupe)
self.emit(SIGNAL('dupeMarkingChanged()'))
#--- Events #--- Events
def application_will_terminate(self): def application_will_terminate(self):
self.save() self.save()
@@ -244,10 +208,8 @@ class DupeGuru(DupeGuruBase, QObject):
self.reg.show_nag() self.reg.show_nag()
def job_finished(self, jobid): def job_finished(self, jobid):
self.emit(SIGNAL('resultsChanged()')) self._job_completed(jobid)
if jobid == JOB_LOAD: if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE) and self.last_op_error_count > 0:
self.emit(SIGNAL('directoriesChanged()'))
elif 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) msg = "{0} files could not be processed.".format(self.results.mark_count)
QMessageBox.warning(self.main_window, 'Warning', msg) QMessageBox.warning(self.main_window, 'Warning', msg)
elif jobid == JOB_SCAN: elif jobid == JOB_SCAN:

View File

@@ -23,6 +23,7 @@ class DetailsDialog(QDialog):
self.tableModel = DetailsModel(self.model) self.tableModel = DetailsModel(self.model)
# tableView is defined in subclasses # tableView is defined in subclasses
self.tableView.setModel(self.tableModel) self.tableView.setModel(self.tableModel)
self.model.connect()
def _setupUi(self): # Virtual def _setupUi(self): # Virtual
pass pass

View File

@@ -26,7 +26,6 @@ class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked) self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked)
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked) self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked)
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged) self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
self.connect(self.app, SIGNAL('directoriesChanged()'), self.directoriesChanged)
def _setupUi(self): def _setupUi(self):
self.setupUi(self) self.setupUi(self)
@@ -60,10 +59,6 @@ class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
return return
self.lastAddedFolder = dirpath self.lastAddedFolder = dirpath
self.app.add_directory(dirpath) self.app.add_directory(dirpath)
self.directoriesModel.reset()
def directoriesChanged(self):
self.directoriesModel.reset()
def doneButtonClicked(self): def doneButtonClicked(self):
self.hide() self.hide()
@@ -76,8 +71,7 @@ class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
node = index.internalPointer() node = index.internalPointer()
if node.parent is None: if node.parent is None:
row = index.row() row = index.row()
del self.app.directories[row] self.app.remove_directory(row)
self.directoriesModel.reset()
def selectionChanged(self, selected, deselected): def selectionChanged(self, selected, deselected):
self._updateRemoveButton() self._updateRemoveButton()

View File

@@ -12,7 +12,9 @@ from PyQt4.QtCore import QModelIndex, Qt, QRect, QEvent, QPoint, QUrl
from PyQt4.QtGui import (QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush, QStyle, from PyQt4.QtGui import (QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush, QStyle,
QStyleOptionComboBox, QStyleOptionViewItemV4) QStyleOptionComboBox, QStyleOptionViewItemV4)
from qtlib.tree_model import TreeNode, TreeModel from qtlib.tree_model import RefNode, TreeModel
from core.gui.directory_tree import DirectoryTree
HEADERS = ['Name', 'State'] HEADERS = ['Name', 'State']
STATES = ['Normal', 'Reference', 'Excluded'] STATES = ['Normal', 'Reference', 'Excluded']
@@ -58,36 +60,17 @@ class DirectoriesDelegate(QStyledItemDelegate):
editor.setGeometry(option.rect) 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): class DirectoriesModel(TreeModel):
def __init__(self, app): def __init__(self, app):
self.app = app
self.dirs = app.directories
TreeModel.__init__(self) TreeModel.__init__(self)
self.model = DirectoryTree(self, app)
self.model.connect()
def _createNode(self, ref, row): def _createNode(self, ref, row):
return DirectoryNode(self, None, ref, row) return RefNode(self, None, ref, row)
def _getChildren(self): def _getChildren(self):
return self.dirs return list(self.model)
def columnCount(self, parent): def columnCount(self, parent):
return 2 return 2
@@ -96,15 +79,16 @@ class DirectoriesModel(TreeModel):
if not index.isValid(): if not index.isValid():
return None return None
node = index.internalPointer() node = index.internalPointer()
ref = node.ref
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
if index.column() == 0: if index.column() == 0:
return node.name return ref.name
else: else:
return STATES[self.dirs.get_state(node.ref)] return STATES[ref.state]
elif role == Qt.EditRole and index.column() == 1: elif role == Qt.EditRole and index.column() == 1:
return self.dirs.get_state(node.ref) return ref.state
elif role == Qt.ForegroundRole: elif role == Qt.ForegroundRole:
state = self.dirs.get_state(node.ref) state = ref.state
if state == 1: if state == 1:
return QBrush(Qt.blue) return QBrush(Qt.blue)
elif state == 2: elif state == 2:
@@ -121,7 +105,7 @@ class DirectoriesModel(TreeModel):
urls = unicode(unquoted, 'utf-8').split('\r\n') urls = unicode(unquoted, 'utf-8').split('\r\n')
paths = [unicode(QUrl(url).toLocalFile()) for url in urls if url] paths = [unicode(QUrl(url).toLocalFile()) for url in urls if url]
for path in paths: for path in paths:
self.app.add_directory(path) self.model.add_directory(path)
self.reset() self.reset()
return True return True
@@ -146,7 +130,8 @@ class DirectoriesModel(TreeModel):
if not index.isValid() or role != Qt.EditRole or index.column() != 1: if not index.isValid() or role != Qt.EditRole or index.column() != 1:
return False return False
node = index.internalPointer() node = index.internalPointer()
self.dirs.set_state(node.ref, value) ref = node.ref
ref.state = value
return True return True
def supportedDropActions(self): def supportedDropActions(self):
@@ -154,3 +139,7 @@ class DirectoriesModel(TreeModel):
# work with ActionMove either. So screw that, and accept anything. # work with ActionMove either. So screw that, and accept anything.
return Qt.ActionMask return Qt.ActionMask
#--- model --> view
def refresh(self):
self.reset()

View File

@@ -8,7 +8,7 @@
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView, from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
QMessageBox, QInputDialog, QLineEdit, QItemSelectionModel, QDesktopServices) QMessageBox, QInputDialog, QLineEdit, QDesktopServices)
from hsutil.misc import nonone from hsutil.misc import nonone
@@ -16,7 +16,8 @@ from core.app import NoScannableFileError, AllFilesAreRefError
import dg_rc import dg_rc
from main_window_ui import Ui_MainWindow from main_window_ui import Ui_MainWindow
from results_model import ResultsDelegate, ResultsModel from results_model import ResultsModel
from stats_label import StatsLabel
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, app): def __init__(self, app):
@@ -24,23 +25,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app = app self.app = app
self._last_filter = None self._last_filter = None
self._setupUi() self._setupUi()
self.resultsDelegate = ResultsDelegate() self.resultsModel = ResultsModel(self.app, self.resultsView)
self.resultsModel = ResultsModel(self.app) self.stats = StatsLabel(app, self.statusLabel)
self.resultsView.setModel(self.resultsModel)
self.resultsView.setItemDelegate(self.resultsDelegate)
self._load_columns() self._load_columns()
self._update_column_actions_status() 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.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(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate) 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) self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked)
self.connect(self.resultsView, SIGNAL('spacePressed()'), self.resultsSpacePressed)
def _setupUi(self): def _setupUi(self):
self.setupUi(self) self.setupUi(self)
@@ -108,11 +102,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
h.setSectionHidden(index, not visible) h.setSectionHidden(index, not visible)
h.setResizeMode(0, QHeaderView.Stretch) 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): def _save_columns(self):
h = self.resultsView.header() h = self.resultsView.header()
widths = [] widths = []
@@ -131,9 +120,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
colid = action.column_index colid = action.column_index
action.setChecked(not h.isSectionHidden(colid)) action.setChecked(not h.isSectionHidden(colid))
def _update_status_line(self):
self.statusLabel.setText(self.app.stat_line)
#--- Actions #--- Actions
def aboutTriggered(self): def aboutTriggered(self):
self.app.show_about_box() self.app.show_about_box()
@@ -142,13 +128,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.actionsButton.showMenu() self.actionsButton.showMenu()
def addToIgnoreListTriggered(self): def addToIgnoreListTriggered(self):
dupes = self.resultsView.selectedDupes() self.app.add_selected_to_ignore_list()
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): def applyFilterTriggered(self):
title = "Apply Filter" title = "Apply Filter"
@@ -191,8 +171,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.delete_marked() self.app.delete_marked()
def deltaTriggered(self): def deltaTriggered(self):
self.resultsModel.delta = self.actionDelta.isChecked() self.resultsModel.delta_values = self.actionDelta.isChecked()
self._redraw_results()
def detailsTriggered(self): def detailsTriggered(self):
self.app.show_details() self.app.show_details()
@@ -211,7 +190,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
QDesktopServices.openUrl(url) QDesktopServices.openUrl(url)
def makeReferenceTriggered(self): def makeReferenceTriggered(self):
self.app.make_reference(self.resultsView.selectedDupes()) self.app.make_selected_reference()
def markAllTriggered(self): def markAllTriggered(self):
self.app.mark_all() self.app.mark_all()
@@ -223,8 +202,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.mark_none() self.app.mark_none()
def markSelectedTriggered(self): def markSelectedTriggered(self):
dupes = self.resultsView.selectedDupes() self.app.toggle_selected_mark_state()
self.app.toggle_marking_for_dupes(dupes)
def moveTriggered(self): def moveTriggered(self):
self.app.copy_or_move_marked(False) self.app.copy_or_move_marked(False)
@@ -251,16 +229,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
title = "Remove duplicates" title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(count) msg = "You are about to remove {0} files from results. Continue?".format(count)
if self._confirm(title, msg): if self._confirm(title, msg):
self.app.remove_marked_duplicates() self.app.remove_marked()
def removeSelectedTriggered(self): def removeSelectedTriggered(self):
dupes = self.resultsView.selectedDupes() self.app.remove_selected()
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): def renameTriggered(self):
self.resultsView.edit(self.resultsView.selectionModel().currentIndex()) self.resultsView.edit(self.resultsView.selectionModel().currentIndex())
@@ -304,27 +276,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.actionActions.menu().exec_(event.globalPos()) 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): def resultsDoubleClicked(self):
self.app.open_selected() self.app.open_selected()
def resultsReset(self): def resultsSpacePressed(self):
self.resultsView.expandAll() self.app.toggle_selected_mark_state()
if self.app.selected_dupes:
[modelIndex] = self.resultsModel.indexesForDupes(self.app.selected_dupes[:1])
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)

View File

@@ -6,74 +6,63 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license # http://www.hardcoded.net/licenses/hs_license
from PyQt4.QtCore import SIGNAL, Qt, QAbstractItemModel, QModelIndex, QRect from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QBrush, QStyledItemDelegate, QFont, QTreeView, QColor from PyQt4.QtGui import (QBrush, QStyledItemDelegate, QFont, QTreeView, QColor, QItemSelectionModel,
QItemSelection)
from qtlib.tree_model import TreeNode, TreeModel from qtlib.tree_model import TreeModel, RefNode
class ResultNode(TreeNode): from core.gui.result_tree import ResultTree as ResultTreeModel
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): class ResultsDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index): def initStyleOption(self, option, index):
QStyledItemDelegate.initStyleOption(self, option, index) QStyledItemDelegate.initStyleOption(self, option, index)
node = index.internalPointer() node = index.internalPointer()
if node.group.ref is node.dupe: ref = node.ref
if ref._group.ref is ref._dupe:
newfont = QFont(option.font) newfont = QFont(option.font)
newfont.setBold(True) newfont.setBold(True)
option.font = newfont option.font = newfont
class ResultsModel(TreeModel): class ResultsModel(TreeModel):
def __init__(self, app): def __init__(self, app, view):
TreeModel.__init__(self)
self.view = view
self._app = app self._app = app
self._results = app.results
self._data = app.data self._data = app.data
self._delta_columns = app.DELTA_COLUMNS self._delta_columns = app.DELTA_COLUMNS
self.delta = False self.resultsDelegate = ResultsDelegate()
self._power_marker = False self.model = ResultTreeModel(self, app)
TreeModel.__init__(self) self.view.setItemDelegate(self.resultsDelegate)
self.view.setModel(self)
self.model.connect()
self.connect(self.view.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
def _createNode(self, ref, row): def _createNode(self, ref, row):
if self.power_marker: return RefNode(self, None, ref, row)
# 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): def _getChildren(self):
return self._results.dupes if self.power_marker else self._results.groups return list(self.model)
def _updateSelection(self):
selectedIndexes = []
for path in self.model.selected_paths:
modelIndex = self.findIndex(path)
if modelIndex.isValid():
selectedIndexes.append(modelIndex)
if selectedIndexes:
selection = QItemSelection()
for modelIndex in selectedIndexes:
selection.select(modelIndex, modelIndex)
flags = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
self.view.selectionModel().select(selection, flags)
flags = QItemSelectionModel.Rows
self.view.selectionModel().setCurrentIndex(selectedIndexes[0], flags)
self.view.scrollTo(selectedIndexes[0])
else:
self.view.selectionModel().clear()
def columnCount(self, parent): def columnCount(self, parent):
return len(self._data.COLUMNS) return len(self._data.COLUMNS)
@@ -82,51 +71,23 @@ class ResultsModel(TreeModel):
if not index.isValid(): if not index.isValid():
return None return None
node = index.internalPointer() node = index.internalPointer()
ref = node.ref
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
data = node.deltaData if self.delta else node.normalData data = ref.data_delta if self.model.delta_values else ref.data
return data[index.column()] return data[index.column()]
elif role == Qt.CheckStateRole: elif role == Qt.CheckStateRole:
if index.column() == 0 and node.dupe is not node.group.ref: if index.column() == 0 and ref.markable:
state = Qt.Checked if self._results.is_marked(node.dupe) else Qt.Unchecked return Qt.Checked if ref.marked else Qt.Unchecked
return state
elif role == Qt.ForegroundRole: elif role == Qt.ForegroundRole:
if node.dupe is node.group.ref or node.dupe.is_ref: if ref._dupe is ref._group.ref or ref._dupe.is_ref:
return QBrush(Qt.blue) return QBrush(Qt.blue)
elif self.delta and index.column() in self._delta_columns: elif self.model.delta_values and index.column() in self._delta_columns:
return QBrush(QColor(255, 142, 40)) # orange return QBrush(QColor(255, 142, 40)) # orange
elif role == Qt.EditRole: elif role == Qt.EditRole:
if index.column() == 0: if index.column() == 0:
return node.normalData[index.column()] return ref.data[index.column()]
return None 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): def flags(self, index):
if not index.isValid(): if not index.isValid():
return Qt.ItemIsEnabled return Qt.ItemIsEnabled
@@ -144,48 +105,61 @@ class ResultsModel(TreeModel):
if not index.isValid(): if not index.isValid():
return False return False
node = index.internalPointer() node = index.internalPointer()
ref = node.ref
if role == Qt.CheckStateRole: if role == Qt.CheckStateRole:
if index.column() == 0: if index.column() == 0:
self._app.toggle_marking_for_dupes([node.dupe]) self._app.mark_dupe(ref._dupe, value.toBool())
return True return True
if role == Qt.EditRole: if role == Qt.EditRole:
if index.column() == 0: if index.column() == 0:
value = unicode(value.toString()) value = unicode(value.toString())
if self._app.rename_dupe(node.dupe, value): return self.model.rename_selected(value)
node.invalidate()
return True
return False return False
def sort(self, column, order): def sort(self, column, order):
if self.power_marker: self.model.sort(column, order == Qt.AscendingOrder)
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 #--- Properties
@property @property
def power_marker(self): def power_marker(self):
return self._power_marker return self.model.power_marker
@power_marker.setter @power_marker.setter
def power_marker(self, value): def power_marker(self, value):
if value == self._power_marker: self.model.power_marker = value
return
self._power_marker = value @property
def delta_values(self):
return self.model.delta_values
@delta_values.setter
def delta_values(self, value):
self.model.delta_values = value
#--- Events
def selectionChanged(self, selected, deselected):
indexes = self.view.selectionModel().selectedRows()
nodes = [index.internalPointer() for index in indexes]
self.model.selected_nodes = [node.ref for node in nodes]
#--- model --> view
def refresh(self):
self.reset() self.reset()
self.view.expandAll()
self._updateSelection()
def invalidate_markings(self):
# redraw view
# HACK. this is the only way I found to update the widget without reseting everything
self.view.scroll(0, 1)
self.view.scroll(0, -1)
class ResultsView(QTreeView): class ResultsView(QTreeView):
#--- Override #--- Override
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.text() == ' ': if event.text() == ' ':
self.model().toggleMarked(self.selectionModel().selectedRows()) self.emit(SIGNAL('spacePressed()'))
return return
QTreeView.keyPressEvent(self, event) QTreeView.keyPressEvent(self, event)
@@ -193,11 +167,3 @@ class ResultsView(QTreeView):
self.emit(SIGNAL('doubleClicked()')) self.emit(SIGNAL('doubleClicked()'))
# We don't call the superclass' method because the default behavior is to rename the cell. # 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())

20
qt/base/stats_label.py Normal file
View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-12
# Copyright 2010 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.gui.stats_label import StatsLabel as StatsLabelModel
class StatsLabel(object):
def __init__(self, app, view):
self.view = view
self.model = StatsLabelModel(self, app)
self.model.connect()
def refresh(self):
self.view.setText(self.model.display)

View File

@@ -16,7 +16,7 @@ from preferences_dialog import PreferencesDialog
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_me' LOGO_NAME = 'logo_me'
NAME = 'dupeGuru Music Edition' NAME = 'dupeGuru Music Edition'
VERSION = '5.7.1' VERSION = '5.7.2'
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8]) DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8])
def __init__(self): def __init__(self):

View File

@@ -1,43 +0,0 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# Copyright 2010 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_me\\dupeguru_me_help dist\\help")
# AdvancedInstaller.com has to be in your PATH
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('AdvancedInstaller.com /edit installer_tmp.aip /SetVersion %s' % version)
print_and_do('AdvancedInstaller.com /build installer_tmp.aip -force')
os.remove('installer_tmp.aip')

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# Copyright 2010 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')))

View File

@@ -56,7 +56,7 @@ class File(fs.File):
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_pe' LOGO_NAME = 'logo_pe'
NAME = 'dupeGuru Picture Edition' NAME = 'dupeGuru Picture Edition'
VERSION = '1.8.2' VERSION = '1.8.3'
DELTA_COLUMNS = frozenset([2, 5, 6]) DELTA_COLUMNS = frozenset([2, 5, 6])
def __init__(self): def __init__(self):

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-22
# Copyright 2010 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_pe\\dupeguru_pe_help dist\\help")
# AdvancedInstaller.com has to be in your PATH
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('AdvancedInstaller.com /edit installer_tmp.aip /SetVersion %s' % version)
print_and_do('AdvancedInstaller.com /build installer_tmp.aip -force')
os.remove('installer_tmp.aip')

View File

@@ -10,13 +10,6 @@
import os import os
import os.path as op 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): def move(src, dst):
if not op.exists(src): if not op.exists(src):
return return

View File

@@ -26,7 +26,7 @@ class Directories(DirectoriesBase):
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_se' LOGO_NAME = 'logo_se'
NAME = 'dupeGuru' NAME = 'dupeGuru'
VERSION = '2.9.1' VERSION = '2.9.2'
DELTA_COLUMNS = frozenset([2, 4, 5]) DELTA_COLUMNS = frozenset([2, 4, 5])
def __init__(self): def __init__(self):

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-24
# Copyright 2010 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_se\\dupeguru_help dist\\help")
# AdvancedInstaller.com has to be in your PATH
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('AdvancedInstaller.com /edit installer_tmp.aip /SetVersion %s' % version)
print_and_do('AdvancedInstaller.com /build installer_tmp.aip -force')
os.remove('installer_tmp.aip')

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env python
# Created By: Virgil Dupras
# Created On: 2009-05-24
# Copyright 2010 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')))

View File

@@ -1,30 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?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"> <DOCUMENT type="Advanced Installer" CreateVersion="4.4.1" version="7.5" modules="professional" RootPath="." Language="en">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_DESKTOP_SH" Value="1" Type="4"/> <ROW Property="AI_FINDEXE_TITLE" Value="Select the installation package for [|ProductName]" ValueLocId="AI.Property.FindExeTitle"/>
<ROW Property="AI_QUICKLAUNCH_SH" Value="1" Type="4"/>
<ROW Property="AI_SHORTCUTSREG" Value="0|0|0|"/> <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="ALLUSERS" Value="2"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/> <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="ARPCONTACT" Value="support@hardcoded.net"/>
<ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/> <ROW Property="ARPHELPLINK" Value="http://www.hardcoded.net/support/"/>
<ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru/"/> <ROW Property="ARPURLINFOABOUT" Value="http://www.hardcoded.net/dupeguru/"/>
<ROW Property="ARPURLUPDATEINFO" 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="BannerBitmap" MultiBuildValue="DefaultBuild:banner_image.jpg" Type="1"/>
<ROW Property="CTRLS" Value="2"/> <ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="default_dialog.bmp" Type="1"/> <ROW Property="DialogBitmap" MultiBuildValue="DefaultBuild:dialog_image.jpg" Type="1"/>
<ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/> <ROW Property="Manufacturer" Value="Hardcoded Software" ValueLocId="*"/>
<ROW Property="ProductCode" Value="1033:{D1E765C2-98C4-49AF-80DA-A5F803EB4FC3} "/> <ROW Property="ProductCode" Value="1033:{D1E765C2-98C4-49AF-80DA-A5F803EB4FC3} " Type="16"/>
<ROW Property="ProductLanguage" Value="1033"/> <ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="dupeGuru" ValueLocId="*"/> <ROW Property="ProductName" Value="dupeGuru" ValueLocId="*"/>
<ROW Property="ProductVersion" Value="2.7.0"/> <ROW Property="ProductVersion" Value="2.7.0"/>
<ROW Property="RUNAPPLICATION" Value="1" Type="4"/> <ROW Property="RUNAPPLICATION" Value="1" Type="4"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/> <ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND;AI_SETUPEXEPATH;SETUPEXEDIR"/>
<ROW Property="UpgradeCode" Value="{33E0D6C8-D7C6-46ED-B1A9-ECFE409EC9D5}"/> <ROW Property="UpgradeCode" Value="{33E0D6C8-D7C6-46ED-B1A9-ECFE409EC9D5}"/>
<ROW Property="WindowsFamily9X" Value="Windows 9x/ME"/> <ROW Property="WindowsFamily9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
<ROW Property="WindowsTypeNT" Value="Windows 2000"/> <ROW Property="WindowsTypeNT" MultiBuildValue="DefaultBuild:Windows 2000" ValueLocId="-"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/> <ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
@@ -33,132 +30,124 @@
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/> <ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
<ROW Directory="accessible_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="access~1|accessible"/> <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="codecs_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="codecs"/>
<ROW Directory="help_DIR" Directory_Parent="APPDIR" DefaultDir="help"/> <ROW Directory="gen_py_DIR" Directory_Parent="support_DIR" DefaultDir="gen_py"/>
<ROW Directory="iconengines_DIR" Directory_Parent="qt4_plugins_DIR" DefaultDir="iconen~1|iconengines"/> <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="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"/> <ROW Directory="qt4_plugins_DIR" Directory_Parent="APPDIR" DefaultDir="qt4_pl~1|qt4_plugins"/>
<ROW Directory="support_DIR" Directory_Parent="APPDIR" DefaultDir="support"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent"> <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="AIShRegAnswer" ComponentId="{775090B3-2E56-40F5-9DD8-24A2F82DA601}" Directory_="APPDIR" Attributes="4" KeyPath="AIShRegAnswer"/>
<ROW Component="MSVCP90.dll" ComponentId="{EA7F99CE-AA38-4676-863A-4BBD78B635F3}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCP90.dll" FullKeyPath="APPDIR\MSVCP90.dll"/> <ROW Component="AI_ExePath" ComponentId="{56EC90C1-F906-4F6E-95B2-1876CAA651C2}" Directory_="APPDIR" Attributes="4" KeyPath="AI_ExePath"/>
<ROW Component="MSVCR90.dll" ComponentId="{9C4646F4-7CB2-4BA2-9F00-16B38EDEF590}" Directory_="APPDIR" Attributes="0" KeyPath="MSVCR90.dll" FullKeyPath="APPDIR\MSVCR90.dll"/> <ROW Component="PyWinTypes26.dll" ComponentId="{B664FF8C-C60F-423C-9AC4-26144896E583}" Directory_="APPDIR" Attributes="0" KeyPath="PyWinTypes26.dll"/>
<ROW Component="POWRPROF.dll" ComponentId="{2896ECE4-56FD-452A-A1CE-5C95919136C6}" Directory_="APPDIR" Attributes="0" KeyPath="POWRPROF.dll" FullKeyPath="APPDIR\POWRPROF.dll"/> <ROW Component="QtCore4.dll" ComponentId="{F517476C-BC6D-40B6-A063-5A10680ECA05}" Directory_="APPDIR" Attributes="0" KeyPath="QtCore4.dll"/>
<ROW Component="PyWinTypes26.dll" ComponentId="{B664FF8C-C60F-423C-9AC4-26144896E583}" Directory_="APPDIR" Attributes="0" KeyPath="PyWinTypes26.dll" FullKeyPath="APPDIR\PyWinTypes26.dll"/> <ROW Component="QtGui4.dll" ComponentId="{4915BAC4-AFB0-42E1-BF2E-D4C3E58D4BEE}" Directory_="APPDIR" Attributes="0" KeyPath="QtGui4.dll"/>
<ROW Component="QtCore4.dll" ComponentId="{F517476C-BC6D-40B6-A063-5A10680ECA05}" Directory_="APPDIR" Attributes="0" KeyPath="QtCore4.dll" FullKeyPath="APPDIR\QtCore4.dll"/> <ROW Component="bz2.pyd" ComponentId="{E03E8F51-0E8D-40A2-9ED0-A8EA0ED4CD19}" Directory_="APPDIR" Attributes="0" KeyPath="bz2.pyd" Type="0"/>
<ROW Component="QtGui4.dll" ComponentId="{4915BAC4-AFB0-42E1-BF2E-D4C3E58D4BEE}" Directory_="APPDIR" Attributes="0" KeyPath="QtGui4.dll" FullKeyPath="APPDIR\QtGui4.dll"/> <ROW Component="dupeGuru.exe" ComponentId="{A8FFC84F-B54B-4883-B9FD-5C545AF0E51C}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru.exe"/>
<ROW Component="SHLWAPI.dll" ComponentId="{E533841B-68B8-459F-8C67-6D7721B9E926}" Directory_="APPDIR" Attributes="0" KeyPath="SHLWAPI.dll" FullKeyPath="APPDIR\SHLWAPI.dll"/> <ROW Component="init_.py" ComponentId="{7B86D715-9C99-4021-8A3C-437BEA4986BC}" Directory_="gen_py_DIR" Attributes="0" KeyPath="init_.py" Type="0"/>
<ROW Component="bz2.pyd" ComponentId="{E03E8F51-0E8D-40A2-9ED0-A8EA0ED4CD19}" Directory_="APPDIR" Attributes="0" KeyPath="bz2.pyd" FullKeyPath="APPDIR"/> <ROW Component="python26.dll" ComponentId="{C47E3AEB-FCE1-4A7D-90AF-26D52100756F}" Directory_="APPDIR" Attributes="0" KeyPath="python26.dll"/>
<ROW Component="credits.htm" ComponentId="{0F4F2A16-8BC1-44C0-AEAF-B62CD08992B9}" Directory_="help_DIR" Attributes="0" KeyPath="credits.htm" FullKeyPath="APPDIR\help"/> <ROW Component="pythoncom26.dll" ComponentId="{474C48BA-8C13-428C-B932-49C65A1619FC}" Directory_="APPDIR" Attributes="0" KeyPath="pythoncom26.dll"/>
<ROW Component="dupeGuru.exe" ComponentId="{A8FFC84F-B54B-4883-B9FD-5C545AF0E51C}" Directory_="APPDIR" Attributes="0" KeyPath="dupeGuru.exe" FullKeyPath="APPDIR\dupeGuru.exe"/> <ROW Component="qcncodecs4.dll" ComponentId="{1FA15E05-79B4-490E-8BE7-2915DAFECDA0}" Directory_="codecs_DIR" Attributes="0" KeyPath="qcncodecs4.dll"/>
<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="qgif4.dll" ComponentId="{12390BD7-63E5-4BAE-A760-84D6E47387F3}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qgif4.dll"/>
<ROW Component="iertutil.dll" ComponentId="{408B35EA-AD4A-449A-9A6C-DE5301667BB4}" Directory_="APPDIR" Attributes="0" KeyPath="iertutil.dll" FullKeyPath="APPDIR\iertutil.dll"/> <ROW Component="qico4.dll" ComponentId="{7EC94828-5141-4383-BB9C-89C6AE543237}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qico4.dll"/>
<ROW Component="mfc90.dll" ComponentId="{570D1F1E-F914-4E0A-AC2B-A8DAEDA57D06}" Directory_="APPDIR" Attributes="0" KeyPath="mfc90.dll" FullKeyPath="APPDIR\mfc90.dll"/> <ROW Component="qjpcodecs4.dll" ComponentId="{A607689B-97DD-4F1F-9655-7EEC2D934A75}" Directory_="codecs_DIR" Attributes="0" KeyPath="qjpcodecs4.dll"/>
<ROW Component="python26.dll" ComponentId="{C47E3AEB-FCE1-4A7D-90AF-26D52100756F}" Directory_="APPDIR" Attributes="0" KeyPath="python26.dll" FullKeyPath="APPDIR\python26.dll"/> <ROW Component="qjpeg4.dll" ComponentId="{58EB6546-1E7D-48E3-A407-08B945D68317}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qjpeg4.dll"/>
<ROW Component="pythoncom26.dll" ComponentId="{474C48BA-8C13-428C-B932-49C65A1619FC}" Directory_="APPDIR" Attributes="0" KeyPath="pythoncom26.dll" FullKeyPath="APPDIR\pythoncom26.dll"/> <ROW Component="qkrcodecs4.dll" ComponentId="{3D322ADA-9D38-4B5F-8335-44BDE935D5D7}" Directory_="codecs_DIR" Attributes="0" KeyPath="qkrcodecs4.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="qmng4.dll" ComponentId="{11B243B6-A6E5-4282-A58B-5A4F5A2CB253}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qmng4.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="qsvg4.dll" ComponentId="{D689DDEB-D4E9-4DE0-B32B-85FD25C40726}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qsvg4.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="qsvgicon4.dll" ComponentId="{6EF94FDD-3E92-4886-925F-B264C962E7EF}" Directory_="iconengines_DIR" Attributes="0" KeyPath="qsvgicon4.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="qtaccessiblecompatwidgets4.dll" ComponentId="{D0CC18F8-1B1D-40E6-BFB3-14F2DE96B6ED}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblecompatwidgets4.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="qtaccessiblewidgets4.dll" ComponentId="{0DB9EE4C-922F-42AC-80CC-4EA3CBBB1629}" Directory_="accessible_DIR" Attributes="0" KeyPath="qtaccessiblewidgets4.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="qtiff4.dll" ComponentId="{660F482B-9508-4A26-BC1A-610E84829CA4}" Directory_="imageformats_DIR" Attributes="0" KeyPath="qtiff4.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="qtwcodecs4.dll" ComponentId="{68610953-B652-4340-BBB9-B1EEB3A6AF7A}" Directory_="codecs_DIR" Attributes="0" KeyPath="qtwcodecs4.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="updater.exe" ComponentId="{CB63C33D-EB1B-420A-8BAA-CD380923F12B}" Directory_="APPDIR" Attributes="0" KeyPath="updater.exe"/>
<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>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent"> <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"/> <ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0" Components="updater.exe dupeGuru.exe AIShRegAnswer python26.dll pythoncom26.dll PyWinTypes26.dll QtCore4.dll QtGui4.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 AI_ExePath qtaccessiblecompatwidgets4.dll init_.py"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/> <ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent"> <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="PyQt4.QtCore.pyd" Component_="bz2.pyd" FileName="PyQt4Q~1.pyd|PyQt4.QtCore.pyd" Attributes="0" SourcePath="dist\PyQt4.QtCore.pyd" SelfReg="false" Sequence="10"/>
<ROW File="MSVCR90.dll" Component_="MSVCR90.dll" FileName="MSVCR90.dll" Attributes="0" SourcePath="dist\MSVCR90.dll" SelfReg="false" Sequence="6"/> <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="11"/>
<ROW File="POWRPROF.dll" Component_="POWRPROF.dll" FileName="POWRPROF.dll" Attributes="0" SourcePath="dist\POWRPROF.dll" SelfReg="false" Sequence="7"/> <ROW File="PyWinTypes26.dll" Component_="PyWinTypes26.dll" FileName="PyWinT~1.dll|PyWinTypes26.dll" Attributes="0" SourcePath="dist\PyWinTypes26.dll" SelfReg="false" Sequence="5"/>
<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="QtCore4.dll" Component_="QtCore4.dll" FileName="QtCore4.dll" Attributes="0" SourcePath="dist\QtCore4.dll" SelfReg="false" Sequence="6"/>
<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="QtGui4.dll" Component_="QtGui4.dll" FileName="QtGui4.dll" Attributes="0" SourcePath="dist\QtGui4.dll" SelfReg="false" Sequence="7"/>
<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="bz2.pyd" Component_="bz2.pyd" FileName="bz2.pyd" Attributes="0" SourcePath="dist\bz2.pyd" SelfReg="false" Sequence="8"/>
<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="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="hashlib.pyd" Component_="bz2.pyd" FileName="_hashlib.pyd" Attributes="0" SourcePath="dist\_hashlib.pyd" SelfReg="false" Sequence="31"/>
<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="init_.py" Component_="init_.py" FileName="__init__.py" Attributes="0" SourcePath="dist\support\gen_py\__init__.py" SelfReg="false" Sequence="36"/>
<ROW File="hashlib.pyd" Component_="bz2.pyd" FileName="_hashlib.pyd" Attributes="0" SourcePath="dist\_hashlib.pyd" SelfReg="false" Sequence="39"/> <ROW File="pyexpat.pyd" Component_="bz2.pyd" FileName="pyexpat.pyd" Attributes="0" SourcePath="dist\pyexpat.pyd" SelfReg="false" Sequence="9"/>
<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="python26.dll" Component_="python26.dll" FileName="python26.dll" Attributes="0" SourcePath="dist\python26.dll" SelfReg="false" Sequence="3"/>
<ROW File="iertutil.dll" Component_="iertutil.dll" FileName="iertutil.dll" Attributes="0" SourcePath="dist\iertutil.dll" SelfReg="false" Sequence="3"/> <ROW File="pythoncom26.dll" Component_="pythoncom26.dll" FileName="python~1.dll|pythoncom26.dll" Attributes="0" SourcePath="dist\pythoncom26.dll" SelfReg="false" Sequence="4"/>
<ROW File="intro.htm" Component_="credits.htm" FileName="intro.htm" Attributes="0" SourcePath="dist\help\intro.htm" SelfReg="false" Sequence="49"/> <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="13"/>
<ROW File="mfc90.dll" Component_="mfc90.dll" FileName="mfc90.dll" Attributes="0" SourcePath="dist\mfc90.dll" SelfReg="false" Sequence="4"/> <ROW File="qgif4.dll" Component_="qgif4.dll" FileName="qgif4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qgif4.dll" SelfReg="false" Sequence="18"/>
<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="qico4.dll" Component_="qico4.dll" FileName="qico4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qico4.dll" SelfReg="false" Sequence="19"/>
<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="qjpcodecs4.dll" Component_="qjpcodecs4.dll" FileName="qjpcod~1.dll|qjpcodecs4.dll" Attributes="0" SourcePath="dist\qt4_plugins\codecs\qjpcodecs4.dll" SelfReg="false" Sequence="14"/>
<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="qjpeg4.dll" Component_="qjpeg4.dll" FileName="qjpeg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qjpeg4.dll" SelfReg="false" Sequence="20"/>
<ROW File="pyexpat.pyd" Component_="bz2.pyd" FileName="pyexpat.pyd" Attributes="0" SourcePath="dist\pyexpat.pyd" SelfReg="false" Sequence="16"/> <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="15"/>
<ROW File="python26.dll" Component_="python26.dll" FileName="python26.dll" Attributes="0" SourcePath="dist\python26.dll" SelfReg="false" Sequence="8"/> <ROW File="qmng4.dll" Component_="qmng4.dll" FileName="qmng4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qmng4.dll" SelfReg="false" Sequence="21"/>
<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="qsvg4.dll" Component_="qsvg4.dll" FileName="qsvg4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qsvg4.dll" SelfReg="false" Sequence="22"/>
<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="qsvgicon4.dll" Component_="qsvgicon4.dll" FileName="qsvgic~1.dll|qsvgicon4.dll" Attributes="0" SourcePath="dist\qt4_plugins\iconengines\qsvgicon4.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="25"/> <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="35"/>
<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="qtaccessiblewidgets4.dll" Component_="qtaccessiblewidgets4.dll" FileName="qtacce~2.dll|qtaccessiblewidgets4.dll" Attributes="0" SourcePath="dist\qt4_plugins\accessible\qtaccessiblewidgets4.dll" SelfReg="false" Sequence="12"/>
<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="qtiff4.dll" Component_="qtiff4.dll" FileName="qtiff4.dll" Attributes="0" SourcePath="dist\qt4_plugins\imageformats\qtiff4.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="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="16"/>
<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="select.pyd" Component_="bz2.pyd" FileName="select.pyd" Attributes="0" SourcePath="dist\select.pyd" 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="28"/> <ROW File="sip.pyd" Component_="bz2.pyd" FileName="sip.pyd" Attributes="0" SourcePath="dist\sip.pyd" 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="29"/> <ROW File="socket.pyd" Component_="bz2.pyd" FileName="_socket.pyd" Attributes="0" SourcePath="dist\_socket.pyd" SelfReg="false" Sequence="32"/>
<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="ssl.pyd" Component_="bz2.pyd" FileName="_ssl.pyd" Attributes="0" SourcePath="dist\_ssl.pyd" SelfReg="false" Sequence="33"/>
<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="unicodedata.pyd" Component_="bz2.pyd" FileName="unicod~1.pyd|unicodedata.pyd" Attributes="0" SourcePath="dist\unicodedata.pyd" SelfReg="false" Sequence="26"/>
<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="updater.exe" Component_="updater.exe" FileName="updater.exe" Attributes="0" SourcePath="&lt;AI_HOME&gt;updater.exe" SelfReg="false" Sequence="1"/>
<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="win32api.pyd" Component_="bz2.pyd" FileName="win32api.pyd" Attributes="0" SourcePath="dist\win32api.pyd" SelfReg="false" Sequence="27"/>
<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="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="28"/>
<ROW File="results.htm" Component_="credits.htm" FileName="results.htm" Attributes="0" SourcePath="dist\help\results.htm" SelfReg="false" Sequence="53"/> <ROW File="win32sysloader.pyd" Component_="bz2.pyd" FileName="_win32~1.pyd|_win32sysloader.pyd" Attributes="0" SourcePath="dist\_win32sysloader.pyd" SelfReg="false" Sequence="34"/>
<ROW File="select.pyd" Component_="bz2.pyd" FileName="select.pyd" Attributes="0" SourcePath="dist\select.pyd" SelfReg="false" Sequence="31"/> <ROW File="win32trace.pyd" Component_="bz2.pyd" FileName="win32t~1.pyd|win32trace.pyd" Attributes="0" SourcePath="dist\win32trace.pyd" SelfReg="false" Sequence="29"/>
<ROW File="sip.pyd" Component_="bz2.pyd" FileName="sip.pyd" Attributes="0" SourcePath="dist\sip.pyd" SelfReg="false" Sequence="32"/> <ROW File="win32ui.pyd" Component_="bz2.pyd" FileName="win32ui.pyd" Attributes="0" SourcePath="dist\win32ui.pyd" SelfReg="false" Sequence="30"/>
<ROW File="socket.pyd" Component_="bz2.pyd" FileName="_socket.pyd" Attributes="0" SourcePath="dist\_socket.pyd" SelfReg="false" Sequence="41"/> </COMPONENT>
<ROW File="ssl.pyd" Component_="bz2.pyd" FileName="_ssl.pyd" Attributes="0" SourcePath="dist\_ssl.pyd" SelfReg="false" Sequence="42"/> <COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
<ROW File="unicodedata.pyd" Component_="bz2.pyd" FileName="unicod~1.pyd|unicodedata.pyd" Attributes="0" SourcePath="dist\unicodedata.pyd" SelfReg="false" Sequence="33"/> <ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" PackageName="install\dupeguru_win_[|ProductVersion]" Languages="en" InstallationType="4" CabsLocation="1" PackageType="1" FilesInsideExe="true" CreateMd5="true" ExtractionFolder="[AppDataFolder][|Manufacturer]\[|ProductName]\install" ExtUI="true" ExeName="dupeguru_win_[|ProductVersion]"/>
<ROW File="updater.exe" Component_="updater.exe" FileName="updater.exe" Attributes="0" SourcePath="&lt;updater.exe&gt;" SelfReg="false" Sequence="1"/> <ATTRIBUTE name="CurrentBuild" value="DefaultBuild"/>
<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>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent"> <COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;ui.ail&gt;"/> <ROW Path="&lt;AI_DICTS&gt;ui.ail"/>
<ROW Path="&lt;ui_en.ail&gt;"/> <ROW Path="&lt;AI_DICTS&gt;ui_en.ail"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent"> <COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="FolderDlg.aip" Path="&lt;FolderDlg.aip&gt;"/> <ROW Fragment="CommonUI.aip" Path="&lt;AI_FRAGS&gt;CommonUI.aip"/>
<ROW Fragment="ShortcutsDlg.aip" Path="&lt;ShortcutsDlg.aip&gt;"/> <ROW Fragment="FolderDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\FolderDlg.aip"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;StaticUIStrings.aip&gt;"/> <ROW Fragment="SequenceDialogs.aip" Path="&lt;AI_THEMES&gt;classic\fragments\SequenceDialogs.aip"/>
<ROW Fragment="UI.aip" Path="&lt;UI.aip&gt;"/> <ROW Fragment="Sequences.aip" Path="&lt;AI_FRAGS&gt;Sequences.aip"/>
<ROW Fragment="ShortcutsDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\ShortcutsDlg.aip"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;AI_FRAGS&gt;StaticUIStrings.aip"/>
<ROW Fragment="UI.aip" Path="&lt;AI_THEMES&gt;classic\fragments\UI.aip"/>
<ROW Fragment="Validation.aip" Path="&lt;AI_FRAGS&gt;Validation.aip"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiActionTextComponent">
<ROW Action="AI_DeleteLzma" Description="Deleting files extracted from archive" DescriptionLocId="ActionText.Description.AI_DeleteLzma" TemplateLocId="-"/>
<ROW Action="AI_DeleteRLzma" Description="Deleting files extracted from archive" DescriptionLocId="ActionText.Description.AI_DeleteLzma" TemplateLocId="-"/>
<ROW Action="AI_ExtractLzma" Description="Extracting files from archive" DescriptionLocId="ActionText.Description.AI_ExtractLzma" TemplateLocId="-"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
<ROW Property="AI_SETUPEXEPATH" Signature_="AI_EXE_PATH_CU" Builds="DefaultBuild"/>
<ROW Property="AI_SETUPEXEPATH" Signature_="AI_EXE_PATH_LM" Builds="DefaultBuild"/>
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/> <ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionMachine"/>
<ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/> <ROW Property="AI_SHORTCUTSREG" Signature_="AI_ShRegOptionUser"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="aicustact.dll" SourcePath="&lt;aicustact.dll&gt;"/> <ROW Name="Prereq.dll" SourcePath="&lt;AI_CUSTACTS&gt;Prereq.dll"/>
<ROW Name="default_banner.bmp" SourcePath="&lt;default-banner.bmp&gt;"/> <ROW Name="aicustact.dll" SourcePath="&lt;AI_CUSTACTS&gt;aicustact.dll"/>
<ROW Name="default_dialog.bmp" SourcePath="&lt;default-dialog.bmp&gt;"/> <ROW Name="banner_image.jpg" SourcePath="&lt;AI_THEMES&gt;classic\resources\banner-image.jpg"/>
<ROW Name="dialog_image.jpg" SourcePath="&lt;AI_THEMES&gt;classic\resources\dialog-image.jpg"/>
<ROW Name="lzmaextractor.dll" SourcePath="&lt;AI_CUSTACTS&gt;lzmaextractor.dll"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiControlComponent">
<ATTRIBUTE name="FixedSizeBitmaps" value="0"/> <ATTRIBUTE name="FixedSizeBitmaps" value="0"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiControlConditionComponent">
<ROW Dialog_="ShortcutsDlg" Control_="StartmenuShortcutsCheckBox" Action="Show" Condition="(Not Installed)"/> <ROW Dialog_="ShortcutsDlg" Control_="StartmenuShortcutsCheckBox" Action="Show" Condition="(Not Installed)"/>
<ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/> <ROW Dialog_="ShortcutsDlg" Control_="QuickLaunchShorcutsCheckBox" Action="Hide" Condition="(Not Installed) AND (VersionNT&lt;&quot;601&quot;)"/>
<ROW Dialog_="ShortcutsDlg" Control_="StartupShorcutsCheckBox" Action="Hide" Condition="(Not Installed)"/> <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)"/> <ATTRIBUTE name="DeletedRows" value="ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed) AND (VersionNT&lt;&quot;601&quot;)@ShortcutsDlg#StartupShorcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#StartmenuShortcutsCheckBox#Show#(Not Installed)@ShortcutsDlg#QuickLaunchShorcutsCheckBox#Show#(Not Installed)"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/> <ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="ShortcutsDlg" Condition="AI_INSTALL" Ordering="1"/>
@@ -172,15 +161,21 @@
<ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/> <ROW Dialog_="ShortcutsDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/> <ROW Dialog_="ShortcutsDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="1"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="qt4_plugins_DIR" Component_="qt4_plugins"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_AppSearchEx" Type="1" Source="Prereq.dll" Target="DoAppSearchEx"/>
<ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/> <ROW Action="AI_DELETE_SHORTCUTS" Type="1" Source="aicustact.dll" Target="DeleteShortcuts"/>
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/> <ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
<ROW Action="AI_DeleteCadLzma" Type="51" Source="AI_DeleteLzma" Target="[AI_SETUPEXEPATH]"/>
<ROW Action="AI_DeleteLzma" Type="1025" Source="lzmaextractor.dll" Target="DeleteLZMAFiles"/>
<ROW Action="AI_DeleteRCadLzma" Type="51" Source="AI_DeleteRLzma" Target="[AI_SETUPEXEPATH]"/>
<ROW Action="AI_DeleteRLzma" Type="1281" Source="lzmaextractor.dll" Target="DeleteLZMAFiles"/>
<ROW Action="AI_ExtractCadLzma" Type="51" Source="AI_ExtractLzma" Target="[AI_SETUPEXEPATH]"/>
<ROW Action="AI_ExtractLzma" Type="1025" Source="lzmaextractor.dll" Target="ExtractLZMAFiles"/>
<ROW Action="AI_FindExeLzma" Type="1" Source="lzmaextractor.dll" Target="FindEXE"/>
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="[#dupeGuru.exe]"/> <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_PREPARE_UPGRADE" Type="65" Source="aicustact.dll" Target="PrepareUpgrade"/>
<ROW Action="AI_RESTORE_LOCATION" Type="1" Source="aicustact.dll" Target="RestoreLocation"/> <ROW Action="AI_RESTORE_LOCATION" Type="65" Source="aicustact.dll" Target="RestoreLocation"/>
<ROW Action="AI_ResolveKnownFolders" Type="1" Source="aicustact.dll" Target="AI_ResolveKnownFolders"/>
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/> <ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
<ROW Action="AI_UPDATER_UNINSTALL" Type="18" Source="updater.exe" Target="/clean silent"/> <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_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]"/>
@@ -188,7 +183,7 @@
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/> <ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="SystemFolder_msiexec.exe" SourcePath="&lt;uninstall.ico&gt;" Index="0"/> <ROW Name="SystemFolder_msiexec.exe" SourcePath="&lt;AI_RES&gt;uninstall.ico" Index="0"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIniFileComponent"> <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="AppDir" FileName="updater.ini" DirProperty="APPDIR" Section="General" Key="AppDir" Value="[APPDIR]" Action="0" Component_="updater.exe"/>
@@ -204,51 +199,59 @@
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/> <ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_STORE_LOCATION" Condition="Not Installed" Sequence="1545"/> <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_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_UPDATER_UNINSTALL" Condition="($updater.exe = 2) AND (?updater.exe = 3) AND NOT (UPGRADINGPRODUCTCODE)" Sequence="1547"/>
<ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE=&quot;ALL&quot;)" Sequence="1449"/> <ROW Action="AI_DELETE_SHORTCUTS" Condition="NOT (REMOVE=&quot;ALL&quot;)" Sequence="1449"/>
<ROW Action="AI_AppSearchEx" Sequence="101"/>
<ROW Action="AI_DeleteCadLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="199" Builds="DefaultBuild"/>
<ROW Action="AI_DeleteRCadLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="198" Builds="DefaultBuild"/>
<ROW Action="AI_ExtractCadLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="197" Builds="DefaultBuild"/>
<ROW Action="AI_FindExeLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="196" Builds="DefaultBuild"/>
<ROW Action="AI_ExtractLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="1549" Builds="DefaultBuild"/>
<ROW Action="AI_DeleteRLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="1548" Builds="DefaultBuild"/>
<ROW Action="AI_DeleteLzma" Condition="SETUPEXEDIR=&quot;&quot; AND Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (NOT PATCH)" Sequence="6599" Builds="DefaultBuild"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/> <ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="740"/>
<ROW Action="AI_AppSearchEx" Sequence="101"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="51"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent"> <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="Version9X OR VersionNT64 OR (VersionNT &gt;= 500 )" Description="[ProductName] cannot be installed on systems earlier than [WindowsTypeNT]" DescriptionLocId="AI.LaunchCondition.NoSpecificNT" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] can not be installed on [WindowsFamily9X]"/> <ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsFamily9X]" DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
</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>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
<ROW Signature_="AI_EXE_PATH_CU" Root="1" Key="Software\Caphyon\Advanced Installer\LZMA\[ProductCode]\[ProductVersion]" Name="AI_ExePath" Type="2"/>
<ROW Signature_="AI_EXE_PATH_LM" Root="2" Key="Software\Caphyon\Advanced Installer\LZMA\[ProductCode]\[ProductVersion]" Name="AI_ExePath" Type="2"/>
<ROW Signature_="AI_ShRegOptionMachine" Root="2" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/> <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"/> <ROW Signature_="AI_ShRegOptionUser" Root="1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Type="2"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/> <ROW Registry="AIShRegAnswer" Root="-1" Key="Software\Caphyon\Advanced Installer\Installs\[ProductCode]" Name="AIShRegAnswer" Value="[AI_SHORTCUTSREG]" Component_="AIShRegAnswer"/>
<ROW Registry="AI_ExePath" Root="-1" Key="Software\Caphyon\Advanced Installer\LZMA\[ProductCode]\[ProductVersion]" Name="AI_ExePath" Value="[AI_SETUPEXEPATH]" Component_="AI_ExePath"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent"> <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="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="Uninstall_dupeGuru" Directory_="SHORTCUTDIR" Name="Uninst~1|Uninstall dupeGuru" Component_="PyWinTypes26.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" 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"/> <ROW Shortcut="dupeGuru_1" Directory_="SHORTCUTDIR" Name="dupeGuru" Component_="dupeGuru.exe" Target="[#dupeGuru.exe]" Hotkey="0" IconIndex="0" ShowCmd="1" WkDir="APPDIR"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiThemeComponent">
<ATTRIBUTE name="UsedTheme" value="classic"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent"> <COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
<ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/> <ROW UpgradeCode="[|UpgradeCode]" VersionMax="[|ProductVersion]" Attributes="1025" ActionProperty="OLDPRODUCTS"/>
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/> <ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqComponent"> <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"/> <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" MD5="5689d43c3b201dd3810fa3bba4a6476a"/>
<ATTRIBUTE name="ExtractionFolder" value="[AppDataFolder][|Manufacturer]\[|ProductName]\install"/> <ATTRIBUTE name="PrereqsOrder" value="0"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.PreReqSearchComponent"> <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"/> <ROW SearchKey="SP" Prereq="0" SearchType="9" SearchString="HKLM\SOFTWARE\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033\SP" RefContent="M1" Order="1" Property="PreReqSearch"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.SynchronizedFolderComponent"> <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"/> <ROW Directory_="APPDIR" SourcePath="dist" Feature="MainFeature" ExcludePattern="*~|#*#|%*%|._|CVS|.cvsignore|SCCS|vssver.scc|mssccprj.scc|vssver2.scc|.svn|.DS_Store|*.vshost.*" ExcludeFlags="6"/>
</COMPONENT> </COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.UpdaterComponent"> <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"/> <ROW Updater="updater.exe" URL="URL" SearchFreq="CheckFrequency" DownloadsFolder="DownloadsFolder" ID="ID" TargetDir="AppDir" AppName="ApplicationName" CompanyName="CompanyName" UnistallCASeq="AI_UPDATER_UNINSTALL"/>