Initial commit.

--HG--
extra : convert_revision : svn%3Ac306627e-7827-47d3-bdf0-9a457c9553a1/trunk%402
This commit is contained in:
hsoft 2009-06-01 09:55:11 +00:00
parent 4f197ffd5a
commit e9a97afdf8
354 changed files with 38083 additions and 0 deletions

17
base/cocoa/AppDelegate.h Normal file
View File

@ -0,0 +1,17 @@
#import <Cocoa/Cocoa.h>
#import "RecentDirectories.h"
#import "PyDupeGuru.h"
@interface AppDelegateBase : NSObject
{
IBOutlet PyDupeGuruBase *py;
IBOutlet RecentDirectories *recentDirectories;
IBOutlet NSMenuItem *unlockMenuItem;
NSString *_appName;
}
- (IBAction)unlockApp:(id)sender;
- (PyDupeGuruBase *)py;
- (RecentDirectories *)recentDirectories;
@end

30
base/cocoa/AppDelegate.m Normal file
View File

@ -0,0 +1,30 @@
#import "AppDelegate.h"
#import "ProgressController.h"
#import "RegistrationInterface.h"
#import "Utils.h"
#import "Consts.h"
@implementation AppDelegateBase
- (id)init
{
self = [super init];
_appName = @"";
return self;
}
- (IBAction)unlockApp:(id)sender
{
if ([[self py] isRegistered])
return;
RegistrationInterface *ri = [[RegistrationInterface alloc] initWithApp:[self py] name:_appName limitDescription:LIMIT_DESC];
if ([ri enterCode] == NSOKButton)
{
NSString *menuTitle = [NSString stringWithFormat:@"Thanks for buying %@",_appName];
[unlockMenuItem setTitle:menuTitle];
}
[ri release];
}
- (PyDupeGuruBase *)py { return py; }
- (RecentDirectories *)recentDirectories { return recentDirectories; }
@end

17
base/cocoa/Consts.h Normal file
View File

@ -0,0 +1,17 @@
#import <Cocoa/Cocoa.h>
#define DuplicateSelectionChangedNotification @"DuplicateSelectionChangedNotification"
#define ResultsChangedNotification @"ResultsChangedNotification"
#define ResultsMarkingChangedNotification @"ResultsMarkingChangedNotification"
#define RegistrationRequired @"RegistrationRequired"
#define JobStarted @"JobStarted"
#define JobInProgress @"JobInProgress"
#define jobLoad @"job_load"
#define jobScan @"job_scan"
#define jobCopy @"job_copy"
#define jobMove @"job_move"
#define jobDelete @"job_delete"
#define DEMO_MAX_ACTION_COUNT 10
#define LIMIT_DESC @"In the demo version, only 10 duplicates per session can be sent to Trash, moved or copied."

View File

@ -0,0 +1,25 @@
#import <Cocoa/Cocoa.h>
#import "RecentDirectories.h"
#import "Outline.h"
#import "PyDupeGuru.h"
@interface DirectoryPanelBase : NSWindowController
{
IBOutlet NSPopUpButton *addButtonPopUp;
IBOutlet OutlineView *directories;
IBOutlet NSButton *removeButton;
PyDupeGuruBase *_py;
RecentDirectories *_recentDirectories;
}
- (id)initWithParentApp:(id)aParentApp;
- (IBAction)askForDirectory:(id)sender;
- (IBAction)changeDirectoryState:(id)sender;
- (IBAction)popupAddDirectoryMenu:(id)sender;
- (IBAction)removeSelectedDirectory:(id)sender;
- (IBAction)toggleVisible:(id)sender;
- (void)addDirectory:(NSString *)directory;
- (void)refreshRemoveButtonText;
@end

178
base/cocoa/DirectoryPanel.m Normal file
View File

@ -0,0 +1,178 @@
#import "DirectoryPanel.h"
#import "Dialogs.h"
#import "Utils.h"
#import "AppDelegate.h"
@implementation DirectoryPanelBase
- (id)initWithParentApp:(id)aParentApp
{
self = [super initWithWindowNibName:@"Directories"];
[self window];
AppDelegateBase *app = aParentApp;
_py = [app py];
_recentDirectories = [app recentDirectories];
[directories setPy:_py];
NSPopUpButtonCell *cell = [[directories tableColumnWithIdentifier:@"1"] dataCell];
[cell addItemWithTitle:@"Normal"];
[cell addItemWithTitle:@"Reference"];
[cell addItemWithTitle:@"Excluded"];
for (int 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];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:) name:NSOutlineViewSelectionDidChangeNotification object:directories];
return self;
}
/* Actions */
- (IBAction)askForDirectory:(id)sender
{
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setCanChooseFiles:YES];
[op setCanChooseDirectories:YES];
[op setAllowsMultipleSelection:NO];
[op setTitle:@"Select a directory to add to the scanning list"];
[op setDelegate:self];
if ([op runModalForTypes:nil] == NSOKButton)
{
NSString *directory = [[op filenames] objectAtIndex:0];
[self addDirectory:directory];
}
}
- (IBAction)changeDirectoryState:(id)sender
{
OVNode *node = [directories itemAtRow:[directories clickedRow]];
[_py setDirectory:p2a([node indexPath]) state:i2n([sender tag])];
[node resetAllBuffers];
[directories display];
}
- (IBAction)popupAddDirectoryMenu:(id)sender
{
if ([[_recentDirectories directories] count] == 0)
{
[self askForDirectory:sender];
return;
}
NSMenu *m = [addButtonPopUp menu];
while ([m numberOfItems] > 0)
[m removeItemAtIndex:0];
NSMenuItem *mi = [m addItemWithTitle:@"Add New Directory..." action:@selector(askForDirectory:) keyEquivalent:@""];
[mi setTarget:self];
[m addItem:[NSMenuItem separatorItem]];
[_recentDirectories fillMenu:m];
[addButtonPopUp selectItem:nil];
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
}
- (IBAction)removeSelectedDirectory:(id)sender
{
[[self window] makeKeyAndOrderFront:nil];
if ([directories selectedRow] < 0)
return;
OVNode *node = [directories itemAtRow:[directories selectedRow]];
if ([node level] == 1)
{
[_py removeDirectory:i2n([node index])];
[directories reloadData];
}
else
{
int state = n2i([[node buffer] objectAtIndex:1]);
int newState = state == 2 ? 0 : 2; // If excluded, put it back
[_py setDirectory:p2a([node indexPath]) state:i2n(newState)];
[node resetAllBuffers];
[directories display];
}
[self refreshRemoveButtonText];
}
- (IBAction)toggleVisible:(id)sender
{
if ([[self window] isVisible])
[[self window] close];
else
[[self window] makeKeyAndOrderFront:nil];
}
/* Public */
- (void)addDirectory:(NSString *)directory
{
int r = [[_py addDirectory:directory] intValue];
if (r)
{
NSString *m;
switch (r)
{
case 1:
{
m = @"This directory already is in the list.";
break;
}
case 2:
{
m = @"This directory does not exist.";
break;
}
}
[Dialogs showMessage:m];
}
[directories reloadData];
[_recentDirectories addDirectory:directory];
[[self window] makeKeyAndOrderFront:nil];
}
- (void)refreshRemoveButtonText
{
if ([directories selectedRow] < 0)
{
[removeButton setEnabled:NO];
return;
}
[removeButton setEnabled:YES];
OVNode *node = [directories itemAtRow:[directories selectedRow]];
int state = n2i([[node buffer] objectAtIndex:1]);
NSString *buttonText = state == 2 ? @"Put Back" : @"Remove";
[removeButton setTitle:buttonText];
}
/* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
OVNode *node = item;
int 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 isdir;
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isdir];
return isdir;
}
/* Notifications */
- (void)directorySelectionChanged:(NSNotification *)aNotification
{
[self refreshRemoveButtonText];
}
@end

Binary file not shown.

26
base/cocoa/Info.plist Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.yourcocoaframework</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

56
base/cocoa/PyDupeGuru.h Normal file
View File

@ -0,0 +1,56 @@
#import <Cocoa/Cocoa.h>
#import "PyApp.h"
@interface PyDupeGuruBase : PyApp
//Actions
- (NSNumber *)addDirectory:(NSString *)name;
- (void)removeDirectory:(NSNumber *)index;
- (void)setDirectory:(NSArray *)indexPath state:(NSNumber *)state;
- (void)loadResults;
- (void)saveResults;
- (void)loadIgnoreList;
- (void)saveIgnoreList;
- (void)clearIgnoreList;
- (void)purgeIgnoreList;
- (NSString *)exportToXHTMLwithColumns:(NSArray *)aColIds xslt:(NSString *)xsltPath css:(NSString *)cssPath;
- (NSNumber *)doScan;
- (void)selectPowerMarkerNodePaths:(NSArray *)aIndexPaths;
- (void)selectResultNodePaths:(NSArray *)aIndexPaths;
- (void)toggleSelectedMark;
- (void)markAll;
- (void)markInvert;
- (void)markNone;
- (void)addSelectedToIgnoreList;
- (void)refreshDetailsWithSelected;
- (void)removeSelected;
- (void)openSelected;
- (NSNumber *)renameSelected:(NSString *)aNewName;
- (void)revealSelected;
- (void)makeSelectedReference;
- (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)deleteMarked;
- (void)removeMarked;
//Data
- (NSNumber *)getIgnoreListCount;
- (NSNumber *)getMarkCount;
- (NSString *)getStatLine;
- (NSNumber *)getOperationalErrorCount;
//Scanning options
- (void)setMinMatchPercentage:(NSNumber *)percentage;
- (void)setMixFileKind:(NSNumber *)mix_file_kind;
- (void)setDisplayDeltaValues:(NSNumber *)display_delta_values;
- (void)setEscapeFilterRegexp:(NSNumber *)escape_filter_regexp;
- (void)setRemoveEmptyFolders:(NSNumber *)remove_empty_folders;
- (void)setSizeThreshold:(int)size_threshold;
@end

34
base/cocoa/ResultWindow.h Normal file
View File

@ -0,0 +1,34 @@
#import <Cocoa/Cocoa.h>
#import "Outline.h"
#import "DirectoryPanel.h"
#import "PyDupeGuru.h"
@interface MatchesView : OutlineView
- (void)keyDown:(NSEvent *)theEvent;
@end
@interface ResultWindowBase : NSWindowController
{
@protected
IBOutlet PyDupeGuruBase *py;
IBOutlet id app;
IBOutlet NSView *actionMenuView;
IBOutlet NSSegmentedControl *deltaSwitch;
IBOutlet NSView *deltaSwitchView;
IBOutlet NSView *filterFieldView;
IBOutlet MatchesView *matches;
IBOutlet NSView *pmSwitchView;
BOOL _powerMode;
BOOL _displayDelta;
}
- (NSString *)logoImageName;
/* Actions */
- (IBAction)changeDelta:(id)sender;
- (IBAction)copyMarked:(id)sender;
- (IBAction)deleteMarked:(id)sender;
- (IBAction)expandAll:(id)sender;
- (IBAction)moveMarked:(id)sender;
/* Notifications */
- (void)jobCompleted:(NSNotification *)aNotification;
@end

316
base/cocoa/ResultWindow.m Normal file
View File

@ -0,0 +1,316 @@
#import "ResultWindow.h"
#import "Dialogs.h"
#import "ProgressController.h"
#import "Utils.h"
#import "RegistrationInterface.h"
#import "AppDelegate.h"
#import "Consts.h"
#define tbbDirectories @"tbbDirectories"
#define tbbDetails @"tbbDetail"
#define tbbPreferences @"tbbPreferences"
#define tbbPowerMarker @"tbbPowerMarker"
#define tbbScan @"tbbScan"
#define tbbAction @"tbbAction"
#define tbbDelta @"tbbDelta"
#define tbbFilter @"tbbFilter"
@implementation MatchesView
- (void)keyDown:(NSEvent *)theEvent
{
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
// get flags and strip the lower 16 (device dependant) bits
unsigned int 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
- (void)awakeFromNib
{
[self window];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
}
/* Virtual */
- (NSString *)logoImageName
{
return @"dg_logo32";
}
/* Actions */
- (IBAction)changeDelta:(id)sender
{
_displayDelta = [deltaSwitch selectedSegment] == 1;
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches reloadData];
[self expandAll:nil];
}
- (IBAction)copyMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setCanChooseFiles:NO];
[op setCanChooseDirectories:YES];
[op setCanCreateDirectories:YES];
[op setAllowsMultipleSelection:NO];
[op setTitle:@"Select a directory to copy marked files to"];
if ([op runModalForTypes:nil] == NSOKButton)
{
NSString *directory = [[op filenames] objectAtIndex:0];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py copyOrMove:b2n(YES) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
}
}
- (IBAction)deleteMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to send %d files to Trash. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return;
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
[py deleteMarked];
}
- (IBAction)expandAll:(id)sender
{
for (int i=0;i < [matches numberOfRows];i++)
[matches expandItem:[matches itemAtRow:i]];
}
- (IBAction)moveMarked:(id)sender
{
int mark_count = [[py getMarkCount] intValue];
if (!mark_count)
return;
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setCanChooseFiles:NO];
[op setCanChooseDirectories:YES];
[op setCanCreateDirectories:YES];
[op setAllowsMultipleSelection:NO];
[op setTitle:@"Select a directory to move marked files to"];
if ([op runModalForTypes:nil] == NSOKButton)
{
NSString *directory = [[op filenames] objectAtIndex:0];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
[py copyOrMove:b2n(NO) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
}
}
/* 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 */
- (void)windowWillClose:(NSNotification *)aNotification
{
[NSApp hide:NSApp];
}
- (void)jobCompleted:(NSNotification *)aNotification
{
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
int r = n2i([py getOperationalErrorCount]);
id lastAction = [[ProgressController mainProgressController] jobId];
if ([lastAction isEqualTo:jobCopy])
{
if (r > 0)
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be copied.",r]];
else
[Dialogs showMessage:@"All marked files were copied sucessfully."];
}
if ([lastAction isEqualTo:jobMove])
{
if (r > 0)
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be moved. They were kept in the results, and still are marked.",r]];
else
[Dialogs showMessage:@"All marked files were moved sucessfully."];
}
if ([lastAction isEqualTo:jobDelete])
{
if (r > 0)
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be sent to Trash. They were kept in the results, and still are marked.",r]];
else
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
}
// Re-activate toolbar items right after the progress bar stops showing instead of waiting until
// a mouse-over is performed
[[[self window] toolbar] validateVisibleItems];
}
- (void)jobInProgress:(NSNotification *)aNotification
{
[Dialogs showMessage:@"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."];
}
- (void)jobStarted:(NSNotification *)aNotification
{
NSDictionary *ui = [aNotification userInfo];
NSString *desc = [ui valueForKey:@"desc"];
[[ProgressController mainProgressController] setJobDesc:desc];
NSString *jobid = [ui valueForKey:@"jobid"];
// NSLog(jobid);
[[ProgressController mainProgressController] setJobId:jobid];
[[ProgressController mainProgressController] showSheetForParent:[self window]];
}
- (void)registrationRequired:(NSNotification *)aNotification
{
NSString *msg = @"This is a demo version, which only allows you 10 delete/copy/move actions per session. You cannot continue.";
[Dialogs showMessage:msg];
}
/* Toolbar */
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
{
NSToolbarItem *tbi = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
if ([itemIdentifier isEqualTo:tbbDirectories])
{
[tbi setLabel: @"Directories"];
[tbi setToolTip: @"Show/Hide the directories panel."];
[tbi setImage: [NSImage imageNamed: @"folder32"]];
[tbi setTarget: app];
[tbi setAction: @selector(toggleDirectories:)];
}
else if ([itemIdentifier isEqualTo:tbbDetails])
{
[tbi setLabel: @"Details"];
[tbi setToolTip: @"Show/Hide the details panel."];
[tbi setImage: [NSImage imageNamed: @"details32"]];
[tbi setTarget: self];
[tbi setAction: @selector(toggleDetailsPanel:)];
}
else if ([itemIdentifier isEqualTo:tbbPreferences])
{
[tbi setLabel: @"Preferences"];
[tbi setToolTip: @"Show the preferences panel."];
[tbi setImage: [NSImage imageNamed: @"preferences32"]];
[tbi setTarget: self];
[tbi setAction: @selector(showPreferencesPanel:)];
}
else if ([itemIdentifier isEqualTo:tbbPowerMarker])
{
[tbi setLabel: @"Power Marker"];
[tbi setToolTip: @"When enabled, only the duplicates are shown, not the references."];
[tbi setView:pmSwitchView];
[tbi setMinSize:[pmSwitchView frame].size];
[tbi setMaxSize:[pmSwitchView frame].size];
}
else if ([itemIdentifier isEqualTo:tbbScan])
{
[tbi setLabel: @"Start Scanning"];
[tbi setToolTip: @"Start scanning for duplicates in the selected directories."];
[tbi setImage: [NSImage imageNamed:[self logoImageName]]];
[tbi setTarget: self];
[tbi setAction: @selector(startDuplicateScan:)];
}
else if ([itemIdentifier isEqualTo:tbbAction])
{
[tbi setLabel: @"Action"];
[tbi setView:actionMenuView];
[tbi setMinSize:[actionMenuView frame].size];
[tbi setMaxSize:[actionMenuView frame].size];
}
else if ([itemIdentifier isEqualTo:tbbDelta])
{
[tbi setLabel: @"Delta Values"];
[tbi setToolTip: @"When enabled, this option makes dupeGuru display, where applicable, delta values instead of absolute values."];
[tbi setView:deltaSwitchView];
[tbi setMinSize:[deltaSwitchView frame].size];
[tbi setMaxSize:[deltaSwitchView frame].size];
}
else if ([itemIdentifier isEqualTo:tbbFilter])
{
[tbi setLabel: @"Filter"];
[tbi setToolTip: @"Filters the results using regular expression."];
[tbi setView:filterFieldView];
[tbi setMinSize:[filterFieldView frame].size];
[tbi setMaxSize:NSMakeSize(1000, [filterFieldView frame].size.height)];
}
[tbi setPaletteLabel: [tbi label]];
return tbi;
}
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:
tbbDirectories,
tbbDetails,
tbbPreferences,
tbbPowerMarker,
tbbScan,
tbbAction,
tbbDelta,
tbbFilter,
NSToolbarSeparatorItemIdentifier,
NSToolbarSpaceItemIdentifier,
NSToolbarFlexibleSpaceItemIdentifier,
nil];
}
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
return [NSArray arrayWithObjects:
tbbScan,
tbbAction,
tbbDirectories,
tbbDetails,
tbbPowerMarker,
tbbDelta,
tbbFilter,
nil];
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{
return ![[ProgressController mainProgressController] isShown];
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
return ![[ProgressController mainProgressController] isShown];
}
@end

View File

@ -0,0 +1,408 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 44;
objects = {
/* Begin PBXBuildFile section */
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
CE62CA730CFAF80D0001B6E0 /* ResultWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = CE62CA710CFAF80D0001B6E0 /* ResultWindow.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE62CA740CFAF80D0001B6E0 /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE62CA720CFAF80D0001B6E0 /* ResultWindow.m */; };
CE70A0FC0CF8C2560048C314 /* PyDupeGuru.h in Headers */ = {isa = PBXBuildFile; fileRef = CE70A0FB0CF8C2560048C314 /* PyDupeGuru.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE7D77050CF8987800BA287E /* Consts.h in Headers */ = {isa = PBXBuildFile; fileRef = CE7D77030CF8987800BA287E /* Consts.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE80DA190FC191320086DCA6 /* RecentDirectories.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA170FC191320086DCA6 /* RecentDirectories.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA1A0FC191320086DCA6 /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA180FC191320086DCA6 /* RecentDirectories.m */; };
CE80DA2E0FC191980086DCA6 /* Dialogs.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA250FC191980086DCA6 /* Dialogs.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA2F0FC191980086DCA6 /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA260FC191980086DCA6 /* Dialogs.m */; };
CE80DA300FC191980086DCA6 /* ProgressController.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA270FC191980086DCA6 /* ProgressController.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA310FC191980086DCA6 /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA280FC191980086DCA6 /* ProgressController.m */; };
CE80DA320FC191980086DCA6 /* PyApp.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA290FC191980086DCA6 /* PyApp.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA330FC191980086DCA6 /* RegistrationInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA2A0FC191980086DCA6 /* RegistrationInterface.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA340FC191980086DCA6 /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA2B0FC191980086DCA6 /* RegistrationInterface.m */; };
CE80DA350FC191980086DCA6 /* Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA2C0FC191980086DCA6 /* Utils.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA360FC191980086DCA6 /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA2D0FC191980086DCA6 /* Utils.m */; };
CE80DA3B0FC191C40086DCA6 /* Outline.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA370FC191C40086DCA6 /* Outline.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA3C0FC191C40086DCA6 /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA380FC191C40086DCA6 /* Outline.m */; };
CE80DA3D0FC191C40086DCA6 /* Table.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80DA390FC191C40086DCA6 /* Table.h */; settings = {ATTRIBUTES = (Private, ); }; };
CE80DA3E0FC191C40086DCA6 /* Table.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80DA3A0FC191C40086DCA6 /* Table.m */; };
CE80DB6D0FC194560086DCA6 /* progress.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE80DB690FC194560086DCA6 /* progress.nib */; };
CE80DB6E0FC194560086DCA6 /* registration.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE80DB6B0FC194560086DCA6 /* registration.nib */; };
CE895B5F0CFAE78300B5ADEA /* AppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = CE895B5D0CFAE78300B5ADEA /* AppDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE895B600CFAE78300B5ADEA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE895B5E0CFAE78300B5ADEA /* AppDelegate.m */; };
CE895C150CFAF0C900B5ADEA /* DirectoryPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = CE895C130CFAF0C900B5ADEA /* DirectoryPanel.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE895C160CFAF0C900B5ADEA /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE895C140CFAF0C900B5ADEA /* DirectoryPanel.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* dgbase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = dgbase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE62CA710CFAF80D0001B6E0 /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = "<group>"; };
CE62CA720CFAF80D0001B6E0 /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = "<group>"; };
CE70A0FB0CF8C2560048C314 /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyDupeGuru.h; sourceTree = "<group>"; };
CE7D77030CF8987800BA287E /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
CE80DA170FC191320086DCA6 /* RecentDirectories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RecentDirectories.h; path = ../../../cocoalib/RecentDirectories.h; sourceTree = SOURCE_ROOT; };
CE80DA180FC191320086DCA6 /* RecentDirectories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RecentDirectories.m; path = ../../../cocoalib/RecentDirectories.m; sourceTree = SOURCE_ROOT; };
CE80DA250FC191980086DCA6 /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = ../../../cocoalib/Dialogs.h; sourceTree = SOURCE_ROOT; };
CE80DA260FC191980086DCA6 /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
CE80DA270FC191980086DCA6 /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
CE80DA280FC191980086DCA6 /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
CE80DA290FC191980086DCA6 /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
CE80DA2A0FC191980086DCA6 /* RegistrationInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RegistrationInterface.h; path = ../../../cocoalib/RegistrationInterface.h; sourceTree = SOURCE_ROOT; };
CE80DA2B0FC191980086DCA6 /* RegistrationInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegistrationInterface.m; path = ../../../cocoalib/RegistrationInterface.m; sourceTree = SOURCE_ROOT; };
CE80DA2C0FC191980086DCA6 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = ../../../cocoalib/Utils.h; sourceTree = SOURCE_ROOT; };
CE80DA2D0FC191980086DCA6 /* Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Utils.m; path = ../../../cocoalib/Utils.m; sourceTree = SOURCE_ROOT; };
CE80DA370FC191C40086DCA6 /* Outline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Outline.h; path = ../../../cocoalib/Outline.h; sourceTree = SOURCE_ROOT; };
CE80DA380FC191C40086DCA6 /* Outline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Outline.m; path = ../../../cocoalib/Outline.m; sourceTree = SOURCE_ROOT; };
CE80DA390FC191C40086DCA6 /* Table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Table.h; path = ../../../cocoalib/Table.h; sourceTree = SOURCE_ROOT; };
CE80DA3A0FC191C40086DCA6 /* Table.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Table.m; path = ../../../cocoalib/Table.m; sourceTree = SOURCE_ROOT; };
CE80DB6A0FC194560086DCA6 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = ../../../cocoalib/English.lproj/progress.nib; sourceTree = SOURCE_ROOT; };
CE80DB6C0FC194560086DCA6 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = ../../../cocoalib/English.lproj/registration.nib; sourceTree = SOURCE_ROOT; };
CE895B5D0CFAE78300B5ADEA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
CE895B5E0CFAE78300B5ADEA /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
CE895C130CFAF0C900B5ADEA /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = "<group>"; };
CE895C140CFAF0C900B5ADEA /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = "<group>"; };
D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8DC2EF560486A6940098B216 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
034768DFFF38A50411DB9C8B /* Products */ = {
isa = PBXGroup;
children = (
8DC2EF5B0486A6940098B216 /* dgbase.framework */,
);
name = Products;
sourceTree = "<group>";
};
0867D691FE84028FC02AAC07 /* dgbase */ = {
isa = PBXGroup;
children = (
08FB77AEFE84172EC02AAC07 /* Classes */,
CE80DA160FC1910F0086DCA6 /* cocoalib */,
32C88DFF0371C24200C91783 /* Other Sources */,
089C1665FE841158C02AAC07 /* Resources */,
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */,
034768DFFF38A50411DB9C8B /* Products */,
);
name = dgbase;
sourceTree = "<group>";
};
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = {
isa = PBXGroup;
children = (
1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */,
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */,
);
name = "External Frameworks and Libraries";
sourceTree = "<group>";
};
089C1665FE841158C02AAC07 /* Resources */ = {
isa = PBXGroup;
children = (
CE80DB690FC194560086DCA6 /* progress.nib */,
CE80DB6B0FC194560086DCA6 /* registration.nib */,
8DC2EF5A0486A6940098B216 /* Info.plist */,
089C1666FE841158C02AAC07 /* InfoPlist.strings */,
);
name = Resources;
sourceTree = "<group>";
};
08FB77AEFE84172EC02AAC07 /* Classes */ = {
isa = PBXGroup;
children = (
CE62CA710CFAF80D0001B6E0 /* ResultWindow.h */,
CE62CA720CFAF80D0001B6E0 /* ResultWindow.m */,
CE895C130CFAF0C900B5ADEA /* DirectoryPanel.h */,
CE895C140CFAF0C900B5ADEA /* DirectoryPanel.m */,
CE895B5D0CFAE78300B5ADEA /* AppDelegate.h */,
CE895B5E0CFAE78300B5ADEA /* AppDelegate.m */,
);
name = Classes;
sourceTree = "<group>";
};
1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7B2FEA5585E11CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
0867D6A5FE840307C02AAC07 /* AppKit.framework */,
D2F7E79907B2D74100F64583 /* CoreData.framework */,
0867D69BFE84028FC02AAC07 /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
32C88DFF0371C24200C91783 /* Other Sources */ = {
isa = PBXGroup;
children = (
CE70A0FB0CF8C2560048C314 /* PyDupeGuru.h */,
CE7D77030CF8987800BA287E /* Consts.h */,
);
name = "Other Sources";
sourceTree = "<group>";
};
CE80DA160FC1910F0086DCA6 /* cocoalib */ = {
isa = PBXGroup;
children = (
CE80DA370FC191C40086DCA6 /* Outline.h */,
CE80DA380FC191C40086DCA6 /* Outline.m */,
CE80DA390FC191C40086DCA6 /* Table.h */,
CE80DA3A0FC191C40086DCA6 /* Table.m */,
CE80DA250FC191980086DCA6 /* Dialogs.h */,
CE80DA260FC191980086DCA6 /* Dialogs.m */,
CE80DA270FC191980086DCA6 /* ProgressController.h */,
CE80DA280FC191980086DCA6 /* ProgressController.m */,
CE80DA290FC191980086DCA6 /* PyApp.h */,
CE80DA2A0FC191980086DCA6 /* RegistrationInterface.h */,
CE80DA2B0FC191980086DCA6 /* RegistrationInterface.m */,
CE80DA2C0FC191980086DCA6 /* Utils.h */,
CE80DA2D0FC191980086DCA6 /* Utils.m */,
CE80DA170FC191320086DCA6 /* RecentDirectories.h */,
CE80DA180FC191320086DCA6 /* RecentDirectories.m */,
);
name = cocoalib;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
8DC2EF500486A6940098B216 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
CE7D77050CF8987800BA287E /* Consts.h in Headers */,
CE70A0FC0CF8C2560048C314 /* PyDupeGuru.h in Headers */,
CE895B5F0CFAE78300B5ADEA /* AppDelegate.h in Headers */,
CE895C150CFAF0C900B5ADEA /* DirectoryPanel.h in Headers */,
CE62CA730CFAF80D0001B6E0 /* ResultWindow.h in Headers */,
CE80DA190FC191320086DCA6 /* RecentDirectories.h in Headers */,
CE80DA2E0FC191980086DCA6 /* Dialogs.h in Headers */,
CE80DA300FC191980086DCA6 /* ProgressController.h in Headers */,
CE80DA320FC191980086DCA6 /* PyApp.h in Headers */,
CE80DA330FC191980086DCA6 /* RegistrationInterface.h in Headers */,
CE80DA350FC191980086DCA6 /* Utils.h in Headers */,
CE80DA3B0FC191C40086DCA6 /* Outline.h in Headers */,
CE80DA3D0FC191C40086DCA6 /* Table.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
8DC2EF4F0486A6940098B216 /* dgbase */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "dgbase" */;
buildPhases = (
8DC2EF500486A6940098B216 /* Headers */,
8DC2EF520486A6940098B216 /* Resources */,
8DC2EF540486A6940098B216 /* Sources */,
8DC2EF560486A6940098B216 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = dgbase;
productInstallPath = "$(HOME)/Library/Frameworks";
productName = dgbase;
productReference = 8DC2EF5B0486A6940098B216 /* dgbase.framework */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0867D690FE84028FC02AAC07 /* Project object */ = {
isa = PBXProject;
buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "dgbase" */;
compatibilityVersion = "Xcode 3.0";
hasScannedForEncodings = 1;
mainGroup = 0867D691FE84028FC02AAC07 /* dgbase */;
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
8DC2EF4F0486A6940098B216 /* dgbase */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8DC2EF520486A6940098B216 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */,
CE80DB6D0FC194560086DCA6 /* progress.nib in Resources */,
CE80DB6E0FC194560086DCA6 /* registration.nib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8DC2EF540486A6940098B216 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CE895B600CFAE78300B5ADEA /* AppDelegate.m in Sources */,
CE895C160CFAF0C900B5ADEA /* DirectoryPanel.m in Sources */,
CE62CA740CFAF80D0001B6E0 /* ResultWindow.m in Sources */,
CE80DA1A0FC191320086DCA6 /* RecentDirectories.m in Sources */,
CE80DA2F0FC191980086DCA6 /* Dialogs.m in Sources */,
CE80DA310FC191980086DCA6 /* ProgressController.m in Sources */,
CE80DA340FC191980086DCA6 /* RegistrationInterface.m in Sources */,
CE80DA360FC191980086DCA6 /* Utils.m in Sources */,
CE80DA3C0FC191C40086DCA6 /* Outline.m in Sources */,
CE80DA3E0FC191C40086DCA6 /* Table.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
089C1666FE841158C02AAC07 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
089C1667FE841158C02AAC07 /* English */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
CE80DB690FC194560086DCA6 /* progress.nib */ = {
isa = PBXVariantGroup;
children = (
CE80DB6A0FC194560086DCA6 /* English */,
);
name = progress.nib;
sourceTree = SOURCE_ROOT;
};
CE80DB6B0FC194560086DCA6 /* registration.nib */ = {
isa = PBXVariantGroup;
children = (
CE80DB6C0FC194560086DCA6 /* English */,
);
name = registration.nib;
sourceTree = SOURCE_ROOT;
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
1DEB91AE08733DA50010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COPY_PHASE_STRIP = NO;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/../../../cocoalib/build/Release\"",
);
FRAMEWORK_VERSION = A;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_FIX_AND_CONTINUE = YES;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Library/Frameworks";
PRODUCT_NAME = dgbase;
WRAPPER_EXTENSION = framework;
ZERO_LINK = YES;
};
name = Debug;
};
1DEB91AF08733DA50010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/../../../cocoalib/build/Release\"",
);
FRAMEWORK_VERSION = A;
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "@executable_path/../Frameworks";
PRODUCT_NAME = dgbase;
WRAPPER_EXTENSION = framework;
};
name = Release;
};
1DEB91B208733DA50010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_C_LANGUAGE_STANDARD = c99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
};
name = Debug;
};
1DEB91B308733DA50010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
GCC_C_LANGUAGE_STANDARD = c99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "dgbase" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB91AE08733DA50010E9CD /* Debug */,
1DEB91AF08733DA50010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "dgbase" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB91B208733DA50010E9CD /* Debug */,
1DEB91B308733DA50010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0867D690FE84028FC02AAC07 /* Project object */;
}

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

35
base/qt/about_box.py Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
# Unit Name: about_box
# Created By: Virgil Dupras
# Created On: 2009-05-09
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QCoreApplication, SIGNAL
from PyQt4.QtGui import QDialog, QDialogButtonBox, QPixmap
from about_box_ui import Ui_AboutBox
class AboutBox(QDialog, Ui_AboutBox):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be done in the Designer
self.setWindowTitle(u"About %s" % QCoreApplication.instance().applicationName())
self.nameLabel.setText(QCoreApplication.instance().applicationName())
self.versionLabel.setText('Version ' + QCoreApplication.instance().applicationVersion())
self.logoLabel.setPixmap(QPixmap(':/%s_big' % self.app.LOGO_NAME))
self.registerButton = self.buttonBox.addButton("Register", QDialogButtonBox.ActionRole)
#--- Events
def buttonClicked(self, button):
if button is self.registerButton:
self.app.ask_for_reg_code()

133
base/qt/about_box.ui Normal file
View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutBox</class>
<widget class="QDialog" name="AboutBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>190</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>About dupeGuru</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="logoLabel">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="dg.qrc">:/logo_me_big</pixmap>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="nameLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>dupeGuru</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Copyright Hardcoded Software 2009</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Registered To:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="registeredEmailLabel">
<property name="text">
<string>UNREGISTERED</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="dg.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AboutBox</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutBox</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

269
base/qt/app.py Normal file
View File

@ -0,0 +1,269 @@
#!/usr/bin/env python
# Unit Name: app
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
import logging
import os.path as op
import traceback
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
from hsutil import job
from hsutil.reg import RegistrationRequired
from dupeguru import data_pe
from dupeguru.app import (DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY,
JOB_DELETE)
from main_window import MainWindow
from directories_dialog import DirectoriesDialog
from about_box import AboutBox
from reg import Registration
from error_report_dialog import ErrorReportDialog
JOBID2TITLE = {
JOB_SCAN: "Scanning for duplicates",
JOB_LOAD: "Loading",
JOB_MOVE: "Moving",
JOB_COPY: "Copying",
JOB_DELETE: "Sending files to the recycle bin",
}
class Progress(QProgressDialog, job.ThreadedJobPerformer):
def __init__(self, parent):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QProgressDialog.__init__(self, '', u"Cancel", 0, 100, parent, flags)
self.setModal(True)
self.setAutoReset(False)
self.setAutoClose(False)
self._timer = QTimer()
self._jobid = ''
self.connect(self._timer, SIGNAL('timeout()'), self.updateProgress)
def updateProgress(self):
# the values might change before setValue happens
last_progress = self.last_progress
last_desc = self.last_desc
if not self._job_running or last_progress is None:
self._timer.stop()
self.close()
self.emit(SIGNAL('finished(QString)'), self._jobid)
if self._last_error is not None:
s = ''.join(traceback.format_exception(*self._last_error))
dialog = ErrorReportDialog(self.parent(), s)
dialog.exec_()
return
if self.wasCanceled():
self.job_cancelled = True
return
if last_desc:
self.setLabelText(last_desc)
self.setValue(last_progress)
def run(self, jobid, title, target, args=()):
self._jobid = jobid
self.reset()
self.setLabelText('')
self.run_threaded(target, args)
self.setWindowTitle(title)
self.show()
self._timer.start(500)
def demo_method(method):
def wrapper(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
except RegistrationRequired:
msg = "The demo version of dupeGuru only allows 10 actions (delete/move/copy) per session."
QMessageBox.information(self.main_window, 'Demo', msg)
return wrapper
class DupeGuru(DupeGuruBase, QObject):
LOGO_NAME = '<replace this>'
NAME = '<replace this>'
DELTA_COLUMNS = frozenset()
def __init__(self, data_module, appid):
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
DupeGuruBase.__init__(self, data_module, appdata, appid)
QObject.__init__(self)
self._setup()
#--- Private
def _setup(self):
self.selected_dupe = None
self.prefs = self._create_preferences()
self.prefs.load()
self._update_options()
self.main_window = self._create_main_window()
self._progress = Progress(self.main_window)
self.directories_dialog = DirectoriesDialog(self.main_window, self)
self.details_dialog = self._create_details_dialog(self.main_window)
self.preferences_dialog = self._create_preferences_dialog(self.main_window)
self.about_box = AboutBox(self.main_window, self)
self.reg = Registration(self)
self.set_registration(self.prefs.registration_code, self.prefs.registration_email)
if not self.registered:
self.reg.show_nag()
self.main_window.show()
self.load()
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished)
def _setup_as_registered(self):
self.prefs.registration_code = self.registration_code
self.prefs.registration_email = self.registration_email
self.main_window.actionRegister.setVisible(False)
self.about_box.registerButton.hide()
self.about_box.registeredEmailLabel.setText(self.prefs.registration_email)
def _update_options(self):
self.scanner.mix_file_kind = self.prefs.mix_file_kind
self.options['escape_filter_regexp'] = self.prefs.use_regexp
self.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
#--- Virtual
def _create_details_dialog(self, parent):
raise NotImplementedError()
def _create_main_window(self):
return MainWindow(app=self)
def _create_preferences(self):
raise NotImplementedError()
def _create_preferences_dialog(self, parent):
raise NotImplementedError()
#--- Override
def _start_job(self, jobid, func):
title = JOBID2TITLE[jobid]
try:
j = self._progress.create_job()
self._progress.run(jobid, title, func, args=(j, ))
except job.JobInProgressError:
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
QMessageBox.information(self.main_window, 'Action in progress', msg)
#--- Public
def add_dupes_to_ignore_list(self, duplicates):
for dupe in duplicates:
self.AddToIgnoreList(dupe)
self.remove_duplicates(duplicates)
def ApplyFilter(self, filter):
DupeGuruBase.ApplyFilter(self, filter)
self.emit(SIGNAL('resultsChanged()'))
def ask_for_reg_code(self):
if self.reg.ask_for_code():
self._setup_ui_as_registered()
@demo_method
def copy_or_move_marked(self, copy):
opname = 'copy' if copy else 'move'
title = "Select a directory to {0} marked files to".format(opname)
flags = QFileDialog.ShowDirsOnly
destination = unicode(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
if not destination:
return
recreate_path = self.prefs.destination_type
DupeGuruBase.copy_or_move_marked(self, copy, destination, recreate_path)
delete_marked = demo_method(DupeGuruBase.delete_marked)
def make_reference(self, duplicates):
DupeGuruBase.make_reference(self, duplicates)
self.emit(SIGNAL('resultsChanged()'))
def mark_all(self):
self.results.mark_all()
self.emit(SIGNAL('dupeMarkingChanged()'))
def mark_invert(self):
self.results.mark_invert()
self.emit(SIGNAL('dupeMarkingChanged()'))
def mark_none(self):
self.results.mark_none()
self.emit(SIGNAL('dupeMarkingChanged()'))
def open_selected(self):
if self.selected_dupe is None:
return
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path))
QDesktopServices.openUrl(url)
def remove_duplicates(self, duplicates):
self.results.remove_duplicates(duplicates)
self.emit(SIGNAL('resultsChanged()'))
def remove_marked_duplicates(self):
marked = [d for d in self.results.dupes if self.results.is_marked(d)]
self.remove_duplicates(marked)
def rename_dupe(self, dupe, newname):
try:
dupe.move(dupe.parent, newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def reveal_selected(self):
if self.selected_dupe is None:
return
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path[:-1]))
QDesktopServices.openUrl(url)
def select_duplicate(self, dupe):
self.selected_dupe = dupe
self.emit(SIGNAL('duplicateSelected()'))
def show_about_box(self):
self.about_box.show()
def show_details(self):
self.details_dialog.show()
def show_directories(self):
self.directories_dialog.show()
def show_help(self):
url = QUrl.fromLocalFile(op.abspath('help/intro.htm'))
QDesktopServices.openUrl(url)
def show_preferences(self):
self.preferences_dialog.load()
result = self.preferences_dialog.exec_()
if result == QDialog.Accepted:
self.preferences_dialog.save()
self.prefs.save()
self._update_options()
def toggle_marking_for_dupes(self, dupes):
for dupe in dupes:
self.results.mark_toggle(dupe)
self.emit(SIGNAL('dupeMarkingChanged()'))
#--- Events
def application_will_terminate(self):
self.Save()
self.SaveIgnoreList()
def job_finished(self, jobid):
self.emit(SIGNAL('resultsChanged()'))
if jobid == JOB_LOAD:
self.emit(SIGNAL('directoriesChanged()'))
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE) and self.last_op_error_count > 0:
msg = "{0} files could not be processed.".format(self.results.mark_count)
QMessageBox.warning(self.main_window, 'Warning', msg)

78
base/qt/details_table.py Normal file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python
# Unit Name: details_table
# Created By: Virgil Dupras
# Created On: 2009-05-17
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, SIGNAL, QAbstractTableModel, QVariant
from PyQt4.QtGui import QHeaderView, QTableView
HEADER = ['Attribute', 'Selected', 'Reference']
class DetailsModel(QAbstractTableModel):
def __init__(self, app):
QAbstractTableModel.__init__(self)
self._app = app
self._data = app.data
self._dupe_data = None
self._ref_data = None
self.connect(app, SIGNAL('duplicateSelected()'), self.duplicateSelected)
def columnCount(self, parent):
return len(HEADER)
def data(self, index, role):
if not index.isValid():
return QVariant()
if role != Qt.DisplayRole:
return QVariant()
column = index.column()
row = index.row()
if column == 0:
return QVariant(self._data.COLUMNS[row]['display'])
elif column == 1 and self._dupe_data:
return QVariant(self._dupe_data[row])
elif column == 2 and self._ref_data:
return QVariant(self._ref_data[row])
return QVariant()
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(HEADER):
return QVariant(HEADER[section])
return QVariant()
def rowCount(self, parent):
return len(self._data.COLUMNS)
#--- Events
def duplicateSelected(self):
dupe = self._app.selected_dupe
group = self._app.results.get_group_of_duplicate(dupe)
ref = group.ref
self._dupe_data = self._data.GetDisplayInfo(dupe, group)
self._ref_data = self._data.GetDisplayInfo(ref, group)
self.reset()
class DetailsTable(QTableView):
def __init__(self, *args):
QTableView.__init__(self, *args)
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QTableView.SelectRows)
self.setShowGrid(False)
def setModel(self, model):
QTableView.setModel(self, model)
# The model needs to be set to set header stuff
hheader = self.horizontalHeader()
hheader.setHighlightSections(False)
hheader.setStretchLastSection(False)
hheader.resizeSection(0, 100)
hheader.setResizeMode(0, QHeaderView.Fixed)
hheader.setResizeMode(1, QHeaderView.Stretch)
hheader.setResizeMode(2, QHeaderView.Stretch)
vheader = self.verticalHeader()
vheader.setVisible(False)
vheader.setDefaultSectionSize(18)

17
base/qt/dg.qrc Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="details">images/details32.png</file>
<file alias="logo_pe">images/dgpe_logo_32.png</file>
<file alias="logo_pe_big">images/dgpe_logo_128.png</file>
<file alias="logo_me">images/dgme_logo_32.png</file>
<file alias="logo_me_big">images/dgme_logo_128.png</file>
<file alias="logo_se">images/dgse_logo_32.png</file>
<file alias="logo_se_big">images/dgse_logo_128.png</file>
<file alias="folder">images/folderwin32.png</file>
<file alias="gear">images/gear.png</file>
<file alias="preferences">images/preferences32.png</file>
<file alias="actions">images/actions32.png</file>
<file alias="delta">images/delta32.png</file>
<file alias="power_marker">images/power_marker32.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,80 @@
#!/usr/bin/env python
# Unit Name: directories_dialog
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QDialog, QFileDialog, QHeaderView
from directories_dialog_ui import Ui_DirectoriesDialog
from directories_model import DirectoriesModel, DirectoriesDelegate
class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
def __init__(self, parent, app):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.app = app
self._setupUi()
self._updateRemoveButton()
self.connect(self.doneButton, SIGNAL('clicked()'), self.doneButtonClicked)
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked)
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked)
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
self.connect(self.app, SIGNAL('directoriesChanged()'), self.directoriesChanged)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be done in the Designer
self.directoriesModel = DirectoriesModel(self.app)
self.directoriesDelegate = DirectoriesDelegate()
self.treeView.setItemDelegate(self.directoriesDelegate)
self.treeView.setModel(self.directoriesModel)
header = self.treeView.header()
header.setStretchLastSection(False)
header.setResizeMode(0, QHeaderView.Stretch)
header.setResizeMode(1, QHeaderView.Fixed)
header.resizeSection(1, 100)
def _updateRemoveButton(self):
indexes = self.treeView.selectedIndexes()
if not indexes:
self.removeButton.setEnabled(False)
return
self.removeButton.setEnabled(True)
index = indexes[0]
node = index.internalPointer()
# label = 'Remove' if node.parent is None else 'Exclude'
def addButtonClicked(self):
title = u"Select a directory to add to the scanning list"
flags = QFileDialog.ShowDirsOnly
dirpath = unicode(QFileDialog.getExistingDirectory(self, title, '', flags))
if not dirpath:
return
self.app.AddDirectory(dirpath)
self.directoriesModel.reset()
def directoriesChanged(self):
self.directoriesModel.reset()
def doneButtonClicked(self):
self.hide()
def removeButtonClicked(self):
indexes = self.treeView.selectedIndexes()
if not indexes:
return
index = indexes[0]
node = index.internalPointer()
if node.parent is None:
row = index.row()
del self.app.directories[row]
self.directoriesModel.reset()
def selectionChanged(self, selected, deselected):
self._updateRemoveButton()

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DirectoriesDialog</class>
<widget class="QDialog" name="DirectoriesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>420</width>
<height>338</height>
</rect>
</property>
<property name="windowTitle">
<string>Directories</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="treeView">
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="minimumSize">
<size>
<width>91</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addButton">
<property name="minimumSize">
<size>
<width>91</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="doneButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>91</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Done</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,108 @@
#!/usr/bin/env python
# Unit Name: directories_model
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import QVariant, QModelIndex, Qt, QRect, QEvent, QPoint
from PyQt4.QtGui import QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush
from tree_model import TreeNode, TreeModel
HEADERS = ['Name', 'State']
STATES = ['Normal', 'Reference', 'Excluded']
class DirectoriesDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QComboBox(parent);
editor.addItems(STATES)
return editor
def setEditorData(self, editor, index):
value, ok = index.model().data(index, Qt.EditRole).toInt()
assert ok
editor.setCurrentIndex(value);
press = QMouseEvent(QEvent.MouseButtonPress, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
release = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QApplication.sendEvent(editor, press)
QApplication.sendEvent(editor, release)
# editor.showPopup() # this causes a weird glitch. the ugly workaround is above.
def setModelData(self, editor, model, index):
value = QVariant(editor.currentIndex())
model.setData(index, value, Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
class DirectoryNode(TreeNode):
def __init__(self, parent, ref, row):
TreeNode.__init__(self, parent, row)
self.ref = ref
def _get_children(self):
children = []
for index, directory in enumerate(self.ref.dirs):
node = DirectoryNode(self, directory, index)
children.append(node)
return children
class DirectoriesModel(TreeModel):
def __init__(self, app):
self._dirs = app.directories
TreeModel.__init__(self)
def _root_nodes(self):
nodes = []
for index, directory in enumerate(self._dirs):
nodes.append(DirectoryNode(None, directory, index))
return nodes
def columnCount(self, parent):
return 2
def data(self, index, role):
if not index.isValid():
return QVariant()
node = index.internalPointer()
if role == Qt.DisplayRole:
if index.column() == 0:
return QVariant(node.ref.name)
else:
return QVariant(STATES[self._dirs.GetState(node.ref.path)])
elif role == Qt.EditRole and index.column() == 1:
return QVariant(self._dirs.GetState(node.ref.path))
elif role == Qt.ForegroundRole:
state = self._dirs.GetState(node.ref.path)
if state == 1:
return QVariant(QBrush(Qt.blue))
elif state == 2:
return QVariant(QBrush(Qt.red))
return QVariant()
def flags(self, index):
if not index.isValid():
return 0
result = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 1:
result |= Qt.ItemIsEditable
return result
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal:
if role == Qt.DisplayRole and section < len(HEADERS):
return QVariant(HEADERS[section])
return QVariant()
def setData(self, index, value, role):
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
return False
node = index.internalPointer()
state, ok = value.toInt()
assert ok
self._dirs.SetState(node.ref.path, state)
return True

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
# Unit Name: error_report_dialog
# Created By: Virgil Dupras
# Created On: 2009-05-23
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QUrl
from PyQt4.QtGui import QDialog, QDesktopServices
from error_report_dialog_ui import Ui_ErrorReportDialog
class ErrorReportDialog(QDialog, Ui_ErrorReportDialog):
def __init__(self, parent, error):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.setupUi(self)
self.errorTextEdit.setPlainText(error)
def accept(self):
text = self.errorTextEdit.toPlainText()
url = QUrl("mailto:support@hardcoded.net?SUBJECT=Error Report&BODY=%s" % text)
QDesktopServices.openUrl(url)
QDialog.accept(self)

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ErrorReportDialog</class>
<widget class="QDialog" name="ErrorReportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>553</width>
<height>349</height>
</rect>
</property>
<property name="windowTitle">
<string>Error Report</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Something went wrong. Would you like to send the error report to Hardcoded Software?</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="errorTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="dontSendButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Don't Send</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sendButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Send</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>sendButton</sender>
<signal>clicked()</signal>
<receiver>ErrorReportDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>485</x>
<y>320</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>174</y>
</hint>
</hints>
</connection>
<connection>
<sender>dontSendButton</sender>
<signal>clicked()</signal>
<receiver>ErrorReportDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>373</x>
<y>320</y>
</hint>
<hint type="destinationlabel">
<x>276</x>
<y>174</y>
</hint>
</hints>
</connection>
</connections>
</ui>

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

@ -0,0 +1,20 @@
#!/usr/bin/env python
# Unit Name: gen
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
import os
def print_and_do(cmd):
print cmd
os.system(cmd)
print_and_do("pyuic4 main_window.ui > main_window_ui.py")
print_and_do("pyuic4 directories_dialog.ui > directories_dialog_ui.py")
print_and_do("pyuic4 about_box.ui > about_box_ui.py")
print_and_do("pyuic4 reg_submit_dialog.ui > reg_submit_dialog_ui.py")
print_and_do("pyuic4 reg_demo_dialog.ui > reg_demo_dialog_ui.py")
print_and_do("pyuic4 error_report_dialog.ui > error_report_dialog_ui.py")
print_and_do("pyrcc4 dg.qrc > dg_rc.py")

304
base/qt/main_window.py Normal file
View File

@ -0,0 +1,304 @@
#!/usr/bin/env python
# Unit Name: main_window
# Created By: Virgil Dupras
# Created On: 2009-04-25
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
QMessageBox, QInputDialog, QLineEdit)
from hsutil.misc import nonone
from dupeguru.app import NoScannableFileError, AllFilesAreRefError
import dg_rc
from main_window_ui import Ui_MainWindow
from results_model import ResultsDelegate, ResultsModel
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, app):
QMainWindow.__init__(self, None)
self.app = app
self._last_filter = None
self._setupUi()
self.resultsDelegate = ResultsDelegate()
self.resultsModel = ResultsModel(self.app)
self.resultsView.setModel(self.resultsModel)
self.resultsView.setItemDelegate(self.resultsDelegate)
self._load_columns()
self._update_column_actions_status()
self.resultsView.expandAll()
self._update_status_line()
self.connect(self.app, SIGNAL('resultsChanged()'), self.resultsChanged)
self.connect(self.app, SIGNAL('dupeMarkingChanged()'), self.dupeMarkingChanged)
self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit)
self.connect(self.resultsView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self.resultsModel, SIGNAL('modelReset()'), self.resultsReset)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
h = self.resultsView.header()
h.setHighlightSections(False)
h.setMovable(True)
h.setStretchLastSection(False)
h.setDefaultAlignment(Qt.AlignLeft)
self.setWindowTitle(QCoreApplication.instance().applicationName())
self.actionScan.setIcon(QIcon(QPixmap(':/%s' % self.app.LOGO_NAME)))
# Columns menu
menu = self.menuColumns
self._column_actions = []
for index, column in enumerate(self.app.data.COLUMNS):
action = menu.addAction(column['display'])
action.setCheckable(True)
action.column_index = index
self._column_actions.append(action)
menu.addSeparator()
action = menu.addAction("Reset to Defaults")
action.column_index = -1
# Action menu
actionMenu = QMenu('Actions', self.toolBar)
actionMenu.setIcon(QIcon(QPixmap(":/actions")))
actionMenu.addAction(self.actionDeleteMarked)
actionMenu.addAction(self.actionMoveMarked)
actionMenu.addAction(self.actionCopyMarked)
actionMenu.addAction(self.actionRemoveMarked)
actionMenu.addSeparator()
actionMenu.addAction(self.actionRemoveSelected)
actionMenu.addAction(self.actionIgnoreSelected)
actionMenu.addAction(self.actionMakeSelectedReference)
actionMenu.addSeparator()
actionMenu.addAction(self.actionOpenSelected)
actionMenu.addAction(self.actionRevealSelected)
actionMenu.addAction(self.actionRenameSelected)
self.actionActions.setMenu(actionMenu)
button = QToolButton(self.toolBar)
button.setDefaultAction(actionMenu.menuAction())
button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.actionsButton = button
self.toolBar.insertWidget(self.actionActions, button) # the action is a placeholder
self.toolBar.removeAction(self.actionActions)
self.statusLabel = QLabel(self)
self.statusbar.addPermanentWidget(self.statusLabel, 1)
#--- Private
def _confirm(self, title, msg, default_button=QMessageBox.Yes):
buttons = QMessageBox.Yes | QMessageBox.No
answer = QMessageBox.question(self, title, msg, buttons, default_button)
return answer == QMessageBox.Yes
def _load_columns(self):
h = self.resultsView.header()
h.setResizeMode(QHeaderView.Interactive)
prefs = self.app.prefs
attrs = zip(prefs.columns_width, prefs.columns_visible)
for index, (width, visible) in enumerate(attrs):
h.resizeSection(index, width)
h.setSectionHidden(index, not visible)
h.setResizeMode(0, QHeaderView.Stretch)
def _redraw_results(self):
# HACK. this is the only way I found to update the widget without reseting everything
self.resultsView.scroll(0, 1)
self.resultsView.scroll(0, -1)
def _save_columns(self):
h = self.resultsView.header()
widths = []
visible = []
for i in range(len(self.app.data.COLUMNS)):
widths.append(h.sectionSize(i))
visible.append(not h.isSectionHidden(i))
prefs = self.app.prefs
prefs.columns_width = widths
prefs.columns_visible = visible
prefs.save()
def _update_column_actions_status(self):
h = self.resultsView.header()
for action in self._column_actions:
colid = action.column_index
action.setChecked(not h.isSectionHidden(colid))
def _update_status_line(self):
self.statusLabel.setText(self.app.stat_line)
#--- Actions
def aboutTriggered(self):
self.app.show_about_box()
def actionsTriggered(self):
self.actionsButton.showMenu()
def addToIgnoreListTriggered(self):
dupes = self.resultsView.selectedDupes()
if not dupes:
return
title = "Add to Ignore List"
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes))
if self._confirm(title, msg):
self.app.add_dupes_to_ignore_list(dupes)
def applyFilterTriggered(self):
title = "Apply Filter"
msg = "Type the filter you want to apply on your results. See help for details."
text = nonone(self._last_filter, '[*]')
answer, ok = QInputDialog.getText(self, title, msg, QLineEdit.Normal, text)
if not ok:
return
answer = unicode(answer)
self.app.ApplyFilter(answer)
self._last_filter = answer
def cancelFilterTriggered(self):
self.app.ApplyFilter('')
def checkForUpdateTriggered(self):
QProcess.execute('updater.exe', ['/checknow'])
def clearIgnoreListTriggered(self):
title = "Clear Ignore List"
count = len(self.app.scanner.ignore_list)
if not count:
QMessageBox.information(self, title, "Nothing to clear.")
return
msg = "Do you really want to remove all {0} items from the ignore list?".format(count)
if self._confirm(title, msg, QMessageBox.No):
self.app.scanner.ignore_list.Clear()
QMessageBox.information(self, title, "Ignore list cleared.")
def copyTriggered(self):
self.app.copy_or_move_marked(True)
def deleteTriggered(self):
count = self.app.results.mark_count
if not count:
return
title = "Delete duplicates"
msg = "You are about to send {0} files to the recycle bin. Continue?".format(count)
if self._confirm(title, msg):
self.app.delete_marked()
def deltaTriggered(self):
self.resultsModel.delta = self.actionDelta.isChecked()
self._redraw_results()
def detailsTriggered(self):
self.app.show_details()
def directoriesTriggered(self):
self.app.show_directories()
def makeReferenceTriggered(self):
self.app.make_reference(self.resultsView.selectedDupes())
def markAllTriggered(self):
self.app.mark_all()
def markInvertTriggered(self):
self.app.mark_invert()
def markNoneTriggered(self):
self.app.mark_none()
def markSelectedTriggered(self):
dupes = self.resultsView.selectedDupes()
self.app.toggle_marking_for_dupes(dupes)
def moveTriggered(self):
self.app.copy_or_move_marked(False)
def openTriggered(self):
self.app.open_selected()
def powerMarkerTriggered(self):
self.resultsModel.power_marker = self.actionPowerMarker.isChecked()
def preferencesTriggered(self):
self.app.show_preferences()
def registerTrigerred(self):
self.app.ask_for_reg_code()
def removeMarkedTriggered(self):
count = self.app.results.mark_count
if not count:
return
title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(count)
if self._confirm(title, msg):
self.app.remove_marked_duplicates()
def removeSelectedTriggered(self):
dupes = self.resultsView.selectedDupes()
if not dupes:
return
title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes))
if self._confirm(title, msg):
self.app.remove_duplicates(dupes)
def renameTriggered(self):
self.resultsView.edit(self.resultsView.selectionModel().currentIndex())
def revealTriggered(self):
self.app.reveal_selected()
def scanTriggered(self):
title = "Start a new scan"
if len(self.app.results.groups) > 0:
msg = "Are you sure you want to start a new duplicate scan?"
if not self._confirm(title, msg):
return
try:
self.app.start_scanning()
except NoScannableFileError:
msg = "The selected directories contain no scannable file."
QMessageBox.warning(self, title, msg)
self.app.show_directories()
except AllFilesAreRefError:
msg = "You cannot make a duplicate scan with only reference directories."
QMessageBox.warning(self, title, msg)
def showHelpTriggered(self):
self.app.show_help()
#--- Events
def application_will_terminate(self):
self._save_columns()
def columnToggled(self, action):
colid = action.column_index
if colid == -1:
self.app.prefs.reset_columns()
self._load_columns()
else:
h = self.resultsView.header()
h.setSectionHidden(colid, not h.isSectionHidden(colid))
self._update_column_actions_status()
def dupeMarkingChanged(self):
self._redraw_results()
self._update_status_line()
def resultsChanged(self):
self.resultsView.model().reset()
def resultsReset(self):
self.resultsView.expandAll()
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)

911
base/qt/main_window.ui Normal file
View File

@ -0,0 +1,911 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>514</height>
</rect>
</property>
<property name="windowTitle">
<string>dupeGuru</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="ResultsView" name="resultsView">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuColumns">
<property name="title">
<string>Columns</string>
</property>
</widget>
<widget class="QMenu" name="menuActions">
<property name="title">
<string>Actions</string>
</property>
<addaction name="actionDeleteMarked"/>
<addaction name="actionMoveMarked"/>
<addaction name="actionCopyMarked"/>
<addaction name="actionRemoveMarked"/>
<addaction name="separator"/>
<addaction name="actionRemoveSelected"/>
<addaction name="actionIgnoreSelected"/>
<addaction name="actionMakeSelectedReference"/>
<addaction name="separator"/>
<addaction name="actionOpenSelected"/>
<addaction name="actionRevealSelected"/>
<addaction name="actionRenameSelected"/>
<addaction name="separator"/>
<addaction name="actionApplyFilter"/>
<addaction name="actionCancelFilter"/>
</widget>
<widget class="QMenu" name="menuMark">
<property name="title">
<string>Mark</string>
</property>
<addaction name="actionMarkAll"/>
<addaction name="actionMarkNone"/>
<addaction name="actionInvertMarking"/>
<addaction name="actionMarkSelected"/>
</widget>
<widget class="QMenu" name="menuModes">
<property name="title">
<string>Modes</string>
</property>
<addaction name="actionPowerMarker"/>
<addaction name="actionDelta"/>
</widget>
<widget class="QMenu" name="menuWindow">
<property name="title">
<string>Windows</string>
</property>
<addaction name="actionDetails"/>
<addaction name="actionDirectories"/>
<addaction name="actionPreferences"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionShowHelp"/>
<addaction name="actionRegister"/>
<addaction name="actionCheckForUpdate"/>
<addaction name="actionAbout"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionScan"/>
<addaction name="separator"/>
<addaction name="actionClearIgnoreList"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuMark"/>
<addaction name="menuActions"/>
<addaction name="menuColumns"/>
<addaction name="menuModes"/>
<addaction name="menuWindow"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionScan"/>
<addaction name="actionActions"/>
<addaction name="actionDirectories"/>
<addaction name="actionDetails"/>
<addaction name="actionPreferences"/>
<addaction name="actionDelta"/>
<addaction name="actionPowerMarker"/>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
</widget>
<action name="actionScan">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/logo_pe</normaloff>:/logo_pe</iconset>
</property>
<property name="text">
<string>Start Scan</string>
</property>
<property name="toolTip">
<string>Start scanning for duplicates</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionDirectories">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/folder</normaloff>:/folder</iconset>
</property>
<property name="text">
<string>Directories</string>
</property>
<property name="shortcut">
<string>Ctrl+4</string>
</property>
</action>
<action name="actionDetails">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/details</normaloff>:/details</iconset>
</property>
<property name="text">
<string>Details</string>
</property>
<property name="shortcut">
<string>Ctrl+3</string>
</property>
</action>
<action name="actionActions">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/actions</normaloff>:/actions</iconset>
</property>
<property name="text">
<string>Actions</string>
</property>
</action>
<action name="actionPreferences">
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/preferences</normaloff>:/preferences</iconset>
</property>
<property name="text">
<string>Preferences</string>
</property>
<property name="shortcut">
<string>Ctrl+5</string>
</property>
</action>
<action name="actionDelta">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/delta</normaloff>:/delta</iconset>
</property>
<property name="text">
<string>Delta Values</string>
</property>
<property name="shortcut">
<string>Ctrl+2</string>
</property>
</action>
<action name="actionPowerMarker">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="dg.qrc">
<normaloff>:/power_marker</normaloff>:/power_marker</iconset>
</property>
<property name="text">
<string>Power Marker</string>
</property>
<property name="shortcut">
<string>Ctrl+1</string>
</property>
</action>
<action name="actionDeleteMarked">
<property name="text">
<string>Send Marked to Recycle Bin</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</action>
<action name="actionMoveMarked">
<property name="text">
<string>Move Marked to...</string>
</property>
<property name="shortcut">
<string>Ctrl+M</string>
</property>
</action>
<action name="actionCopyMarked">
<property name="text">
<string>Copy Marked to...</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+M</string>
</property>
</action>
<action name="actionRemoveMarked">
<property name="text">
<string>Remove Marked from Results</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="actionRemoveSelected">
<property name="text">
<string>Remove Selected from Results</string>
</property>
<property name="shortcut">
<string>Ctrl+Del</string>
</property>
</action>
<action name="actionIgnoreSelected">
<property name="text">
<string>Add Selected to Ignore List</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+Del</string>
</property>
</action>
<action name="actionMakeSelectedReference">
<property name="text">
<string>Make Selected Reference</string>
</property>
<property name="shortcut">
<string>Ctrl+Space</string>
</property>
</action>
<action name="actionOpenSelected">
<property name="text">
<string>Open Selected with Default Application</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="actionRevealSelected">
<property name="text">
<string>Open Containing Folder of Selected</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+O</string>
</property>
</action>
<action name="actionRenameSelected">
<property name="text">
<string>Rename Selected</string>
</property>
<property name="shortcut">
<string>F2</string>
</property>
</action>
<action name="actionMarkAll">
<property name="text">
<string>Mark All</string>
</property>
<property name="shortcut">
<string>Ctrl+A</string>
</property>
</action>
<action name="actionMarkNone">
<property name="text">
<string>Mark None</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+A</string>
</property>
</action>
<action name="actionInvertMarking">
<property name="text">
<string>Invert Marking</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+A</string>
</property>
</action>
<action name="actionMarkSelected">
<property name="text">
<string>Mark Selected</string>
</property>
</action>
<action name="actionClearIgnoreList">
<property name="text">
<string>Clear Ignore List</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionApplyFilter">
<property name="text">
<string>Apply Filter</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="actionCancelFilter">
<property name="text">
<string>Cancel Filter</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+F</string>
</property>
</action>
<action name="actionShowHelp">
<property name="text">
<string>dupeGuru Help</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About dupeGuru</string>
</property>
</action>
<action name="actionRegister">
<property name="text">
<string>Register dupeGuru</string>
</property>
</action>
<action name="actionCheckForUpdate">
<property name="text">
<string>Check for Update</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>ResultsView</class>
<extends>QTreeView</extends>
<header>results_model</header>
</customwidget>
</customwidgets>
<resources>
<include location="dg.qrc"/>
</resources>
<connections>
<connection>
<sender>actionDirectories</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>directoriesTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionActions</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>actionsTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCopyMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>copyTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionDeleteMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>deleteTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionDelta</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>deltaTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionDetails</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>detailsTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionIgnoreSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>addToIgnoreListTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMakeSelectedReference</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>makeReferenceTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMoveMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>moveTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionOpenSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>openTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionPowerMarker</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>powerMarkerTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionPreferences</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>preferencesTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRemoveMarked</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>removeMarkedTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRemoveSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>removeSelectedTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRevealSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>revealTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRenameSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>renameTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionScan</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>scanTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionClearIgnoreList</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>clearIgnoreListTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMarkAll</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markAllTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMarkNone</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markNoneTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionMarkSelected</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markSelectedTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionInvertMarking</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>markInvertTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionApplyFilter</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>applyFilterTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCancelFilter</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>cancelFilterTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionShowHelp</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>showHelpTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionAbout</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>aboutTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionRegister</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>registerTrigerred()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>actionCheckForUpdate</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>checkForUpdateTriggered()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>314</x>
<y>256</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>directoriesTriggered()</slot>
<slot>scanTriggered()</slot>
<slot>actionsTriggered()</slot>
<slot>detailsTriggered()</slot>
<slot>preferencesTriggered()</slot>
<slot>deltaTriggered()</slot>
<slot>powerMarkerTriggered()</slot>
<slot>deleteTriggered()</slot>
<slot>moveTriggered()</slot>
<slot>copyTriggered()</slot>
<slot>removeMarkedTriggered()</slot>
<slot>removeSelectedTriggered()</slot>
<slot>addToIgnoreListTriggered()</slot>
<slot>makeReferenceTriggered()</slot>
<slot>openTriggered()</slot>
<slot>revealTriggered()</slot>
<slot>renameTriggered()</slot>
<slot>clearIgnoreListTriggered()</slot>
<slot>clearPictureCacheTriggered()</slot>
<slot>markAllTriggered()</slot>
<slot>markNoneTriggered()</slot>
<slot>markInvertTriggered()</slot>
<slot>markSelectedTriggered()</slot>
<slot>applyFilterTriggered()</slot>
<slot>cancelFilterTriggered()</slot>
<slot>showHelpTriggered()</slot>
<slot>aboutTriggered()</slot>
<slot>registerTrigerred()</slot>
<slot>checkForUpdateTriggered()</slot>
</slots>
</ui>

109
base/qt/preferences.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python
# Unit Name: preferences
# Created By: Virgil Dupras
# Created On: 2009-05-03
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import QSettings, QVariant
from hsutil.misc import tryint
def variant_to_py(v):
value = None
ok = False
t = v.type()
if t == QVariant.String:
value = unicode(v.toString())
ok = True # anyway
# might be bool or int, try them
if v == 'true':
value = True
elif value == 'false':
value = False
else:
value = tryint(value, value)
elif t == QVariant.Int:
value, ok = v.toInt()
elif t == QVariant.Bool:
value, ok = v.toBool(), True
elif t in (QVariant.List, QVariant.StringList):
value, ok = map(variant_to_py, v.toList()), True
if not ok:
raise TypeError()
return value
class Preferences(object):
# (width, is_visible)
COLUMNS_DEFAULT_ATTRS = []
def __init__(self):
self.reset()
self.reset_columns()
def _load_specific(self, settings, get):
# load prefs specific to the dg edition
pass
def load(self):
self.reset()
settings = QSettings()
def get(name, default):
if settings.contains(name):
return variant_to_py(settings.value(name))
else:
return default
self.filter_hardness = get('FilterHardness', self.filter_hardness)
self.mix_file_kind = get('MixFileKind', self.mix_file_kind)
self.use_regexp = get('UseRegexp', self.use_regexp)
self.remove_empty_folders = get('RemoveEmptyFolders', self.remove_empty_folders)
self.destination_type = get('DestinationType', self.destination_type)
widths = get('ColumnsWidth', self.columns_width)
# only set nonzero values
for index, width in enumerate(widths[:len(self.columns_width)]):
if width > 0:
self.columns_width[index] = width
self.columns_visible = get('ColumnsVisible', self.columns_visible)
self.registration_code = get('RegistrationCode', self.registration_code)
self.registration_email = get('RegistrationEmail', self.registration_email)
self._load_specific(settings, get)
def _reset_specific(self):
# reset prefs specific to the dg edition
pass
def reset(self):
self.filter_hardness = 95
self.mix_file_kind = True
self.use_regexp = False
self.remove_empty_folders = False
self.destination_type = 1
self.registration_code = ''
self.registration_email = ''
self._reset_specific()
def reset_columns(self):
self.columns_width = [width for width, _ in self.COLUMNS_DEFAULT_ATTRS]
self.columns_visible = [visible for _, visible in self.COLUMNS_DEFAULT_ATTRS]
def _save_specific(self, settings, set_):
# save prefs specific to the dg edition
pass
def save(self):
settings = QSettings()
def set_(name, value):
settings.setValue(name, QVariant(value))
set_('FilterHardness', self.filter_hardness)
set_('MixFileKind', self.mix_file_kind)
set_('UseRegexp', self.use_regexp)
set_('RemoveEmptyFolders', self.remove_empty_folders)
set_('DestinationType', self.destination_type)
set_('ColumnsWidth', self.columns_width)
set_('ColumnsVisible', self.columns_visible)
set_('RegistrationCode', self.registration_code)
set_('RegistrationEmail', self.registration_email)
self._save_specific(settings, set_)

34
base/qt/reg.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
# Unit Name: reg
# Created By: Virgil Dupras
# Created On: 2009-05-09
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from hashlib import md5
from PyQt4.QtGui import QDialog
from reg_submit_dialog import RegSubmitDialog
from reg_demo_dialog import RegDemoDialog
class Registration(object):
def __init__(self, app):
self.app = app
def ask_for_code(self):
dialog = RegSubmitDialog(self.app.main_window, self.app.is_code_valid)
result = dialog.exec_()
code = unicode(dialog.codeEdit.text())
email = unicode(dialog.emailEdit.text())
dialog.setParent(None) # free it
if result == QDialog.Accepted and self.app.is_code_valid(code, email):
self.app.set_registration(code, email)
return True
return False
def show_nag(self):
dialog = RegDemoDialog(self.app.main_window, self)
dialog.exec_()
dialog.setParent(None) # free it

View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
# Unit Name: reg_demo_dialog
# Created By: Virgil Dupras
# Created On: 2009-05-10
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
from reg_demo_dialog_ui import Ui_RegDemoDialog
class RegDemoDialog(QDialog, Ui_RegDemoDialog):
def __init__(self, parent, reg):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self.reg = reg
self._setupUi()
self.connect(self.enterCodeButton, SIGNAL('clicked()'), self.enterCodeClicked)
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
appname = QCoreApplication.instance().applicationName()
title = self.windowTitle()
title = title.replace('$appname', appname)
self.setWindowTitle(title)
title = self.titleLabel.text()
title = title.replace('$appname', appname)
self.titleLabel.setText(title)
desc = self.demoDescLabel.text()
desc = desc.replace('$appname', appname)
self.demoDescLabel.setText(desc)
#--- Events
def enterCodeClicked(self):
if self.reg.ask_for_code():
self.accept()
def purchaseClicked(self):
url = QUrl('http://www.hardcoded.net/purchase.htm')
QDesktopServices.openUrl(url)

140
base/qt/reg_demo_dialog.ui Normal file
View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RegDemoDialog</class>
<widget class="QDialog" name="RegDemoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>387</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>$appname Demo Version</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>$appname Demo Version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="demoDescLabel">
<property name="text">
<string>You are currently running a demo version of $appname. This version has limited functionalities, and you need to buy it to have access to these functionalities.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="tryButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Try Demo</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="enterCodeButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Enter Code</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="purchaseButton">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Purchase</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>tryButton</sender>
<signal>clicked()</signal>
<receiver>RegDemoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>112</x>
<y>161</y>
</hint>
<hint type="destinationlabel">
<x>201</x>
<y>94</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python
# Unit Name: reg_submit_dialog
# Created By: Virgil Dupras
# Created On: 2009-05-09
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
from reg_submit_dialog_ui import Ui_RegSubmitDialog
class RegSubmitDialog(QDialog, Ui_RegSubmitDialog):
def __init__(self, parent, is_valid_func):
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
QDialog.__init__(self, parent, flags)
self._setupUi()
self.is_valid_func = is_valid_func
self.connect(self.submitButton, SIGNAL('clicked()'), self.submitClicked)
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
appname = QCoreApplication.instance().applicationName()
prompt = self.promptLabel.text()
prompt = prompt.replace('$appname', appname)
self.promptLabel.setText(prompt)
#--- Events
def purchaseClicked(self):
url = QUrl('http://www.hardcoded.net/purchase.htm')
QDesktopServices.openUrl(url)
def submitClicked(self):
code = unicode(self.codeEdit.text())
email = unicode(self.emailEdit.text())
title = "Registration"
if self.is_valid_func(code, email):
msg = "This code is valid. Thanks!"
QMessageBox.information(self, title, msg)
self.accept()
else:
msg = "This code is invalid"
QMessageBox.warning(self, title, msg)

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RegSubmitDialog</class>
<widget class="QDialog" name="RegSubmitDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>134</height>
</rect>
</property>
<property name="windowTitle">
<string>Enter your registration code</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="promptLabel">
<property name="text">
<string>Please enter your $appname registration code and registered e-mail (the e-mail you used for the purchase), then press &quot;Submit&quot;.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="formAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Registration code:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Registered e-mail:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="codeEdit"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="emailEdit"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="purchaseButton">
<property name="text">
<string>Purchase</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="submitButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Submit</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>cancelButton</sender>
<signal>clicked()</signal>
<receiver>RegSubmitDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>260</x>
<y>159</y>
</hint>
<hint type="destinationlabel">
<x>198</x>
<y>97</y>
</hint>
</hints>
</connection>
</connections>
</ui>

175
base/qt/results_model.py Normal file
View File

@ -0,0 +1,175 @@
#!/usr/bin/env python
# Unit Name:
# Created By: Virgil Dupras
# Created On: 2009-04-23
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import SIGNAL, Qt, QAbstractItemModel, QVariant, QModelIndex, QRect
from PyQt4.QtGui import QBrush, QStyledItemDelegate, QFont, QTreeView, QColor
from tree_model import TreeNode, TreeModel
class ResultNode(TreeNode):
def __init__(self, model, parent, row, dupe, group):
TreeNode.__init__(self, parent, row)
self.model = model
self.dupe = dupe
self.group = group
self._normalData = None
self._deltaData = None
def _get_children(self):
children = []
if self.dupe is self.group.ref:
for index, dupe in enumerate(self.group.dupes):
children.append(ResultNode(self.model, self, index, dupe, self.group))
return children
def reset(self):
self._normalData = None
self._deltaData = None
@property
def normalData(self):
if self._normalData is None:
self._normalData = self.model._data.GetDisplayInfo(self.dupe, self.group, delta=False)
return self._normalData
@property
def deltaData(self):
if self._deltaData is None:
self._deltaData = self.model._data.GetDisplayInfo(self.dupe, self.group, delta=True)
return self._deltaData
class ResultsDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
QStyledItemDelegate.initStyleOption(self, option, index)
node = index.internalPointer()
if node.group.ref is node.dupe:
newfont = QFont(option.font)
newfont.setBold(True)
option.font = newfont
class ResultsModel(TreeModel):
def __init__(self, app):
self._app = app
self._results = app.results
self._data = app.data
self._delta_columns = app.DELTA_COLUMNS
self.delta = False
self._power_marker = False
TreeModel.__init__(self)
def _root_nodes(self):
nodes = []
if self.power_marker:
for index, dupe in enumerate(self._results.dupes):
group = self._results.get_group_of_duplicate(dupe)
nodes.append(ResultNode(self, None, index, dupe, group))
else:
for index, group in enumerate(self._results.groups):
nodes.append(ResultNode(self, None, index, group.ref, group))
return nodes
def columnCount(self, parent):
return len(self._data.COLUMNS)
def data(self, index, role):
if not index.isValid():
return QVariant()
node = index.internalPointer()
if role == Qt.DisplayRole:
data = node.deltaData if self.delta else node.normalData
return QVariant(data[index.column()])
elif role == Qt.CheckStateRole:
if index.column() == 0 and node.dupe is not node.group.ref:
state = Qt.Checked if self._results.is_marked(node.dupe) else Qt.Unchecked
return QVariant(state)
elif role == Qt.ForegroundRole:
if node.dupe is node.group.ref or node.dupe.is_ref:
return QVariant(QBrush(Qt.blue))
elif self.delta and index.column() in self._delta_columns:
return QVariant(QBrush(QColor(255, 142, 40))) # orange
elif role == Qt.EditRole:
if index.column() == 0:
return QVariant(node.normalData[index.column()])
return QVariant()
def dupesForIndexes(self, indexes):
nodes = [index.internalPointer() for index in indexes]
return [node.dupe for node in nodes]
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 0:
flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
return flags
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(self._data.COLUMNS):
return QVariant(self._data.COLUMNS[section]['display'])
return QVariant()
def setData(self, index, value, role):
if not index.isValid():
return False
node = index.internalPointer()
if role == Qt.CheckStateRole:
if index.column() == 0:
self._app.toggle_marking_for_dupes([node.dupe])
return True
if role == Qt.EditRole:
if index.column() == 0:
value = unicode(value.toString())
if self._app.rename_dupe(node.dupe, value):
node.reset()
return True
return False
def sort(self, column, order):
if self.power_marker:
self._results.sort_dupes(column, order == Qt.AscendingOrder, self.delta)
else:
self._results.sort_groups(column, order == Qt.AscendingOrder)
self.reset()
def toggleMarked(self, indexes):
assert indexes
dupes = self.dupesForIndexes(indexes)
self._app.toggle_marking_for_dupes(dupes)
#--- Properties
@property
def power_marker(self):
return self._power_marker
@power_marker.setter
def power_marker(self, value):
if value == self._power_marker:
return
self._power_marker = value
self.reset()
class ResultsView(QTreeView):
#--- Override
def keyPressEvent(self, event):
if event.text() == ' ':
self.model().toggleMarked(self.selectionModel().selectedRows())
return
QTreeView.keyPressEvent(self, event)
def setModel(self, model):
assert isinstance(model, ResultsModel)
QTreeView.setModel(self, model)
#--- Public
def selectedDupes(self):
return self.model().dupesForIndexes(self.selectionModel().selectedRows())

66
base/qt/tree_model.py Normal file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
# Unit Name: tree_model
# Created By: Virgil Dupras
# Created On: 2009-05-04
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt, QAbstractItemModel, QVariant, QModelIndex
class TreeNode(object):
def __init__(self, parent, row):
self.parent = parent
self.row = row
self._children = None
def _get_children(self):
raise NotImplementedError()
@property
def children(self):
if self._children is None:
self._children = self._get_children()
return self._children
class TreeModel(QAbstractItemModel):
def __init__(self):
QAbstractItemModel.__init__(self)
self._nodes = None
def _root_nodes(self):
raise NotImplementedError()
def index(self, row, column, parent):
if not self.nodes:
return QModelIndex()
if not parent.isValid():
return self.createIndex(row, column, self.nodes[row])
node = parent.internalPointer()
return self.createIndex(row, column, node.children[row])
def parent(self, index):
if not index.isValid():
return QModelIndex()
node = index.internalPointer()
if node.parent is None:
return QModelIndex()
else:
return self.createIndex(node.parent.row, 0, node.parent)
def reset(self):
self._nodes = None
QAbstractItemModel.reset(self)
def rowCount(self, parent):
if not parent.isValid():
return len(self.nodes)
node = parent.internalPointer()
return len(node.children)
@property
def nodes(self):
if self._nodes is None:
self._nodes = self._root_nodes()
return self._nodes

BIN
images/actions32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
images/delta32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
images/details32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
images/dgme_logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/dgme_logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/dgme_logo_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
images/dgpe_logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/dgpe_logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
images/dgpe_logo_32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
images/dgse_logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
images/dgse_logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/dgse_logo_32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/folder32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
images/folderwin32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/gear.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

BIN
images/power_marker32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
images/preferences32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

22
me/cocoa/AppDelegate.h Normal file
View File

@ -0,0 +1,22 @@
#import <Cocoa/Cocoa.h>
#import "dgbase/AppDelegate.h"
#import "ResultWindow.h"
#import "DirectoryPanel.h"
#import "PyDupeGuru.h"
@interface AppDelegate : AppDelegateBase
{
IBOutlet NSButton *presetsButton;
IBOutlet NSPopUpButton *presetsPopup;
IBOutlet ResultWindow *result;
DirectoryPanel *_directoryPanel;
}
- (IBAction)openWebsite:(id)sender;
- (IBAction)popupPresets:(id)sender;
- (IBAction)toggleDirectories:(id)sender;
- (IBAction)usePreset:(id)sender;
- (DirectoryPanel *)directoryPanel;
- (PyDupeGuru *)py;
@end

158
me/cocoa/AppDelegate.m Normal file
View File

@ -0,0 +1,158 @@
#import "AppDelegate.h"
#import "cocoalib/ProgressController.h"
#import "cocoalib/RegistrationInterface.h"
#import "cocoalib/Utils.h"
#import "cocoalib/ValueTransformers.h"
#import "cocoalib/Dialogs.h"
#import "Consts.h"
@implementation AppDelegate
+ (void)initialize
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:10];
[d setObject:i2n(3) forKey:@"scanType"];
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
[d setObject:i2n(1) forKey:@"recreatePathType"];
[d setObject:b2n(NO) forKey:@"wordWeighting"];
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
[d setObject:b2n(YES) forKey:@"mixFileKind"];
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
[d setObject:b2n(NO) forKey:@"debug"];
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
[d setObject:b2n(NO) forKey:@"scanTagYear"];
[d setObject:[NSArray array] forKey:@"recentDirectories"];
[d setObject:[NSArray array] forKey:@"columnsOrder"];
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
[ud registerDefaults:d];
}
- (id)init
{
self = [super init];
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:4];
[i addIndex:5];
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeIsNotContent"];
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"];
_directoryPanel = nil;
_appName = APPNAME;
return self;
}
- (IBAction)openWebsite:(id)sender
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_me"]];
}
- (IBAction)popupPresets:(id)sender
{
[presetsPopup selectItem: nil];
[[presetsPopup cell] performClickWithFrame:[sender frame] inView:[sender superview]];
}
- (IBAction)toggleDirectories:(id)sender
{
[[self directoryPanel] toggleVisible:sender];
}
- (IBAction)usePreset:(id)sender
{
NSUserDefaultsController *ud = [NSUserDefaultsController sharedUserDefaultsController];
[ud revertToInitialValues:nil];
NSUserDefaults *d = [ud defaults];
switch ([sender tag])
{
case 0:
{
[d setInteger:5 forKey:@"scanType"];
break;
}
//case 1 is defaults
case 2:
{
[d setInteger:2 forKey:@"scanType"];
break;
}
case 3:
{
[d setInteger:0 forKey:@"scanType"];
[d setInteger:50 forKey:@"minMatchPercentage"];
break;
}
case 4:
{
[d setInteger:0 forKey:@"scanType"];
[d setInteger:50 forKey:@"minMatchPercentage"];
[d setBool:YES forKey:@"matchSimilarWords"];
[d setBool:YES forKey:@"wordWeighting"];
break;
}
}
}
- (DirectoryPanel *)directoryPanel
{
if (!_directoryPanel)
_directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self];
return _directoryPanel;
}
- (PyDupeGuru *)py { return (PyDupeGuru *)py; }
//Delegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[ProgressController mainProgressController] setWorker:py];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
//Restore Columns
NSArray *columnsOrder = [ud arrayForKey:@"columnsOrder"];
NSDictionary *columnsWidth = [ud dictionaryForKey:@"columnsWidth"];
if ([columnsOrder count])
[result restoreColumnsPosition:columnsOrder widths:columnsWidth];
//Reg stuff
if ([RegistrationInterface showNagWithApp:[self py] name:APPNAME limitDescription:LIMIT_DESC])
[unlockMenuItem setTitle:@"Thanks for buying dupeGuru ME!"];
//Restore results
[py loadIgnoreList];
[py loadResults];
}
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
if (![[result window] isVisible])
[result showWindow:NSApp];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setObject: [result getColumnsOrder] forKey:@"columnsOrder"];
[ud setObject: [result getColumnsWidth] forKey:@"columnsWidth"];
[py saveIgnoreList];
[py saveResults];
int sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
if (sc >= 10)
{
sc = -1;
[py purgeIgnoreList];
}
sc++;
[ud setInteger:sc forKey:@"sessionCountSinceLastIgnorePurge"];
// NSApplication does not release nib instances objects, we must do it manually
// Well, it isn't needed because the memory is freed anyway (we are quitting the application
// But I need to release RecentDirectories so it saves the user defaults
[recentDirectories release];
}
- (void)recentDirecoryClicked:(NSString *)directory
{
[[self directoryPanel] addDirectory:directory];
}
@end

5
me/cocoa/Consts.h Normal file
View File

@ -0,0 +1,5 @@
#import "dgbase/Consts.h"
#define APPNAME @"dupeGuru ME"
#define jobScanDeadTracks @"jobScanDeadTracks"

13
me/cocoa/DetailsPanel.h Normal file
View File

@ -0,0 +1,13 @@
#import <Cocoa/Cocoa.h>
#import "cocoalib/PyApp.h"
#import "cocoalib/Table.h"
@interface DetailsPanel : NSWindowController
{
IBOutlet TableView *detailsTable;
}
- (id)initWithPy:(PyApp *)aPy;
- (void)refresh;
@end

16
me/cocoa/DetailsPanel.m Normal file
View File

@ -0,0 +1,16 @@
#import "DetailsPanel.h"
@implementation DetailsPanel
- (id)initWithPy:(PyApp *)aPy
{
self = [super initWithWindowNibName:@"Details"];
[self window]; //So the detailsTable is initialized.
[detailsTable setPy:aPy];
return self;
}
- (void)refresh
{
[detailsTable reloadData];
}
@end

View File

@ -0,0 +1,8 @@
#import <Cocoa/Cocoa.h>
#import "dgbase/DirectoryPanel.h"
@interface DirectoryPanel : DirectoryPanelBase
{
}
- (IBAction)addiTunes:(id)sender;
@end

23
me/cocoa/DirectoryPanel.m Normal file
View File

@ -0,0 +1,23 @@
#import "DirectoryPanel.h"
@implementation DirectoryPanel
- (IBAction)addiTunes:(id)sender
{
[self addDirectory:[@"~/Music/iTunes/iTunes Music" stringByExpandingTildeInPath]];
}
- (IBAction)popupAddDirectoryMenu:(id)sender
{
NSMenu *m = [addButtonPopUp menu];
while ([m numberOfItems] > 0)
[m removeItemAtIndex:0];
NSMenuItem *mi = [m addItemWithTitle:@"Add New Directory..." action:@selector(askForDirectory:) keyEquivalent:@""];
[mi setTarget:self];
mi = [m addItemWithTitle:@"Add iTunes Directory" action:@selector(addiTunes:) keyEquivalent:@""];
[mi setTarget:self];
[m addItem:[NSMenuItem separatorItem]];
[_recentDirectories fillMenu:m];
[addButtonPopUp selectItem: nil];
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
}
@end

View File

@ -0,0 +1,18 @@
{
IBClasses = (
{
CLASS = DetailsPanel;
LANGUAGE = ObjC;
OUTLETS = {detailsTable = NSTableView; };
SUPERCLASS = NSWindowController;
},
{CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
{
CLASS = TableView;
LANGUAGE = ObjC;
OUTLETS = {py = PyApp; };
SUPERCLASS = NSTableView;
}
);
IBVersion = 1;
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBDocumentLocation</key>
<string>432 54 356 240 0 0 1024 746 </string>
<key>IBFramework Version</key>
<string>443.0</string>
<key>IBOpenObjects</key>
<array>
<integer>5</integer>
</array>
<key>IBSystem Version</key>
<string>8I127</string>
</dict>
</plist>

Binary file not shown.

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>addiTunes</key>
<string>id</string>
<key>askForDirectory</key>
<string>id</string>
<key>changeDirectoryState</key>
<string>id</string>
<key>popupAddDirectoryMenu</key>
<string>id</string>
<key>removeSelectedDirectory</key>
<string>id</string>
<key>toggleVisible</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>DirectoryPanel</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>addButtonPopUp</key>
<string>NSPopUpButton</string>
<key>directories</key>
<string>NSOutlineView</string>
<key>removeButton</key>
<string>NSButton</string>
</dict>
<key>SUPERCLASS</key>
<string>DirectoryPanelBase</string>
</dict>
<dict>
<key>CLASS</key>
<string>OutlineView</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>py</key>
<string>PyApp</string>
</dict>
<key>SUPERCLASS</key>
<string>NSOutlineView</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>629</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../../dupeguru.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>5</integer>
</array>
<key>IBSystem Version</key>
<string>9B18</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,257 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>NSSegmentedControl</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSControl</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>openWebsite</key>
<string>id</string>
<key>popupPresets</key>
<string>id</string>
<key>toggleDirectories</key>
<string>id</string>
<key>unlockApp</key>
<string>id</string>
<key>usePreset</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>AppDelegate</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>defaultsController</key>
<string>NSUserDefaultsController</string>
<key>presetsButton</key>
<string>NSButton</string>
<key>presetsPopup</key>
<string>NSPopUpButton</string>
<key>py</key>
<string>PyDupeGuru</string>
<key>recentDirectories</key>
<string>RecentDirectories</string>
<key>result</key>
<string>ResultWindow</string>
<key>unlockMenuItem</key>
<string>NSMenuItem</string>
</dict>
<key>SUPERCLASS</key>
<string>AppDelegateBase</string>
</dict>
<dict>
<key>CLASS</key>
<string>PyApp</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>MatchesView</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>OutlineView</string>
</dict>
<dict>
<key>CLASS</key>
<string>PyDupeGuruBase</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>PyApp</string>
</dict>
<dict>
<key>CLASS</key>
<string>PyDupeGuru</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>PyDupeGuruBase</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>changeDelta</key>
<string>id</string>
<key>changePowerMarker</key>
<string>id</string>
<key>clearIgnoreList</key>
<string>id</string>
<key>collapseAll</key>
<string>id</string>
<key>copyMarked</key>
<string>id</string>
<key>deleteMarked</key>
<string>id</string>
<key>expandAll</key>
<string>id</string>
<key>exportToXHTML</key>
<string>id</string>
<key>filter</key>
<string>id</string>
<key>ignoreSelected</key>
<string>id</string>
<key>markAll</key>
<string>id</string>
<key>markInvert</key>
<string>id</string>
<key>markNone</key>
<string>id</string>
<key>markSelected</key>
<string>id</string>
<key>markToggle</key>
<string>id</string>
<key>moveMarked</key>
<string>id</string>
<key>openSelected</key>
<string>id</string>
<key>refresh</key>
<string>id</string>
<key>removeDeadTracks</key>
<string>id</string>
<key>removeMarked</key>
<string>id</string>
<key>removeSelected</key>
<string>id</string>
<key>renameSelected</key>
<string>id</string>
<key>resetColumnsToDefault</key>
<string>id</string>
<key>revealSelected</key>
<string>id</string>
<key>showPreferencesPanel</key>
<string>id</string>
<key>startDuplicateScan</key>
<string>id</string>
<key>switchSelected</key>
<string>id</string>
<key>toggleColumn</key>
<string>id</string>
<key>toggleDelta</key>
<string>id</string>
<key>toggleDetailsPanel</key>
<string>id</string>
<key>togglePowerMarker</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>ResultWindow</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>actionMenu</key>
<string>NSPopUpButton</string>
<key>actionMenuView</key>
<string>NSView</string>
<key>app</key>
<string>id</string>
<key>columnsMenu</key>
<string>NSMenu</string>
<key>deltaSwitch</key>
<string>NSSegmentedControl</string>
<key>deltaSwitchView</key>
<string>NSView</string>
<key>filterField</key>
<string>NSSearchField</string>
<key>filterFieldView</key>
<string>NSView</string>
<key>matches</key>
<string>MatchesView</string>
<key>pmSwitch</key>
<string>NSSegmentedControl</string>
<key>pmSwitchView</key>
<string>NSView</string>
<key>preferencesPanel</key>
<string>NSWindow</string>
<key>py</key>
<string>PyDupeGuru</string>
<key>stats</key>
<string>NSTextField</string>
</dict>
<key>SUPERCLASS</key>
<string>ResultWindowBase</string>
</dict>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>checkForUpdates</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>SUUpdater</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>clearMenu</key>
<string>id</string>
<key>menuClick</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>RecentDirectories</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>delegate</key>
<string>id</string>
<key>menu</key>
<string>NSMenu</string>
</dict>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>ResultWindowBase</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSWindowController</string>
</dict>
<dict>
<key>CLASS</key>
<string>OutlineView</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>py</key>
<string>PyApp</string>
</dict>
<key>SUPERCLASS</key>
<string>NSOutlineView</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>629</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../../dupeguru.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>598</integer>
</array>
<key>IBSystem Version</key>
<string>9E17</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

Binary file not shown.

34
me/cocoa/Info.plist Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleHelpBookFolder</key>
<string>dupeguru_me_help</string>
<key>CFBundleHelpBookName</key>
<string>dupeGuru ME Help</string>
<key>CFBundleIconFile</key>
<string>dupeguru</string>
<key>CFBundleIdentifier</key>
<string>com.hardcoded_software.dupeguru_me</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>hsft</string>
<key>CFBundleVersion</key>
<string>5.6.1</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUFeedURL</key>
<string>http://www.hardcoded.net/updates/dupeguru_me.appcast</string>
</dict>
</plist>

15
me/cocoa/PyDupeGuru.h Normal file
View File

@ -0,0 +1,15 @@
#import <Cocoa/Cocoa.h>
#import "dgbase/PyDupeGuru.h"
@interface PyDupeGuru : PyDupeGuruBase
//Scanning options
- (void)setScanType:(NSNumber *)scan_type;
- (void)setMinWordCount:(NSNumber *)word_count;
- (void)setMinWordLength:(NSNumber *)word_length;
- (void)setWordWeighting:(NSNumber *)words_are_weighted;
- (void)setMatchSimilarWords:(NSNumber *)match_similar_words;
- (void)enable:(NSNumber *)enable scanForTag:(NSString *)tag;
- (void)scanDeadTracks;
- (void)removeDeadTracks;
- (int)deadTrackCount;
@end

56
me/cocoa/ResultWindow.h Normal file
View File

@ -0,0 +1,56 @@
#import <Cocoa/Cocoa.h>
#import "cocoalib/Outline.h"
#import "dgbase/ResultWindow.h"
#import "DetailsPanel.h"
#import "DirectoryPanel.h"
@interface ResultWindow : ResultWindowBase
{
IBOutlet NSPopUpButton *actionMenu;
IBOutlet NSMenu *columnsMenu;
IBOutlet NSSearchField *filterField;
IBOutlet NSSegmentedControl *pmSwitch;
IBOutlet NSWindow *preferencesPanel;
IBOutlet NSTextField *stats;
NSString *_lastAction;
DetailsPanel *_detailsPanel;
NSMutableArray *_resultColumns;
NSMutableIndexSet *_deltaColumns;
}
- (IBAction)changePowerMarker:(id)sender;
- (IBAction)clearIgnoreList:(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)markToggle:(id)sender;
- (IBAction)openSelected:(id)sender;
- (IBAction)refresh:(id)sender;
- (IBAction)removeDeadTracks:(id)sender;
- (IBAction)removeMarked:(id)sender;
- (IBAction)removeSelected:(id)sender;
- (IBAction)renameSelected:(id)sender;
- (IBAction)resetColumnsToDefault:(id)sender;
- (IBAction)revealSelected:(id)sender;
- (IBAction)showPreferencesPanel:(id)sender;
- (IBAction)startDuplicateScan:(id)sender;
- (IBAction)switchSelected:(id)sender;
- (IBAction)toggleColumn:(id)sender;
- (IBAction)toggleDelta:(id)sender;
- (IBAction)toggleDetailsPanel:(id)sender;
- (IBAction)togglePowerMarker:(id)sender;
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn;
- (NSArray *)getColumnsOrder;
- (NSDictionary *)getColumnsWidth;
- (NSArray *)getSelected:(BOOL)aDupesOnly;
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly;
- (void)initResultColumns;
- (void)performPySelection:(NSArray *)aIndexPaths;
- (void)refreshStats;
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
@end

505
me/cocoa/ResultWindow.m Normal file
View File

@ -0,0 +1,505 @@
#import "ResultWindow.h"
#import "cocoalib/Dialogs.h"
#import "cocoalib/ProgressController.h"
#import "cocoalib/RegistrationInterface.h"
#import "cocoalib/Utils.h"
#import "AppDelegate.h"
#import "Consts.h"
@implementation ResultWindow
/* Override */
- (void)awakeFromNib
{
[super awakeFromNib];
_detailsPanel = nil;
_displayDelta = NO;
_powerMode = NO;
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
[_deltaColumns removeIndex:6];
[deltaSwitch setSelectedSegment:0];
[pmSwitch setSelectedSegment:0];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches setTarget:self];
[matches setDoubleAction:@selector(openSelected:)];
[[actionMenu itemAtIndex:0] setImage:[NSImage imageNamed: @"gear"]];
[self initResultColumns];
[self refreshStats];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(duplicateSelectionChanged:) name:DuplicateSelectionChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsChanged:) name:ResultsChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
NSToolbar *t = [[[NSToolbar alloc] initWithIdentifier:@"ResultWindowToolbar"] autorelease];
[t setAllowsUserCustomization:YES];
[t setAutosavesConfiguration:YES];
[t setDisplayMode:NSToolbarDisplayModeIconAndLabel];
[t setDelegate:self];
[[self window] setToolbar:t];
}
/* Overrides */
- (NSString *)logoImageName
{
return @"dgme_logo32";
}
/* Actions */
- (IBAction)changePowerMarker:(id)sender
{
_powerMode = [pmSwitch selectedSegment] == 1;
if (_powerMode)
[matches setTag:2];
else
[matches setTag:0];
[self expandAll:nil];
[self outlineView:matches didClickTableColumn:nil];
}
- (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)exportToXHTML:(id)sender
{
NSString *xsltPath = [[NSBundle mainBundle] pathForResource:@"dg" ofType:@"xsl"];
NSString *cssPath = [[NSBundle mainBundle] pathForResource:@"hardcoded" ofType:@"css"];
NSString *exported = [py exportToXHTMLwithColumns:[self getColumnsOrder] xslt:xsltPath css:cssPath];
[[NSWorkspace sharedWorkspace] openFile:exported];
}
- (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
{
[(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
{
int col = [matches columnWithIdentifier:@"0"];
int row = [matches selectedRow];
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
}
- (IBAction)resetColumnsToDefault:(id)sender
{
NSMutableArray *columnsOrder = [NSMutableArray array];
[columnsOrder addObject:@"0"];
[columnsOrder addObject:@"2"];
[columnsOrder addObject:@"3"];
[columnsOrder addObject:@"4"];
[columnsOrder addObject:@"16"];
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
[columnsWidth setObject:i2n(214) forKey:@"0"];
[columnsWidth setObject:i2n(63) forKey:@"2"];
[columnsWidth setObject:i2n(50) forKey:@"3"];
[columnsWidth setObject:i2n(50) forKey:@"4"];
[columnsWidth setObject:i2n(57) forKey:@"16"];
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
}
- (IBAction)revealSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:NO]];
[py revealSelected];
}
- (IBAction)showPreferencesPanel:(id)sender
{
[preferencesPanel makeKeyAndOrderFront:sender];
}
- (IBAction)startDuplicateScan:(id)sender
{
if ([matches numberOfRows] > 0)
{
if ([Dialogs askYesNo:@"Are you sure you want to start a new duplicate scan?"] == NSAlertSecondButtonReturn) // NO
return;
}
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
PyDupeGuru *_py = (PyDupeGuru *)py;
[_py setScanType:[ud objectForKey:@"scanType"]];
[_py enable:[ud objectForKey:@"scanTagTrack"] scanForTag:@"track"];
[_py enable:[ud objectForKey:@"scanTagArtist"] scanForTag:@"artist"];
[_py enable:[ud objectForKey:@"scanTagAlbum"] scanForTag:@"album"];
[_py enable:[ud objectForKey:@"scanTagTitle"] scanForTag:@"title"];
[_py enable:[ud objectForKey:@"scanTagGenre"] scanForTag:@"genre"];
[_py enable:[ud objectForKey:@"scanTagYear"] scanForTag:@"year"];
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
[_py setWordWeighting:[ud objectForKey:@"wordWeighting"]];
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
int r = n2i([py doScan]);
[matches reloadData];
[self refreshStats];
if (r == 1)
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
if (r == 3)
{
[Dialogs showMessage:@"The selected directories contain no scannable file."];
[app toggleDirectories:nil];
}
}
- (IBAction)switchSelected:(id)sender
{
[self performPySelection:[self getSelectedPaths:YES]];
[py makeSelectedReference];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)toggleColumn:(id)sender
{
NSMenuItem *mi = sender;
NSString *colId = [NSString stringWithFormat:@"%d",[mi tag]];
NSTableColumn *col = [matches tableColumnWithIdentifier:colId];
if (col == nil)
{
//Add Column
col = [_resultColumns objectAtIndex:[mi tag]];
[matches addTableColumn:col];
[mi setState:NSOnState];
}
else
{
//Remove column
[matches removeTableColumn:col];
[mi setState:NSOffState];
}
}
- (IBAction)toggleDelta:(id)sender
{
if ([deltaSwitch selectedSegment] == 1)
[deltaSwitch setSelectedSegment:0];
else
[deltaSwitch setSelectedSegment:1];
[self changeDelta:sender];
}
- (IBAction)toggleDetailsPanel:(id)sender
{
if (!_detailsPanel)
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
if ([[_detailsPanel window] isVisible])
[[_detailsPanel window] close];
else
[[_detailsPanel window] orderFront:nil];
}
- (IBAction)togglePowerMarker:(id)sender
{
if ([pmSwitch selectedSegment] == 1)
[pmSwitch setSelectedSegment:0];
else
[pmSwitch setSelectedSegment:1];
[self changePowerMarker:sender];
}
/* Public */
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn
{
NSNumber *n = [NSNumber numberWithInt:aIdentifier];
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:[n stringValue]];
[col setWidth:aWidth];
[col setEditable:NO];
[[col dataCell] setFont:[[aColumn dataCell] font]];
[[col headerCell] setStringValue:aTitle];
[col setResizingMask:NSTableColumnUserResizingMask];
[col setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:[n stringValue] ascending:YES]];
return col;
}
//Returns an array of identifiers, in order.
- (NSArray *)getColumnsOrder
{
NSTableColumn *col;
NSString *colId;
NSMutableArray *result = [NSMutableArray array];
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
while (col = [e nextObject])
{
colId = [col identifier];
[result addObject:colId];
}
return result;
}
- (NSDictionary *)getColumnsWidth
{
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSTableColumn *col;
NSString *colId;
NSNumber *width;
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
while (col = [e nextObject])
{
colId = [col identifier];
width = [NSNumber numberWithFloat:[col width]];
[result setObject:width forKey:colId];
}
return result;
}
- (NSArray *)getSelected:(BOOL)aDupesOnly
{
if (_powerMode)
aDupesOnly = NO;
NSIndexSet *indexes = [matches selectedRowIndexes];
NSMutableArray *nodeList = [NSMutableArray array];
OVNode *node;
int 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)performPySelection:(NSArray *)aIndexPaths
{
if (_powerMode)
[py selectPowerMarkerNodePaths:aIndexPaths];
else
[py selectResultNodePaths:aIndexPaths];
}
- (void)initResultColumns
{
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
_resultColumns = [[NSMutableArray alloc] init];
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
[_resultColumns addObject:[self getColumnForIdentifier:1 title:@"Directory" width:120 refCol:refCol]];
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"2"]]; // Size
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"3"]]; // Time
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"4"]]; // Bitrate
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Sample Rate" width:60 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Kind" width:40 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Creation" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Modification" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:9 title:@"Title" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:10 title:@"Artist" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:11 title:@"Album" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:12 title:@"Genre" width:80 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Year" width:40 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Track Number" width:40 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Comment" width:120 refCol:refCol]];
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"16"]]; // Match %
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Words Used" width:120 refCol:refCol]];
[_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]];
}
-(void)refreshStats
{
[stats setStringValue:[py getStatLine]];
}
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
{
NSTableColumn *col;
NSString *colId;
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];
}
}
}
/* 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 */
- (void)duplicateSelectionChanged:(NSNotification *)aNotification
{
if (_detailsPanel)
[_detailsPanel refresh];
}
- (void)jobCompleted:(NSNotification *)aNotification
{
[super jobCompleted:aNotification];
id lastAction = [[ProgressController mainProgressController] jobId];
if ([lastAction isEqualTo:jobScanDeadTracks])
{
int deadTrackCount = [(PyDupeGuru *)py deadTrackCount];
if (deadTrackCount > 0)
{
NSString *msg = @"Your iTunes Library contains %d dead tracks ready to be removed. Continue?";
if ([Dialogs askYesNo:[NSString stringWithFormat:msg,deadTrackCount]] == NSAlertFirstButtonReturn)
[(PyDupeGuru *)py removeDeadTracks];
}
else
{
[Dialogs showMessage:@"You have no dead tracks in your iTunes Library"];
}
}
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
[self performPySelection:[self getSelectedPaths:NO]];
[py refreshDetailsWithSelected];
[[NSNotificationCenter defaultCenter] postNotificationName:DuplicateSelectionChangedNotification object:self];
}
- (void)resultsChanged:(NSNotification *)aNotification
{
[matches reloadData];
[self expandAll:nil];
[self outlineViewSelectionDidChange:nil];
[self refreshStats];
}
- (void)resultsMarkingChanged:(NSNotification *)aNotification
{
[matches invalidateMarkings];
[self refreshStats];
}
@end

BIN
me/cocoa/dupeguru.icns Executable file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,563 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 44;
objects = {
/* Begin PBXAppleScriptBuildPhase section */
CE6B288A0AFB7FC900508D93 /* AppleScript */ = {
isa = PBXAppleScriptBuildPhase;
buildActionMask = 2147483647;
contextName = "";
files = (
);
isSharedContext = 0;
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXAppleScriptBuildPhase section */
/* Begin PBXBuildFile section */
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; };
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
CE12149E0AC86DB900E93983 /* dg.xsl in Resources */ = {isa = PBXBuildFile; fileRef = CE12149C0AC86DB900E93983 /* dg.xsl */; };
CE12149F0AC86DB900E93983 /* hardcoded.css in Resources */ = {isa = PBXBuildFile; fileRef = CE12149D0AC86DB900E93983 /* hardcoded.css */; };
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */ = {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 */; };
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
CE3AA46709DB207900DB3A21 /* Directories.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE3AA46509DB207900DB3A21 /* Directories.nib */; };
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.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 */; };
CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */; };
CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */; };
CE515DF90FC6C12E00EC695D /* Table.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEE0FC6C12E00EC695D /* Table.m */; };
CE515DFA0FC6C12E00EC695D /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DF00FC6C12E00EC695D /* Utils.m */; };
CE515DFB0FC6C12E00EC695D /* ValueTransformers.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DF20FC6C12E00EC695D /* ValueTransformers.m */; };
CE515E020FC6C13E00EC695D /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */; };
CE515E030FC6C13E00EC695D /* progress.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE515DFE0FC6C13E00EC695D /* progress.nib */; };
CE515E040FC6C13E00EC695D /* registration.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE515E000FC6C13E00EC695D /* registration.nib */; };
CE515E1D0FC6C19300EC695D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E160FC6C19300EC695D /* AppDelegate.m */; };
CE515E1E0FC6C19300EC695D /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E190FC6C19300EC695D /* DirectoryPanel.m */; };
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E1C0FC6C19300EC695D /* ResultWindow.m */; };
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
CECA899909DB12CA00A3D774 /* Details.nib in Resources */ = {isa = PBXBuildFile; fileRef = CECA899709DB12CA00A3D774 /* Details.nib */; };
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
CED2A6880A05102700AC4C3F /* power_marker32.png in Resources */ = {isa = PBXBuildFile; fileRef = CED2A6870A05102600AC4C3F /* power_marker32.png */; };
CED2A6970A05128900AC4C3F /* dgme_logo32.png in Resources */ = {isa = PBXBuildFile; fileRef = CED2A6960A05128900AC4C3F /* dgme_logo32.png */; };
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
CEF7823809C8AA0200EF38FF /* gear.png in Resources */ = {isa = PBXBuildFile; fileRef = CEF7823709C8AA0200EF38FF /* gear.png */; };
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
CECC02B709A36E8200CC0A94 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */,
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */,
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.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; };
8D1107320486CEB800E47090 /* dupeGuru ME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "dupeGuru ME.app"; sourceTree = BUILT_PRODUCTS_DIR; };
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_me_help; path = help/dupeguru_me_help; sourceTree = "<group>"; };
CE12149C0AC86DB900E93983 /* dg.xsl */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = text.xml; name = dg.xsl; path = w3/dg.xsl; sourceTree = SOURCE_ROOT; };
CE12149D0AC86DB900E93983 /* hardcoded.css */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = text; name = hardcoded.css; path = w3/hardcoded.css; sourceTree = SOURCE_ROOT; };
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; };
CE381C9509914ACE003581CE /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; };
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
CE381C9B09914ADF003581CE /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = SOURCE_ROOT; };
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dg_cocoa.plugin; path = py/dist/dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
CE3AA46609DB207900DB3A21 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Directories.nib; sourceTree = "<group>"; };
CE515DE00FC6C12E00EC695D /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = cocoalib/Dialogs.h; 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; };
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; };
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; };
CE515DE90FC6C12E00EC695D /* RecentDirectories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RecentDirectories.h; path = cocoalib/RecentDirectories.h; sourceTree = SOURCE_ROOT; };
CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RecentDirectories.m; path = cocoalib/RecentDirectories.m; sourceTree = SOURCE_ROOT; };
CE515DEB0FC6C12E00EC695D /* RegistrationInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RegistrationInterface.h; path = cocoalib/RegistrationInterface.h; sourceTree = SOURCE_ROOT; };
CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegistrationInterface.m; path = cocoalib/RegistrationInterface.m; sourceTree = SOURCE_ROOT; };
CE515DED0FC6C12E00EC695D /* Table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Table.h; path = cocoalib/Table.h; sourceTree = SOURCE_ROOT; };
CE515DEE0FC6C12E00EC695D /* Table.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Table.m; path = cocoalib/Table.m; sourceTree = SOURCE_ROOT; };
CE515DEF0FC6C12E00EC695D /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = cocoalib/Utils.h; sourceTree = SOURCE_ROOT; };
CE515DF00FC6C12E00EC695D /* Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Utils.m; path = cocoalib/Utils.m; sourceTree = SOURCE_ROOT; };
CE515DF10FC6C12E00EC695D /* ValueTransformers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ValueTransformers.h; path = cocoalib/ValueTransformers.h; sourceTree = SOURCE_ROOT; };
CE515DF20FC6C12E00EC695D /* ValueTransformers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ValueTransformers.m; path = cocoalib/ValueTransformers.m; sourceTree = SOURCE_ROOT; };
CE515DFD0FC6C13E00EC695D /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = cocoalib/English.lproj/ErrorReportWindow.xib; sourceTree = "<group>"; };
CE515DFF0FC6C13E00EC695D /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = cocoalib/English.lproj/progress.nib; sourceTree = "<group>"; };
CE515E010FC6C13E00EC695D /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = cocoalib/English.lproj/registration.nib; sourceTree = "<group>"; };
CE515E150FC6C19300EC695D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = dgbase/AppDelegate.h; sourceTree = SOURCE_ROOT; };
CE515E160FC6C19300EC695D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = dgbase/AppDelegate.m; sourceTree = SOURCE_ROOT; };
CE515E170FC6C19300EC695D /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Consts.h; path = dgbase/Consts.h; sourceTree = SOURCE_ROOT; };
CE515E180FC6C19300EC695D /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DirectoryPanel.h; path = dgbase/DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
CE515E190FC6C19300EC695D /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryPanel.m; path = dgbase/DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDupeGuru.h; path = dgbase/PyDupeGuru.h; sourceTree = SOURCE_ROOT; };
CE515E1B0FC6C19300EC695D /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = dgbase/ResultWindow.h; sourceTree = SOURCE_ROOT; };
CE515E1C0FC6C19300EC695D /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = dgbase/ResultWindow.m; sourceTree = SOURCE_ROOT; };
CE68EE6509ABC48000971085 /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
CE68EE6609ABC48000971085 /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
CECA899809DB12CA00A3D774 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/Details.nib; 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>"; };
CED2A6870A05102600AC4C3F /* power_marker32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = power_marker32.png; path = images/power_marker32.png; sourceTree = SOURCE_ROOT; };
CED2A6960A05128900AC4C3F /* dgme_logo32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = dgme_logo32.png; path = images/dgme_logo32.png; sourceTree = SOURCE_ROOT; };
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
CEF7823709C8AA0200EF38FF /* gear.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = gear.png; path = images/gear.png; sourceTree = "<group>"; };
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; };
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = images/preferences32.png; sourceTree = SOURCE_ROOT; };
CEFF18A009A4D387005E6321 /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = PyDupeGuru.h; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8D11072E0486CEB800E47090 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
CE381C9509914ACE003581CE /* AppDelegate.h */,
CE381C9409914ACE003581CE /* AppDelegate.m */,
CE848A1809DD85810004CB44 /* Consts.h */,
CECA899A09DB132E00A3D774 /* DetailsPanel.h */,
CECA899B09DB132E00A3D774 /* DetailsPanel.m */,
CE68EE6509ABC48000971085 /* DirectoryPanel.h */,
CE68EE6609ABC48000971085 /* DirectoryPanel.m */,
CEFF18A009A4D387005E6321 /* PyDupeGuru.h */,
CE381C9B09914ADF003581CE /* ResultWindow.h */,
CE381C9A09914ADF003581CE /* ResultWindow.m */,
);
name = Classes;
sourceTree = "<group>";
};
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
CE1425880AFB718500BD5167 /* Sparkle.framework */,
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D1107320486CEB800E47090 /* dupeGuru ME.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* dupeguru */ = {
isa = PBXGroup;
children = (
080E96DDFE201D6D7F000001 /* Classes */,
CE515E140FC6C17900EC695D /* dgbase */,
CE515DDD0FC6C09400EC695D /* cocoalib */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = dupeguru;
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
29B97316FDCFA39411CA2CEA /* main.m */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */,
CE381CF509915304003581CE /* dg_cocoa.plugin */,
CEFC294309C89E0000D9F998 /* images */,
CE12149B0AC86DB900E93983 /* w3 */,
CEEB135109C837A2004D2330 /* dupeguru.icns */,
8D1107310486CEB800E47090 /* Info.plist */,
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
CECA899709DB12CA00A3D774 /* Details.nib */,
CE3AA46509DB207900DB3A21 /* Directories.nib */,
29B97318FDCFA39411CA2CEA /* MainMenu.nib */,
);
name = Resources;
sourceTree = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
CE12149B0AC86DB900E93983 /* w3 */ = {
isa = PBXGroup;
children = (
CE12149C0AC86DB900E93983 /* dg.xsl */,
CE12149D0AC86DB900E93983 /* hardcoded.css */,
);
path = w3;
sourceTree = SOURCE_ROOT;
};
CE515DDD0FC6C09400EC695D /* cocoalib */ = {
isa = PBXGroup;
children = (
CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */,
CE515DFE0FC6C13E00EC695D /* progress.nib */,
CE515E000FC6C13E00EC695D /* registration.nib */,
CE515DE00FC6C12E00EC695D /* Dialogs.h */,
CE515DE10FC6C12E00EC695D /* Dialogs.m */,
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */,
CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */,
CE515DE40FC6C12E00EC695D /* Outline.h */,
CE515DE50FC6C12E00EC695D /* Outline.m */,
CE515DE60FC6C12E00EC695D /* ProgressController.h */,
CE515DE70FC6C12E00EC695D /* ProgressController.m */,
CE515DE80FC6C12E00EC695D /* PyApp.h */,
CE515DE90FC6C12E00EC695D /* RecentDirectories.h */,
CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */,
CE515DEB0FC6C12E00EC695D /* RegistrationInterface.h */,
CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */,
CE515DED0FC6C12E00EC695D /* Table.h */,
CE515DEE0FC6C12E00EC695D /* Table.m */,
CE515DEF0FC6C12E00EC695D /* Utils.h */,
CE515DF00FC6C12E00EC695D /* Utils.m */,
CE515DF10FC6C12E00EC695D /* ValueTransformers.h */,
CE515DF20FC6C12E00EC695D /* ValueTransformers.m */,
);
name = cocoalib;
sourceTree = "<group>";
};
CE515E140FC6C17900EC695D /* dgbase */ = {
isa = PBXGroup;
children = (
CE515E150FC6C19300EC695D /* AppDelegate.h */,
CE515E160FC6C19300EC695D /* AppDelegate.m */,
CE515E170FC6C19300EC695D /* Consts.h */,
CE515E180FC6C19300EC695D /* DirectoryPanel.h */,
CE515E190FC6C19300EC695D /* DirectoryPanel.m */,
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */,
CE515E1B0FC6C19300EC695D /* ResultWindow.h */,
CE515E1C0FC6C19300EC695D /* ResultWindow.m */,
);
name = dgbase;
sourceTree = "<group>";
};
CEFC294309C89E0000D9F998 /* images */ = {
isa = PBXGroup;
children = (
CED2A6960A05128900AC4C3F /* dgme_logo32.png */,
CED2A6870A05102600AC4C3F /* power_marker32.png */,
CEF7823709C8AA0200EF38FF /* gear.png */,
CEFC295309C89FF200D9F998 /* details32.png */,
CEFC295409C89FF200D9F998 /* preferences32.png */,
CEFC294509C89E3D00D9F998 /* folder32.png */,
);
name = images;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8D1107260486CEB800E47090 /* dupeguru */ = {
isa = PBXNativeTarget;
buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */;
buildPhases = (
8D1107290486CEB800E47090 /* Resources */,
8D11072C0486CEB800E47090 /* Sources */,
8D11072E0486CEB800E47090 /* Frameworks */,
CECC02B709A36E8200CC0A94 /* CopyFiles */,
CE6B288A0AFB7FC900508D93 /* AppleScript */,
);
buildRules = (
);
dependencies = (
);
name = dupeguru;
productInstallPath = "$(HOME)/Applications";
productName = dupeguru;
productReference = 8D1107320486CEB800E47090 /* dupeGuru ME.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
compatibilityVersion = "Xcode 3.0";
hasScannedForEncodings = 1;
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
projectDirPath = "";
projectRoot = "";
targets = (
8D1107260486CEB800E47090 /* dupeguru */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8D1107290486CEB800E47090 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */,
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */,
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */,
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */,
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */,
CEFC295509C89FF200D9F998 /* details32.png in Resources */,
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */,
CEF7823809C8AA0200EF38FF /* gear.png in Resources */,
CECA899909DB12CA00A3D774 /* Details.nib in Resources */,
CE3AA46709DB207900DB3A21 /* Directories.nib in Resources */,
CED2A6880A05102700AC4C3F /* power_marker32.png in Resources */,
CED2A6970A05128900AC4C3F /* dgme_logo32.png in Resources */,
CE12149E0AC86DB900E93983 /* dg.xsl in Resources */,
CE12149F0AC86DB900E93983 /* hardcoded.css in Resources */,
CE515E020FC6C13E00EC695D /* ErrorReportWindow.xib in Resources */,
CE515E030FC6C13E00EC695D /* progress.nib in Resources */,
CE515E040FC6C13E00EC695D /* registration.nib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8D11072C0486CEB800E47090 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */,
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */,
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */,
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */,
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */,
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */,
CE515DF50FC6C12E00EC695D /* Outline.m in Sources */,
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */,
CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */,
CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */,
CE515DF90FC6C12E00EC695D /* Table.m in Sources */,
CE515DFA0FC6C12E00EC695D /* Utils.m in Sources */,
CE515DFB0FC6C12E00EC695D /* ValueTransformers.m in Sources */,
CE515E1D0FC6C19300EC695D /* AppDelegate.m in Sources */,
CE515E1E0FC6C19300EC695D /* DirectoryPanel.m in Sources */,
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
089C165DFE840E0CC02AAC07 /* English */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = {
isa = PBXVariantGroup;
children = (
29B97319FDCFA39411CA2CEA /* English */,
);
name = MainMenu.nib;
sourceTree = SOURCE_ROOT;
};
CE3AA46509DB207900DB3A21 /* Directories.nib */ = {
isa = PBXVariantGroup;
children = (
CE3AA46609DB207900DB3A21 /* English */,
);
name = Directories.nib;
sourceTree = "<group>";
};
CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */ = {
isa = PBXVariantGroup;
children = (
CE515DFD0FC6C13E00EC695D /* English */,
);
name = ErrorReportWindow.xib;
sourceTree = SOURCE_ROOT;
};
CE515DFE0FC6C13E00EC695D /* progress.nib */ = {
isa = PBXVariantGroup;
children = (
CE515DFF0FC6C13E00EC695D /* English */,
);
name = progress.nib;
sourceTree = SOURCE_ROOT;
};
CE515E000FC6C13E00EC695D /* registration.nib */ = {
isa = PBXVariantGroup;
children = (
CE515E010FC6C13E00EC695D /* English */,
);
name = registration.nib;
sourceTree = SOURCE_ROOT;
};
CECA899709DB12CA00A3D774 /* Details.nib */ = {
isa = PBXVariantGroup;
children = (
CECA899809DB12CA00A3D774 /* English */,
);
name = Details.nib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C01FCF4B08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COPY_PHASE_STRIP = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(FRAMEWORK_SEARCH_PATHS)",
"$(SRCROOT)/../../../cocoalib/build/Release",
"\"$(SRCROOT)/../../base/cocoa/build/Release\"",
);
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_FIX_AND_CONTINUE = YES;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = dupeGuru;
WRAPPER_EXTENSION = app;
ZERO_LINK = YES;
};
name = Debug;
};
C01FCF4C08A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = (
"$(FRAMEWORK_SEARCH_PATHS)",
"$(SRCROOT)/../../../cocoalib/build/Release",
"\"$(SRCROOT)/../../base/cocoa/build/Release\"",
);
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
GCC_MODEL_TUNING = G5;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = "dupeGuru ME";
WRAPPER_EXTENSION = app;
};
name = Release;
};
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_C_LANGUAGE_STANDARD = c99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.4;
PREBINDING = NO;
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
};
name = Debug;
};
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
i386,
ppc,
);
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = c99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.4;
PREBINDING = NO;
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk";
STRIP_INSTALLED_PRODUCT = NO;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4B08A954540054247B /* Debug */,
C01FCF4C08A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}

14
me/cocoa/gen.py Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env python
import os
print "Generating help"
os.chdir('help')
os.system('python -u gen.py')
os.system('/Developer/Applications/Utilities/Help\\ Indexer.app/Contents/MacOS/Help\\ Indexer dupeguru_me_help')
os.chdir('..')
print "Generating py plugin"
os.chdir('py')
os.system('python -u gen.py')
os.chdir('..')

21
me/cocoa/main.m Normal file
View File

@ -0,0 +1,21 @@
//
// main.m
// dupeguru
//
// Created by Virgil Dupras on 2006/02/01.
// Copyright __MyCompanyName__ 2006. All rights reserved.
//
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *pluginPath = [[NSBundle mainBundle]
pathForResource:@"dg_cocoa"
ofType:@"plugin"];
NSBundle *pluginBundle = [NSBundle bundleWithPath:pluginPath];
[pluginBundle load];
[pool release];
return NSApplicationMain(argc, (const char **) argv);
}

225
me/cocoa/py/dg_cocoa.py Normal file
View File

@ -0,0 +1,225 @@
#!/usr/bin/env python
import objc
from AppKit import *
from dupeguru import app_me_cocoa, scanner
# Fix py2app imports which chokes on relative imports
from dupeguru import app, app_cocoa, data, directories, engine, export, ignore, results, scanner
from hsfs import auto, manual, stats, tree, utils, music
from hsfs.phys import music
from hsmedia import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
class PyApp(NSObject):
pass #fake class
class PyDupeGuru(PyApp):
def init(self):
self = super(PyDupeGuru,self).init()
self.app = app_me_cocoa.DupeGuruME()
return self
#---Directories
def addDirectory_(self,directory):
return self.app.AddDirectory(directory)
def removeDirectory_(self,index):
self.app.RemoveDirectory(index)
def setDirectory_state_(self,node_path,state):
self.app.SetDirectoryState(node_path,state)
#---Results
def clearIgnoreList(self):
self.app.scanner.ignore_list.Clear()
def doScan(self):
return self.app.start_scanning()
def exportToXHTMLwithColumns_xslt_css_(self,column_ids,xslt_path,css_path):
return self.app.ExportToXHTML(column_ids,xslt_path,css_path)
def loadIgnoreList(self):
self.app.LoadIgnoreList()
def loadResults(self):
self.app.load()
def markAll(self):
self.app.results.mark_all()
def markNone(self):
self.app.results.mark_none()
def markInvert(self):
self.app.results.mark_invert()
def purgeIgnoreList(self):
self.app.PurgeIgnoreList()
def toggleSelectedMark(self):
self.app.ToggleSelectedMarkState()
def saveIgnoreList(self):
self.app.SaveIgnoreList()
def saveResults(self):
self.app.Save()
def refreshDetailsWithSelected(self):
self.app.RefreshDetailsWithSelected()
def selectResultNodePaths_(self,node_paths):
self.app.SelectResultNodePaths(node_paths)
def selectPowerMarkerNodePaths_(self,node_paths):
self.app.SelectPowerMarkerNodePaths(node_paths)
#---Actions
def addSelectedToIgnoreList(self):
self.app.AddSelectedToIgnoreList()
def applyFilter_(self, filter):
self.app.ApplyFilter(filter)
def deleteMarked(self):
self.app.delete_marked()
def makeSelectedReference(self):
self.app.MakeSelectedReference()
def copyOrMove_markedTo_recreatePath_(self,copy,destination,recreate_path):
self.app.copy_or_move_marked(copy, destination, recreate_path)
def openSelected(self):
self.app.OpenSelected()
def removeDeadTracks(self):
self.app.remove_dead_tracks()
def removeMarked(self):
self.app.results.perform_on_marked(lambda x:True, True)
def removeSelected(self):
self.app.RemoveSelected()
def renameSelected_(self,newname):
return self.app.RenameSelected(newname)
def revealSelected(self):
self.app.RevealSelected()
def scanDeadTracks(self):
self.app.scan_dead_tracks()
#---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
@objc.signature('i@:')
def deadTrackCount(self):
return len(self.app.dead_tracks)
def getIgnoreListCount(self):
return len(self.app.scanner.ignore_list)
def getMarkCount(self):
return self.app.results.mark_count
def getStatLine(self):
return self.app.stat_line
def getOperationalErrorCount(self):
return self.app.last_op_error_count
#---Data
@objc.signature('i@:i')
def getOutlineViewMaxLevel_(self, tag):
return self.app.GetOutlineViewMaxLevel(tag)
@objc.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
def setMinMatchPercentage_(self, percentage):
self.app.scanner.min_match_percentage = int(percentage)
def setScanType_(self, scan_type):
try:
self.app.scanner.scan_type = [
scanner.SCAN_TYPE_FILENAME,
scanner.SCAN_TYPE_FIELDS,
scanner.SCAN_TYPE_FIELDS_NO_ORDER,
scanner.SCAN_TYPE_TAG,
scanner.SCAN_TYPE_CONTENT,
scanner.SCAN_TYPE_CONTENT_AUDIO
][scan_type]
except IndexError:
pass
def setWordWeighting_(self, words_are_weighted):
self.app.scanner.word_weighting = words_are_weighted
def setMixFileKind_(self, mix_file_kind):
self.app.scanner.mix_file_kind = mix_file_kind
def setDisplayDeltaValues_(self, display_delta_values):
self.app.display_delta_values = display_delta_values
def setMatchSimilarWords_(self, match_similar_words):
self.app.scanner.match_similar_words = match_similar_words
def setEscapeFilterRegexp_(self, escape_filter_regexp):
self.app.options['escape_filter_regexp'] = escape_filter_regexp
def setRemoveEmptyFolders_(self, remove_empty_folders):
self.app.options['clean_empty_dirs'] = remove_empty_folders
def enable_scanForTag_(self, enable, scan_tag):
if enable:
self.app.scanner.scanned_tags.add(scan_tag)
else:
self.app.scanner.scanned_tags.discard(scan_tag)
#---Worker
def getJobProgress(self):
return self.app.progress.last_progress
def getJobDesc(self):
return self.app.progress.last_desc
def cancelJob(self):
self.app.progress.job_cancelled = True
#---Registration
@objc.signature('i@:')
def isRegistered(self):
return self.app.registered
@objc.signature('i@:@@')
def isCodeValid_withEmail_(self, code, email):
return self.app.is_code_valid(code, email)
def setRegisteredCode_andEmail_(self, code, email):
self.app.set_registration(code, email)

18
me/cocoa/py/gen.py Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
import os
import os.path as op
import shutil
from hsutil.build import print_and_do
os.chdir('dupeguru')
print_and_do('python gen.py')
os.chdir('..')
if op.exists('build'):
shutil.rmtree('build')
if op.exists('dist'):
shutil.rmtree('dist')
print_and_do('python -u setup.py py2app')

14
me/cocoa/py/setup.py Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env python
from distutils.core import setup
import py2app
from hsutil.build import move_testdata_out, put_testdata_back
move_log = move_testdata_out()
try:
setup(
plugin = ['dg_cocoa.py'],
)
finally:
put_testdata_back(move_log)

75
me/cocoa/w3/dg.xsl Normal file
View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
method="xml"
encoding="utf-8"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
indent="yes"/>
<xsl:template match="column">
<xsl:if test="@enabled = 'y'">
<th>
<xsl:value-of select="@display"/>
</th>
</xsl:if>
</xsl:template>
<xsl:template match="file">
<tr>
<xsl:variable name="td_class">
<xsl:if test="position() > 1">
<xsl:text>indented</xsl:text>
</xsl:if>
</xsl:variable>
<xsl:variable name="file_node" select="."/>
<xsl:for-each select="data">
<xsl:variable name="data_pos" select="position()"/>
<xsl:if test="document('columns.xml')/columns/column[$data_pos]/@enabled = 'y'">
<td>
<xsl:if test="position() = 1">
<xsl:attribute name="class">
<xsl:value-of select="$td_class"/>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@value"/>
</td>
</xsl:if>
</xsl:for-each>
<!-- <xsl:for-each select="//results/column">
<td>
<xsl:variable name="attr_name">
<xsl:text>attr_</xsl:text>
<xsl:value-of select="@name"/>
</xsl:variable>
<xsl:value-of select="$file_node/@*[local-name(.) = $attr_name]"/>
</td>
</xsl:for-each> -->
</tr>
</xsl:template>
<xsl:template match="group">
<xsl:apply-templates select="file"/>
</xsl:template>
<xsl:template match="results">
<html>
<head>
<title>dupeGuru Results</title>
<link rel="stylesheet" href="hardcoded.css" type="text/css"/>
</head>
<body>
<h1>dupeGuru Results</h1>
<table>
<tr>
<xsl:apply-templates select="document('columns.xml')/columns/column"/>
</tr>
<xsl:apply-templates select="group"/>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

71
me/cocoa/w3/hardcoded.css Normal file
View File

@ -0,0 +1,71 @@
BODY
{
background-color:white;
}
BODY,A,P,UL,TABLE,TR,TD
{
font-family:Tahoma,Arial,sans-serif;
font-size:10pt;
color: #4477AA;
}
TABLE
{
background-color: #225588;
margin-left: auto;
margin-right: auto;
width: 90%;
}
TR
{
background-color: white;
}
TH
{
font-weight: bold;
color: black;
background-color: #C8D6E5;
}
TH TD
{
color:black;
}
TD
{
padding-left: 2pt;
}
TD.rightelem
{
text-align:right;
/*padding-left:0pt;*/
padding-right: 2pt;
width: 17%;
}
TD.indented
{
padding-left: 12pt;
}
H1
{
font-family:"Courier New",monospace;
color:#6699CC;
font-size:18pt;
color:#6da500;
border-color: #70A0CF;
border-width: 1pt;
border-style: solid;
margin-top: 16pt;
margin-left: 5%;
margin-right: 5%;
padding-top: 2pt;
padding-bottom:2pt;
text-align: center;
}

542
me/help/changelog.yaml Normal file
View File

@ -0,0 +1,542 @@
- date: 2009-05-30
version: 5.6.1
description: |
* Fixed a bug causing a GUI freeze at the beginning of a scan with a lot of files.
* Fixed a bug that sometimes caused a crash when an action was cancelled, and then started again.
- date: 2009-05-23
version: 5.6.0
description: |
* Converted the Windows GUI to Qt.
* Improved the reliability of the scanning process.
- date: 2009-03-28
version: 5.5.2
description: |
* **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-28
version: 5.5.1
description: |
* **Improved** support for AIFF files.
* **Improved** Remove Dead Tracks in iTunes for very large library (Mac OS X).
- date: 2008-09-10
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> support for AIFF files.</li>\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: 5.5.0
- date: 2008-08-07
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> the \"Remove Dead Tracks in\
\ iTunes\" feature.</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: 5.4.3
- date: 2008-06-20
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> unicode handling for filenames\
\ and tags. dupeGuru ME will now find a lot more duplicates if your files have\
\ non-ascii characters in it.</li>\n\t\t\t\t\t\t<li><b>Improved</b> MPEG files\
\ duration detection.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> \"Clear Ignore List\"\
\ crash in Windows.</li>\n\t\t </ul>"
version: 5.4.2
- date: 2008-01-15
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 \
\ </ul>"
version: 5.4.1
- date: 2007-12-06
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> customizable tag scans.</li>\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 \
\ </ul>"
version: 5.4.0
- date: 2007-11-26
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: 5.3.2
- date: 2007-08-12
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a crash with copy and move.</li>\n\
\t\t </ul>"
version: 5.3.1
- date: 2007-07-01
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> a small issue with AAC decoding.</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: 5.3.0
- date: 2007-03-31
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a crash sometimes happening while\
\ loading results.</li>\n\t\t </ul>"
version: 5.2.7
- date: 2007-03-25
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\t\t\t\t<li><b>Fixed</b> a \"bad file descriptor\"\
\ error occasionally popping up.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with\
\ non-latin directory names.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> a column mixup\
\ under Windows. The Artist column couldn't be shown.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ a bug causing the sorting under Power Marker mode not to work under Mac OS X.</li>\n\
\t\t </ul>"
version: 5.2.6
- date: 2007-02-14
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 5.2.0 (Windows).</li>\n\
\t\t\t\t\t\t<li><b>Changed</b> the behavior of the scanning engine when setting\
\ the hardness to 100. It will now only match files that have their words in the\
\ same order.</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: 5.2.5
- date: 2007-01-10
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug with the Move action.</li>\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>\
\ a bug introduced in the last version that caused the status bar not to update\
\ when dupes were marked (Windows).</li>\n\t\t </ul>"
version: 5.2.4
- date: 2007-01-04
description: "<ul>\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: 5.2.3
- date: 2006-12-21
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> Id3v2.4 tags decoding to support\
\ some malformed tags that iTunes sometimes produce.</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: 5.2.2
- 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: 5.2.1
- date: 2006-11-17
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>Removed</b>\
\ the min word length/count options. These came from Mp3 Filter, and just aren't\
\ used anymore. Word weighting does pretty much the same job.</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: 5.2.0
- date: 2006-11-03
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\t\t\t<li><b>Added</b> a \"Remove Dead\
\ Tracks in iTunes\" feature in the Mac OS X version.</li>\n\t\t\t\t\t\t<li><b>Improved</b>\
\ speed and memory usage of the scanning engine, especially when the scan results\
\ in a lot of duplicates.</li>\n\t\t\t\t\t\t<li><b>Improved</b> VBR mp3 support.</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> a bug preventing some duplicate reports to be created\
\ correctly under Windows.</li>\n\t\t </ul>"
version: 5.1.2
- date: 2006-09-29
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> a bug (no, not the same as in 5.1.0)\
\ preventing some duplicates to be found, especially in huge collections.</li>\n\
\t\t </ul>"
version: 5.1.1
- date: 2006-09-26
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> XHTML export feature.</li>\n\t\t\
\t\t\t\t<li><b>Fixed</b> a bug preventing some duplicates to be found when using\
\ the \"Filename - Fields (No Order)\" scan method.</li>\n\t\t </ul>"
version: 5.1.0
- date: 2006-08-30
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> sticky columns.</li>\n\t\t\t\t\t\
\t<li><b>Fixed</b> an issue with file caching between scans.</li>\n\t\t\t\t\t\t\
<li><b>Fixed</b> an issue preventing some duplicates from being deleted/moved/copied.</li>\n\
\t\t </ul>"
version: 5.0.11
- date: 2006-08-27
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> an issue with ignore list and unicode.</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> an issue with file attribute fetching sometimes causing\
\ dupeGuru ME to crash.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> an issue in the directories\
\ panel under Windows.</li>\n\t\t </ul>"
version: 5.0.10
- date: 2006-08-17
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> an issue in the duplicate seeking\
\ engine preventing some duplicates to be found.</li>\n\t\t\t\t\t\t<li>(Yeah,\
\ I'm in a bug fixing frenzy right now :) )</li>\n\t\t </ul>"
version: 5.0.9
- date: 2006-08-16
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> an issue with the new track column\
\ occasionally causing crash.</li>\n\t\t\t\t\t\t<li><b>Fixed</b> an issue with\
\ the handling of corrupted files that occasionally caused crash.</li>\n\t\t \
\ </ul>"
version: 5.0.8
- date: 2006-08-12
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> unicode support.</li>\n\t\t\t\
\t\t\t<li><b>Improved</b> the \"Reveal in Finder\" (\"Open Containing Folder\"\
\ in Windows) feature so it selects the file in the folder it opens.</li>\n\t\t\
\ </ul>"
version: 5.0.7
- date: 2006-08-08
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> the the Track Number detail column.</li>\n\
\t\t\t\t\t\t<li><b>Improved</b> the ignore list system.</li>\n\t\t\t\t\t\t<li><b>Fixed</b>\
\ a bug in the mp3 metadata decoding unit.</li>\n\t\t\t\t\t\t<li>dupeGuru Music\
\ Edition is now a Universal application on Mac OS X.</li>\n\t\t </ul>"
version: 5.0.6
- date: 2006-07-28
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> VBR mp3 metadata decoding.</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> an issue that occasionally made dupeGuru ME crash\
\ on startup.</li>\n\t\t </ul>"
version: 5.0.5
- date: 2006-06-26
description: "<ul>\n\t\t\t\t\t\t<li><b>Fixed</b> an issue with Move and Copy features.</li>\n\
\t\t </ul>"
version: 5.0.4
- date: 2006-06-17
description: "<ul>\n\t\t\t\t\t\t<li><b>Improved</b> duplicate scanning speed.</li>\n\
\t\t\t\t\t\t<li><b>Added</b> a warning that a file couldn't be renamed if a file\
\ with the same name already exists.</li>\n\t\t </ul>"
version: 5.0.3
- date: 2006-06-06
description: "<ul>\n\t\t\t\t\t\t<li><b>Added</b> \"Rename Selected\" feature.</li>\n\
\t\t \t<li><b>Improved</b> MP3 metadata decoding.</li>\n\t\t\t\t\t\t\
<li><b>Fixed</b> some minor issues with \"Reload Last Results\" feature.</li>\n\
\t\t\t\t\t\t<li><b>Fixed</b> ignore list issues.</li>\n\t\t </ul>"
version: 5.0.2
- date: 2006-05-26
description: "<ul>\n\t\t \t<li><b>Fixed</b> occasional progress bar woes\
\ under Windows.</li>\n\t\t\t\t\t\t<li>Nothing has been changed in the Mac OS\
\ X version, but I want to keep version in sync.</li>\n\t\t </ul>"
version: 5.0.1
- date: 2006-05-19
description: "<ul>\n\t\t <li>Complete rewrite</li>\n\t\t\t\t\t\t\
<li>Changed \"Mp3 Filter\" name to \"dupeGuru Music Edition\"</li>\n\t\t\t\t\t\
\t<li>Now runs on Mac OS X.</li>\n\t\t </ul>"
version: 5.0.0
- date: 2006-04-13
description: "<ul>\n\t\t <li><b>*fixed*</b> a critical bug introduced\
\ in 4.2.5: Files couldn't be deleted anymore!</li>\n\t\t <li><b>*fixed*</b>\
\ some more issues with WMA decoding.</li>\n\t\t <li><b>*fixed*</b>\
\ an issue with profile wizard.</li>\n\t\t </ul>"
version: 4.2.6
- date: 2006-04-11
description: "<ul>\n\t\t <li><b>*added*</b> a test zone in the Exclusions\
\ profile section.</li>\n\t\t <li><b>*fixed*</b> a bug with exclusion\
\ patterns.</li>\n\t\t <li><b>*fixed*</b> an issue occuring when\
\ reading some kinds of WMA files.</li>\n\t\t </ul>"
version: 4.2.5
- date: 2006-02-16
description: "<ul>\n\t\t <li><b>*fixed*</b> MPL occasional issues\
\ when saving.</li>\n\t\t <li><b>*fixed*</b> m4p (protected AAC\
\ files) bitrate reading.</li>\n\t\t </ul>"
version: 4.2.4
- date: 2005-10-15
description: "<ul>\n\t\t <li><b>*improved*</b> Added the \"Add Custom\
\ Extension\" button in the File Priority section of the profile editor.</li>\n\
\t\t </ul>"
version: 4.2.3
- date: 2005-10-07
description: "<ul>\n\t\t <li><b>*improved*</b> Results management\
\ by adding the possibility to remove selected (not only checked) duplicates from\
\ the list.</li>\n\t\t <li><b>*fixed*</b> An issue with the \"\
Switch with reference\" feature.</li>\n\t\t <li><b>*fixed*</b>\
\ A stability issue with the result pane.</li>\n\t\t </ul>"
version: 4.2.2
- date: 2005-09-06
description: "<ul>\n\t\t <li><b>*fixed*</b> A little bug with M4A/M4P\
\ support.</li>\n\t\t </ul>"
version: 4.2.1
- date: 2005-08-30
description: "<ul>\n\t\t <li><b>*added*</b> M4A/M4P (iTunes format)\
\ support.</li>\n\t\t <li><b>*added*</b> \"Field order doesn't\
\ matter\" option in Comparison Options.</li>\n\t\t <li><b>*added*</b>\
\ A \"Open directory containing this file\" option in the result window's context\
\ menu.</li>\n\t\t \t<li><b>*fixed*</b> Some bugs with the \"Load last\
\ results\" function.</li>\n\t\t </ul>"
version: 4.2.0
- date: 2005-03-22
description: "<ul>\n\t\t \t<li><b>*fixed*</b> Nasty bug in the wizard\
\ system.</li>\n\t\t \t<li><b>*fixed*</b> Yet another nasty bug in\
\ the Move/Copy option of the result pane.</li>\n\t\t </ul>"
version: 4.1.5
- date: 2004-11-10
description: "<ul>\n\t\t \t<li><b>*added*</b> \"Load last results\" function.</li>\n\
\t\t \t<li><b>*added*</b> Customizable columns in the results window.</li>\n\
\t\t \t<li><b>*fixed*</b> A bug related to special characters in the\
\ XML profiles.</li>\n\t\t \t<li><b>*fixed*</b> The result window scroll\
\ didn't move properly on \"Switch with ref.\".</li>\n\t\t \t<li><b>*fixed*</b>\
\ A bug with the WMA plugin.</li>\n\t\t </ul>"
version: 4.1.4
- date: 2004-10-30
description: "<ul>\n\t\t \t<li><b>*added*</b> Profile summary in the\
\ main window.</li>\n\t\t \t<li><b>*added*</b> An (Artist + title)\
\ ID3 tag comparison type.</li>\n\t\t \t<li><b>*improved*</b> The profile\
\ system by making it XML based.</li>\n\t\t </ul>"
version: 4.1.3
- date: 2004-09-28
description: "<ul>\n\t\t \t<li><b>*improved*</b> Changed the ID3 tag\
\ comparison from (Artist + Title) to (Artist + Title + Album).</li>\n\t\t \
\ </ul>"
version: 4.1.2
- date: 2004-09-22
description: "<ul>\n\t\t \t<li><b>*fixed*</b> A couple of bugs.</li>\n\
\t\t </ul>"
version: 4.1.1
- date: 2004-08-28
description: "<ul>\n\t\t \t<li><b>*added*</b> A \"special selection\"\
\ wizard in the results window.</li>\n\t\t \t<li><b>*improved*</b>\
\ Changed the File content comparison system.</li>\n\t\t \t<li><b>*fixed*</b>\
\ A sorting bug in the directory tree displays</li>\n\t\t </ul>"
version: 4.1.0
- date: 2004-08-10
description: "<ul>\n\t\t \t<li><b>*improved*</b> Redesigned the configuration\
\ wizard (again!).</li>\n\t\t </ul>"
version: 4.0.6
- date: 2004-07-23
description: "<ul>\n\t\t \t<li><b>*improved*</b> Redesigned the profile\
\ directory frame.</li>\n\t\t <li><b>*fixed*</b> A quite big bug\
\ with file priority system.</li>\n\t\t <li><b>*fixed*</b> A bug\
\ with offline registration.</li>\n\t\t </ul>"
version: 4.0.5
- date: 2004-07-15
description: "<ul>\n\t\t <li><b>*fixed*</b> A couple of minor bugs\
\ with profile directories/priorities.</li>\n\t\t <li><b>*improved*</b>\
\ Reduced, thus clarified, most of the text in the profile wizard.</li>\n\t\t\
\ </ul>"
version: 4.0.4
- date: 2004-07-12
description: "<ul>\n\t\t <li><b>*fixed*</b> An issue with \"Similar\
\ word threshold\" setting, and boosted it's performance.</li>\n\t\t \
\ <li><b>*fixed*</b> Some issues with the registering system.</li>\n\t\t\
\ </ul>"
version: 4.0.3
- date: 2004-07-10
description: "<ul>\n\t\t <li><b>*fixed*</b> A couple of obscure bugs.</li>\n\
\t\t <li><b>*improved*</b> Changed a couple of minor things in\
\ this help file.</li>\n\t\t </ul>"
version: 4.0.2
- date: 2004-07-07
description: "<ul>\n\t\t <li><b>*fixed*</b> A couple of issues with\
\ the configuration wizard.</li>\n\t\t <li><b>*fixed*</b> A bug\
\ with the View Details button when not using WinXP.</li>\n\t\t </ul>"
version: 4.0.1
- date: 2004-07-05
description: Mp3 Filter has been rebuilt from scratch for this version. It features
a completely new interface, a profile system and a redesigned configuration wizard.
version: 4.0.0
- date: 2002-12-31
description: I never made a history entry for this version, although it has been
the version that went without changes for the most time (1 year and a half). I
also lost track of when I made it, but a quick fix (3.20.0.5) has been made on
2002/12/31.
version: '3.20'
- date: 2002-08-14
description: Enhanced the Mp3 List system with locking and improved searching.
version: '3.16'
- date: 2002-08-13
description: Added Wizard, tips and installation program.
version: '3.15'
- date: 2002-08-12
description: Added funny animation plugin and Windows Explorer shell extension.
version: '3.14'
- date: 2002-08-11
description: Minor bugfixes + changed the Edit tag interface.
version: '3.12'
- date: 2002-08-10
description: Added Import list feature + first 5kb of the files comparison.
version: '3.11'
- date: 2002-07-26
description: Added extension plugins.
version: '3.10'
- date: 2002-01-30
description: 'Fixed the ID3 Tag editor a bit. Changed the way comparison works:
it now can use ID3 Tags.'
version: '3.01'
- date: 2002-01-29
description: The interface simply has been C-O-M-P-L-E-T-E-L-Y redesigned. Customization
level is at it's maximum, too cool.
version: '3.00'
- date: 2002-01-28
description: Added some speed ONCE AGAIN, improved the memory management and added
a "favourite directories" feature. I also removed some confusing options. The
final result is quite cute!
version: '2.21'
- date: 2002-01-27
description: Interface has been COMPLETELY rebuilt. Now there are MUCH more place
for everything! Several minor bugs has also been fixed Added mass ID3 tag editing.
version: '2.20'
- date: 2001-12-02
description: Shareware again. Fixed some major bugs. Rebuilt (again) the mp3 list
system, it's now much more flexible. Added a configuration wizard. Added a renameing
preview. Well, it's a good update after all eh!
version: '2.10'
- date: 2001-12-01
description: Added multi-language support. Added a "Send to recycle bin" option.
Enhanced rename feature. Corrected some bugs with rename function. Enhanced list
search function.
version: '2.01'
- date: 2001-11-30
description: "As 11 Sept 2001 entered in the History, the release date of this program\
\ will too! Ok, here is the list of Mp3 Filter version 2.00 godly features:\r\n\
\r\n* **SPEED!!!!!!!!!!!!** Forget about what I said before. Previous versions\
\ were TURTLES compared to that one. (Imagine what other programs are eh! :P).\
\ What took 1 minute take 3-5 seconds now, and the more files you have to compare\
\ together, the better will be the files/time ratio will be!\r\n* Multi-list system.\
\ It is now easier than ever to exchange lists with your friends and select songs!\r\
\n* Cuter interface."
version: '2.00'
- date: 2001-06-29
description: There was some stability issues with the internal player I was using.
Mp3 Filter is now using Winamp. Thus, all files playable by Winamp are now playable
by Mp3 Filter. Fixed some minor bugs. Changed the way word exclusion system work.
AND added a song selection system. Now you can select songs from your mp3 list
and copy them to your hard drive without having to worry about where are these
songs.
version: '1.61'
- date: 2001-06-28
description: 'The main theme of this update is efficiency. Mp3 Filter v1.53 was
already pure speed, you will NOT believe this version''s one. 60% faster on ALL
comparisons! Do not search for God anymore, you found Him and He even got an e-mail
adress: cathedly@hotmail.com :P. Ok, to tell you the truth I did not make Mp3
Filter 60 % faster, I made it 60% less slow. My previous algorithm wasn''t bad,
but I thought about another one (this one) that has much better performances.
ALSO: Created an option form. Changed the results display (Added some info along
with the results (size,length,bitrate). Added a word excluding system. Also added
a backup system (Instead of deleting it, you can now move your file to the Mp3
Filter backup directory (Mp3 filter does not compare files in the backup directory).'
version: '1.60'
- date: 2001-06-27
description: Damnit, big update. Added the conditional file searching, file copying,
and rethought the Mp3List system. That new Mp3List system is damn cool! It load
instantly, even with HUGE lists, and it reduces the comparing time with list by
30 godly % !!! You're not gonna believe it! This program is now PURE SPEED!
version: '1.53'
- date: 2001-05-05
description: Quite cool update too. This version now can check if new versions are
available. I also grouped all options in the same menu. I moved the search function.
This function is now a lot cooler. Instead of giving you a list of matching results,
it shows you, in the Mp3 List Stats form, where the song is by positioning itself
in the List Tree.
version: '1.52'
- date: 2001-05-04
description: Waa! I'm so happy! I implemented a poll system to Mp3 Filter! Now you
can answer my questions directly on the program! I can't wait to see if you, people,
will answer!
version: '1.51'
- date: 2001-05-03
description: 'MAJOR UPDATE. This one is quite cool :). You ever used the "Edit Mp3
List" feature? I improved it a lot. Now, when you add a CD to your list, it not
only saves the CD name, but it also saves the whole CD directory system. So when
you use ''Edit Mp3 List'' now, you can browse your CDs as if you would browse
anything. (There''s only one problem: you must REbuild your mp3 list to make it
fit with v1.5)'
version: '1.50'
- date: 2001-05-02
description: Added an equalizer. This equalizer has been a good reason to add an
INI file to the program to store changed parameters.
version: '1.46'
- date: 2001-05-01
description: Added a Banlist to the program. You write down a list of unwanted songs
in your ban list and start a scan. This function will not compare as the rest
does. If ALL words contained in a banlist line are in a filename, it will match.
version: '1.45'
- date: 2001-04-30
description: I made the Mp3 Filter window to minimize when it compares so it can
do it faster (a LOT faster). I also modified the program so it checks the playlist
integrity each time there's a file deleted after a comparison). There was a bug
with the v1.43. When you had the ID3 Tag window up and you closed the program,
it would crash. Fixed that.
version: '1.44'
- date: 2001-04-29
description: I noticed some days ago that people who had a good resolution but the
option to enlarge the icons on, Mp3 Filter had some big problems to display its
main form right. Since you cant resize the form without having the objects in
to resize too, I had to fix it. I also implemented a MUCH faster file searching
system. It takes less than 2 seconds to find all mp3 on my hard disk now.
version: '1.43'
- date: 2001-04-09
description: Added some fun and useful feats. First, I made a cute playlist right-click
menu with Play File, Edit Tag, Locate, Remove from list and delete from disk.
I also added a recursive function to add songs to the playlist (Why didn't I think
about it before?? I have no clue...). I also made the Shuffle thing less.... random.
(It builds a random list and play it, so before a song play again, all songs will
be played (I added that feat some time after v1.41 release, but I didn't thought
that it worth a version change, so I only announce it on 1.42))
version: '1.42'
- date: 2001-04-08
description: I can't avoid it. there is always some bugs after a major update. I
didn't thought about the fact that it was possible to make a playlist with unplayable
files :) fixed that.
version: '1.41'
- date: 2001-04-07
description: MAJOR UPDATE! You wanted a playlist. You got it in this version. Those
big buttons were ugly? Made a cute standard menu. You didn't seem to want to buy
that program. I gave up. Here is it. Freeware again.
version: '1.40'
- date: 2001-03-16
description: Made it possible to play files that are listed after a "Find all Mp3
on this drive". It also tells what song is currently playing on the main title
bar.
version: '1.36'
- date: 2001-03-15
description: Added a system icon. Wow! it almost looks like winamp!
version: '1.35'
- date: 2001-03-14
description: Added music progress bar and made the music playing continuous.
version: '1.34'
- date: 2001-03-13
description: Added mass renaming functions.
version: '1.33'
- date: 2001-03-12
description: Fixed some bugs with those useful function :) (and made the program
shareware)
version: '1.32'
- date: 2001-03-11
description: Added some useful functions.
version: '1.31'
- date: 2001-03-10
description: MAJOR CHANGE. yeah! I scrapped those radio buttons, and extended the
"recurse" function. Now, you only have 2 choices. Or you compare with your list,
or you compare within the folder (and sub-folders). With that system, you can
tell the program to just compare ALL mp3 in your hard drive. You just have to
select your drive root, and press "Find dupe files in this folder" having "Recurse"
checked.
version: '1.30'
- date: 2001-03-09
description: Added the Music Control panel. I just love it. do you?
version: '1.23'
- date: 2001-03-08
description: Fixed some inaccuracy with folder to folder comparison. (Will I be
done fixing someday??) and made mp3 search slightly faster.
version: '1.22'
- date: 2001-03-07
description: Added Mp3 Player (Didn't know it was so easy to include in a program!
I woulda done this before if I knew...) and ID3 Tag Editor. Hum, The Mp3 Player
has some problems reading some mp3s... know that.
version: '1.21'
- date: 2001-03-04
description: When I removed the "find mp3 in my list" thing, some people told me
it was useful. However, I still think that the old way to search mp3 was too messed
up, so I just added a little textbox and a search button for quick search.
version: '1.20'
- date: 2001-03-03
description: Damnit! why didn't I see it? There was a bug with displaying the right
filename on the result boxes with List comparing. The results were switched! Thus,
deleting was impossible after a List compare. Corrected it in 1.18.
version: '1.18'
- date: 2001-03-03
description: "When I read Yippee review (www.yippee.net thanks for review), I tried\
\ to somewhat improve it. What changed? This:\r\n\r\n* Compare engine is less\
\ strict. if one word contains the other, it now match (now, \"Limp Bizkit\" and\
\ \"Limp Bizkitt\" would match)\r\n* Removed some useless features to simplify\
\ the interface. \"Save as...\" (why did I put this on???) and \"Find file in\
\ my list\" (Easier to find a song with \"Edit List\")\r\n* Added some hints to\
\ buttons and some explicative labels.\r\n* \"List Stats\" changed to \"Edit\
\ List\" so you don't have to \"hard change\" your mp3 list."
version: '1.19'
- date: 2001-02-06
description: I never thought that a software history would be useful for such a
small program, but since Mp3 filter won't stop improving, I decided to start it.
So 1.17 is the base version.
version: '1.17'

7
me/help/gen.py Normal file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env python
import os
from web import generate_help
generate_help.main('.', 'dupeguru_me_help', force_render=True)

View File

@ -0,0 +1,409 @@
/*****************************************************
General settings
*****************************************************/
BODY
{
background-color:white;
}
BODY,A,P,UL,TABLE,TR,TD
{
font-family:Tahoma,Arial,sans-serif;
font-size:10pt;
color: #4477AA;/*darker than 5588bb for the sake of the eyes*/
}
/*****************************************************
"A" settings
*****************************************************/
A
{
color: #ae322b;
text-decoration:underline;
font-weight:bold;
}
A.glossaryword {color:#A0A0A0;}
A.noline
{
text-decoration: none;
}
/*****************************************************
Menu and mainframe settings
*****************************************************/
.maincontainer
{
display:block;
margin-left:7%;
margin-right:7%;
padding-left:5px;
padding-right:0px;
border-color:#CCCCCC;
border-style:solid;
border-width:2px;
border-right-width:0px;
border-bottom-width:0px;
border-top-color:#ae322b;
vertical-align:top;
}
TD.menuframe
{
width:30%;
}
.menu
{
margin:4px 4px 4px 4px;
margin-top: 16pt;
border-color:gray;
border-width:1px;
border-style:dotted;
padding-top:10pt;
padding-bottom:10pt;
padding-right:6pt;
}
.submenu
{
list-style-type: none;
margin-left:26pt;
margin-top:0pt;
margin-bottom:0pt;
padding-left:0pt;
}
A.menuitem,A.menuitem_selected
{
font-size:14pt;
font-family:Tahoma,Arial,sans-serif;
font-weight:normal;
padding-left:10pt;
color:#5588bb;
margin-right:2pt;
margin-left:4pt;
text-decoration:none;
}
A.menuitem_selected
{
font-weight:bold;
}
A.submenuitem
{
font-family:Tahoma,Arial,sans-serif;
font-weight:normal;
color:#5588bb;
text-decoration:none;
}
.titleline
{
border-width:3px;
border-style:solid;
border-left-width:0px;
border-right-width:0px;
border-top-width:0px;
border-color:#CCCCCC;
margin-left:28pt;
margin-right:2pt;
line-height:1px;
padding-top:0px;
margin-top:0px;
display:block;
}
.titledescrip
{
text-align:left;
display:block;
margin-left:26pt;
color:#ae322b;
}
.mainlogo
{
display:block;
margin-left:8%;
margin-top:4pt;
margin-bottom:4pt;
}
/*****************************************************
IMG settings
*****************************************************/
IMG
{
border-style:none;
}
IMG.smallbutton
{
margin-right: 20px;
float:none;
}
IMG.floating
{
float:left;
margin-right: 4pt;
margin-bottom: 4pt;
}
IMG.lefticon
{
vertical-align: middle;
padding-right: 2pt;
}
IMG.righticon
{
vertical-align: middle;
padding-left: 2pt;
}
/*****************************************************
TABLE settings
*****************************************************/
TABLE
{
border-style:none;
}
TABLE.box
{
width: 90%;
margin-left:5%;
}
TABLE.centered
{
margin-left: auto;
margin-right: auto;
}
TABLE.hardcoded
{
background-color: #225588;
margin-left: auto;
margin-right: auto;
width: 90%;
}
TR { background-color: transparent; }
TABLE.hardcoded TR { background-color: white }
TABLE.hardcoded TR.header
{
font-weight: bold;
color: black;
background-color: #C8D6E5;
}
TABLE.hardcoded TR.header TD {color:black;}
TABLE.hardcoded TD { padding-left: 2pt; }
TD.minimelem {
padding-right:0px;
padding-left:0px;
text-align:center;
}
TD.rightelem
{
text-align:right;
/*padding-left:0pt;*/
padding-right: 2pt;
width: 17%;
}
/*****************************************************
P settings
*****************************************************/
p,.sub{text-align:justify;}
.centered{text-align:center;}
.sub
{
padding-left: 16pt;
padding-right:16pt;
}
.Note, .ContactInfo
{
border-color: #ae322b;
border-width: 1pt;
border-style: dashed;
text-align:justify;
padding: 2pt 2pt 2pt 2pt;
margin-bottom:4pt;
margin-top:8pt;
list-style-position:inside;
}
.ContactInfo
{
width:60%;
margin-left:5%;
}
.NewsItem
{
border-color:#ae322b;
border-style: solid;
border-right:none;
border-top:none;
border-left:none;
border-bottom-width:1px;
text-align:justify;
padding-left:4pt;
padding-right:4pt;
padding-bottom:8pt;
}
/*****************************************************
Lists settings
*****************************************************/
UL.plain
{
list-style-type: none;
padding-left:0px;
margin-left:0px;
}
LI.plain
{
list-style-type: none;
}
LI.section
{
padding-top: 6pt;
}
UL.longtext LI
{
border-color: #ae322b;
border-width:0px;
border-top-width:1px;
border-style:solid;
margin-top:12px;
}
/*
with UL.longtext LI, there can be anything between
the UL and the LI, and it will still make the
lontext thing, I must break it with this hack
*/
UL.longtext UL LI
{
border-style:none;
margin-top:2px;
}
/*****************************************************
Titles settings
*****************************************************/
H1,H2,H3
{
font-family:"Courier New",monospace;
color:#5588bb;
}
H1
{
font-size:18pt;
color: #ae322b;
border-color: #70A0CF;
border-width: 1pt;
border-style: solid;
margin-top: 16pt;
margin-left: 5%;
margin-right: 5%;
padding-top: 2pt;
padding-bottom:2pt;
text-align: center;
}
H2
{
border-color: #ae322b;
border-bottom-width: 2px;
border-top-width: 0pt;
border-left-width: 2px;
border-right-width: 0pt;
border-bottom-color: #cccccc;
border-style: solid;
margin-top: 16pt;
margin-left: 0pt;
margin-right: 0pt;
padding-bottom:3pt;
padding-left:5pt;
text-align: left;
font-size:16pt;
}
H3
{
display:block;
color:#ae322b;
border-color: #70A0CF;
border-bottom-width: 2px;
border-top-width: 0pt;
border-left-width: 0pt;
border-right-width: 0pt;
border-style: dashed;
margin-top: 12pt;
margin-left: 0pt;
margin-bottom: 4pt;
width:auto;
padding-bottom:3pt;
padding-right:2pt;
padding-left:2pt;
text-align: left;
font-weight:bold;
}
/*****************************************************
Misc. classes
*****************************************************/
.longtext:first-letter {font-size: 150%}
.price, .loweredprice, .specialprice {font-weight:bold;}
.loweredprice {text-decoration:line-through}
.specialprice {color:red}
form
{
margin:0px;
}
.program_summary
{
float:right;
margin: 32pt;
margin-top:0pt;
margin-bottom:0pt;
}
.screenshot
{
float:left;
margin: 8pt;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,14 @@
<%inherit file="/base_help.mako"/>
${next.body()}
<%def name="menu()"><%
self.menuitem('intro.htm', 'Introduction', 'Introduction to dupeGuru')
self.menuitem('quick_start.htm', 'Quick Start', 'Quickly get into the action')
self.menuitem('directories.htm', 'Directories', 'Managing dupeGuru directories')
self.menuitem('preferences.htm', 'Preferences', 'Setting dupeGuru preferences')
self.menuitem('results.htm', 'Results', 'Time to delete these duplicates!')
self.menuitem('power_marker.htm', 'Power Marker', 'Take control of your duplicates')
self.menuitem('faq.htm', 'F.A.Q.', 'Frequently Asked Questions')
self.menuitem('versions.htm', 'Version History', 'Changes dupeGuru went through')
self.menuitem('credits.htm', 'Credits', 'People who contributed to dupeGuru')
%></%def>

View File

@ -0,0 +1,20 @@
<%!
title = 'Credits'
selected_menu_item = 'Credits'
%>
<%inherit file="/base_dg.mako"/>
Below is the list of people who contributed, directly or indirectly to dupeGuru.
${self.credit('Virgil Dupras', 'Developer', "That's me, Hardcoded Software founder", 'www.hardcoded.net', 'hsoft@hardcoded.net')}
${self.credit('Python', 'Programming language', "The bestest of the bests", 'www.python.org')}
${self.credit('PyObjC', 'Python-to-Cocoa bridge', "Used for the Mac OS X version", 'pyobjc.sourceforge.net')}
${self.credit('PyQt', 'Python-to-Qt bridge', "Used for the Windows version", 'www.riverbankcomputing.co.uk')}
${self.credit('Qt', 'GUI Toolkit', "Used for the Windows version", 'www.qtsoftware.com')}
${self.credit('Sparkle', 'Auto-update library', "Used for the Mac OS X version", 'andymatuschak.org/pages/sparkle')}
${self.credit('You', 'dupeGuru user', "What would I do without you?")}

View File

@ -0,0 +1,24 @@
<%!
title = 'Directories'
selected_menu_item = 'Directories'
%>
<%inherit file="/base_dg.mako"/>
There is a panel in dupeGuru called **Directories**. You can open it by clicking on the **Directories** button. This directory contains the list of the directories that will be scanned when you click on **Start Scanning**.
This panel is quite straightforward to use. If you want to add a directory, click on **Add**. If you added directories before, a popup menu with a list of recent directories you added will pop. You can click on one of them to add it directly to your list. If you click on the first item of the popup menu, **Add New Directory...**, you will be prompted for a directory to add. If you never added a directory, no menu will pop and you will directly be prompted for a new directory to add.
To remove a directory, select the directory to remove and click on **Remove**. If a subdirectory is selected when you click remove, the selected directory will be set to **excluded** state (see below) instead of being removed.
Directory states
-----
Every directory can be in one of these 3 states:
* **Normal:** Duplicates found in these directories can be deleted.
* **Reference:** Duplicates found in this directory **cannot** be deleted. Files in reference directories will be in a blue color in the results.
* **Excluded:** Files in this directory will not be included in the scan.
The default state of a directory is, of course, **Normal**. You can use **Reference** state for a directory if you want to be sure that you won't delete any file from it.
When you set the state of a directory, all subdirectories of this directory automatically inherit this state unless you explicitly set a subdirectory's state.

View File

@ -0,0 +1,67 @@
<%!
title = 'dupeGuru ME F.A.Q.'
selected_menu_item = 'F.A.Q.'
%>
<%inherit file="/base_dg.mako"/>
<%text filter="md">
### What is dupeGuru Music Edition?
dupeGuru Music Edition is a tool to find duplicate songs in your music collection. It can base its scan on filenames, tags or content. The filename and tag scans feature a fuzzy matching algorithm that can find duplicate filenames or tags even when they are not exactly the same.
### What makes it better than other duplicate scanners?
The scanning engine is extremely flexible. You can tweak it to really get the kind of results you want. You can read more about dupeGuru tweaking option at the [Preferences page](preferences.htm).
### How safe is it to use dupeGuru ME?
Very safe. dupeGuru has been designed to make sure you don't delete files you didn't mean to delete. First, there is the reference directory system that lets you define directories where you absolutely **don't** want dupeGuru to let you delete files there, and then there is the group reference system that makes sure that you will **always** keep at least one member of the duplicate group.
### What are the demo limitations of dupeGuru ME?
In demo mode, you can only perform actions (delete/copy/move) on 10 duplicates per session.
### The mark box of a file I want to delete is disabled. What must I do?
You cannot mark the reference (The first file) of a duplicate group. However, what you can do is to promote a duplicate file to reference. Thus, if a file you want to mark is reference, select a duplicate file in the group that you want to promote to reference, and click on **Actions-->Make Selected Reference**. If the reference file is from a reference directory (filename written in blue letters), you cannot remove it from the reference position.
### I have a directory from which I really don't want to delete files.
If you want to be sure that dupeGuru will never delete file from a particular directory, just open the **Directories panel**, select that directory, and set its state to **Reference**.
### What is this '(X discarded)' notice in the status bar?
In some cases, some matches are not included in the final results for security reasons. Let me use an example. We have 3 file: A, B and C. We scan them using a low filter hardness. The scanner determines that A matches with B, A matches with C, but B does **not** match with C. Here, dupeGuru has kind of a problem. It cannot create a duplicate group with A, B and C in it because not all files in the group would match together. It could create 2 groups: one A-B group and then one A-C group, but it will not, for security reasons. Lets think about it: If B doesn't match with C, it probably means that either B, C or both are not actually duplicates. If there would be 2 groups (A-B and A-C), you would end up delete both B and C. And if one of them is not a duplicate, that is really not what you want to do, right? So what dupeGuru does in a case like this is to discard the A-C match (and adds a notice in the status bar). Thus, if you delete B and re-run a scan, you will have a A-C match in your next results.
### I want to mark all files from a specific directory. What can I do?
Enable the [Power Marker](power_marker.htm) mode and click on the Directory column to sort your duplicates by Directory. It will then be easy for you to select all duplicates from the same directory, and then press Space to mark all selected duplicates.
### I want to remove all songs that are more than 3 seconds away from their reference file. What can I do?
* Enable the [Power Marker](power_marker.htm) mode.
* Enable the **Delta Values** mode.
* Click on the "Time" column to sort the results by time.
* Select all duplicates below -00:03.
* Click on **Remove Selected from Results**.
* Select all duplicates over 00:03.
* Click on **Remove Selected from Results**.
### I want to make my highest bitrate songs reference files. What can I do?
* Enable the [Power Marker](power_marker.htm) mode.
* Enable the **Delta Values** mode.
* Click on the "Bitrate" column to sort the results by bitrate.
* Click on the "Bitrate" column again to reverse the sort order (see Power Marker page to know why).
* Select all duplicates over 0.
* Click on **Make Selected Reference**.
### I don't want [live] and [remix] versions of my songs counted as duplicates. How do I do that?
If your comparison threshold is low enough, you will probably end up with live and remix versions of your songs in your results. There's nothing you can do to prevent that, but there's something you can do to easily remove them from your results after the scan: post-scan filtering. If, for example, you want to remove every song with anything inside square brackets []:
* **Windows**: Click on **Actions --> Apply Filter**, then type "[*]", then click OK.
* **Mac OS X**: Type "[*]" in the "Filter" field in the toolbar.
* Click on **Mark --> Mark All**.
* Click on **Actions --> Remove Selected from Results**.
</%text>

View File

@ -0,0 +1,13 @@
<%!
title = 'Introduction to dupeGuru ME'
selected_menu_item = 'introduction'
%>
<%inherit file="/base_dg.mako"/>
dupeGuru Music Edition is a tool to find duplicate files on your computer. It can scan either filenames or contents. The filename scan features a fuzzy matching algorithm that can find duplicate filenames even when they are not exactly the same.
Although dupeGuru can easily be used without documentation, reading this file will help you to master it. If you are looking for guidance for your first duplicate scan, you can take a look at the [Quick Start](quick_start.htm) section.
It is a good idea to keep dupeGuru updated. You can download the latest version on the [dupeGuru ME homepage](http://www.hardcoded.net/dupeguru_me/).
<%def name="meta()"><meta name="AppleTitle" content="dupeGuru ME Help"></meta></%def>

View File

@ -0,0 +1,33 @@
<%!
title = 'Power Marker'
selected_menu_item = 'Power Marker'
%>
<%inherit file="/base_dg.mako"/>
You will probably not use the Power Marker feature very often, but if you get into a situation where you need it, you will be pretty happy that this feature exists.
What is it?
-----
When the Power Marker mode is enabled, the duplicates are shown without their respective reference file. You can select, mark and sort this list, just like in normal mode.
So, what is it for?
-----
The dupeGuru results, when in normal mode, are sorted according to duplicate groups' **reference file**. This means that if you want, for example, to mark all duplicates with the "exe" extension, you cannot just sort the results by "Kind" to have all exe duplicates together because a group can be composed of more than one kind of files. That is where Power Marker comes into play. To mark all your "exe" duplicates, you just have to:
* Enable the Power marker mode.
* Add the "Kind" column with the "Columns" menu.
* Click on that "Kind" column to sort the list by kind.
* Locate the first duplicate with a "exe" kind.
* Select it.
* Scroll down the list to locate the last duplicate with a "exe" kind.
* Hold Shift and click on it.
* Press Space to mark all selected duplicates.
Power Marker and delta values
-----
The Power Marker unveil its true power when you use it with the **Delta Values** switch turned on. When you turn it on, relative values will be displayed instead of absolute ones. So if, for example, you want to remove from your results all duplicates that are more than 300 KB away from their reference, you could sort the Power Marker by Size, select all duplicates under -300 in the Size column, delete them, and then do the same for duplicates over 300 at the bottom of the list.
You could also use it to change the reference priority of your duplicate list. When you make a fresh scan, if there are no reference directories, the reference file of every group is the biggest file. If you want to change that, for example, to the latest modification time, you can sort the Power Marker by modification time in **descending** order, select all duplicates with a modification time delta value higher than 0 and click on **Make Selected Reference**. The reason why you must make the sort order descending is because if 2 files among the same duplicate group are selected when you click on **Make Selected Reference**, only the first of the list will be made reference, the other will be ignored. And since you want the last modified file to be reference, having the sort order descending assures you that the first item of the list will be the last modified.

View File

@ -0,0 +1,36 @@
<%!
title = 'Preferences'
selected_menu_item = 'Preferences'
%>
<%inherit file="/base_dg.mako"/>
**Scan Type:** This option determines what aspect of the files will be compared in the duplicate scan. The nature of the duplicate scan varies greatly depending on what you select for this option.
* **Filename:** Every song will have its filename split into words, and then every word will be compared to compute a matching percentage. If this percentage is higher or equal to the **Filter Hardness** (see below for more details), dupeGuru will consider the 2 songs duplicates.
* **Filename - Fields:** Like **Filename**, except that once filename have been split into words, these words are then grouped into fields. The field separator is " - ". The final matching percentage will be the lowest matching percentage among the fields. Thus, "An Artist - The Title" and "An Artist - Other Title" would have a matching percentage of 50 (With a **Filename** scan, it would be 75).
* **Filename - Fields (No Order):** Like **Filename - Fields**, except that field order doesn't matter. For example, "An Artist - The Title" and "The Title - An Artist" would have a matching percentage of 100 instead of 0.
* **Tags:** This method reads the tag (metadata) of every song and compare their fields. This method, like the **Filename - Fields**, considers the lowest matching field as its final matching percentage.
* **Content:** This scan method use the actual content of the songs to determine which are duplicates. For 2 songs to match with this method, they must have the **exact same content**.
* **Audio Content:** Same as content, but only the audio content is compared (without metadata).
**Filter Hardness:** If you chose a filename or tag based scan type, this option determines how similar two filenames/tags must be for dupeGuru to consider them duplicates. If the filter hardness is, for example 80, it means that 80% of the words of two filenames must match. To determine the matching percentage, dupeGuru first counts the total number of words in **both** filenames, then count the number of words matching (every word matching count as 2), and then divide the number of words matching by the total number of words. If the result is higher or equal to the filter hardness, we have a duplicate match. For example, "a b c d" and "c d e" have a matching percentage of 57 (4 words matching, 7 total words).
**Tags to scan:** When using the **Tags** scan type, you can select the tags that will be used for comparison.
**Word weighting:** If you chose a filename or tag based scan type, this option slightly changes how matching percentage is calculated. With word weighting, instead of having a value of 1 in the duplicate count and total word count, every word have a value equal to the number of characters they have. With word weighting, "ab cde fghi" and "ab cde fghij" would have a matching percentage of 53% (19 total characters, 10 characters matching (4 for "ab" and 6 for "cde")).
**Match similar words:** If you turn this option on, similar words will be counted as matches. For example "The White Stripes" and "The White Stripe" would have a match % of 100 instead of 66 with that option turned on. **Warning:** Use this option with caution. It is likely that you will get a lot of false positives in your results when turning it on. However, it will help you to find duplicates that you wouldn't have found otherwise. The scan process also is significantly slower with this option turned on.
**Can mix file kind:** If you check this box, duplicate groups are allowed to have files with different extensions. If you don't check it, well, they aren't!
**Use regular expressions when filtering:** If you check this box, the filtering feature will treat your filter query as a **regular expression**. Explaining them is beyond the scope of this document. A good place to start learning it is <http://www.regular-expressions.info>.
**Remove empty folders after delete or move:** When this option is enabled, folders are deleted after a file is deleted or moved and the folder is empty.
**Copy and Move:** Determines how the Copy and Move operations (in the Action menu) will behave.
* **Right in destination:** All files will be sent directly in the selected destination, without trying to recreate the source path at all.
* **Recreate relative path:** The source file's path will be re-created in the destination directory up to the root selection in the Directories panel. For example, if you added "/Users/foobar/Music" to your Directories panel and you move "/Users/foobar/Music/Artist/Album/the_song.mp3" to the destination "/Users/foobar/MyDestination", the final destination for the file will be "/Users/foobar/MyDestination/Artist/Album" ("/Users/foobar/Music" has been trimmed from source's path in the final destination.).
* **Recreate absolute path:** The source file's path will be re-created in the destination directory in it's entirety. For example, if you move "/Users/foobar/Music/Artist/Album/the_song.mp3" to the destination "/Users/foobar/MyDestination", the final destination for the file will be "/Users/foobar/MyDestination/Users/foobar/Music/Artist/Album".</li>
In all cases, dupeGuru nicely handles naming conflicts by prepending a number to the destination filename if the filename already exists in the destination.

View File

@ -0,0 +1,18 @@
<%!
title = 'Quick Start'
selected_menu_item = 'Quick Start'
%>
<%inherit file="/base_dg.mako"/>
To get you quickly started with dupeGuru, let's just make a standard scan using default preferences.
* Click on **Directories**.
* Click on **Add**.
* Choose a directory you want to scan for duplicates.
* Click on **Start Scanning**.
* Wait until the scan process is over.
* Look at every duplicate (The files that are indented) and verify that it is indeed a duplicate to the group's reference (The file above the duplicate that is not indented and have a disabled mark box).
* If a file is a false duplicate, select it and click on **Actions-->Remove Selected from Results**.
* Once you are sure that there is no false duplicate in your results, click on **Edit-->Mark All**, and then **Actions-->Send Marked to Recycle bin**.
That is only a basic scan. There are a lot of tweaking you can do to get different results and several methods of examining and modifying your results. To know about them, just read the rest of this help file.

View File

@ -0,0 +1,73 @@
<%!
title = 'Results'
selected_menu_item = 'Results'
%>
<%inherit file="/base_dg.mako"/>
When dupeGuru is finished scanning for duplicates, it will show its results in the form of duplicate group list.
About duplicate groups
-----
A duplicate group is a group of files that all match together. Every group has a **reference file** and one or more **duplicate files**. The reference file is the first file of the group. Its mark box is disabled. Below it, and indented, are the duplicate files.
You can mark duplicate files, but you can never mark the reference file of a group. This is a security measure to prevent dupeGuru from deleting not only duplicate files, but their reference. You sure don't want that, do you?
What determines which files are reference and which files are duplicates is first their directory state. A files from a reference directory will always be reference in a duplicate group. If all files are from a normal directory, the size determine which file will be the reference of a duplicate group. dupeGuru assumes that you always want to keep the biggest file, so the biggest files will take the reference position.
You can change the reference file of a group manually. To do so, select the duplicate file you want to promote to reference, and click on **Actions-->Make Selected Reference**.
Reviewing results
-----
Although you can just click on **Edit-->Mark All** and then **Actions-->Send Marked to Recycle bin** to quickly delete all duplicate files in your results, it is always recommended to review all duplicates before deleting them.
To help you reviewing the results, you can bring up the **Details panel**. This panel shows all the details of the currently selected file as well as its reference's details. This is very handy to quickly determine if a duplicate really is a duplicate. You can also double-click on a file to open it with its associated application.
If you have more false duplicates than true duplicates (If your filter hardness is very low), the best way to proceed would be to review duplicates, mark true duplicates and then click on **Actions-->Send Marked to Recycle bin**. If you have more true duplicates than false duplicates, you can instead mark all files that are false duplicates, and use **Actions-->Remove Marked from Results**.
Marking and Selecting
-----
A **marked** duplicate is a duplicate with the little box next to it having a check-mark. A **selected** duplicate is a duplicate being highlighted. The multiple selection actions can be performed in dupeGuru in the standard way (Shift/Command/Control click). You can toggle all selected duplicates' mark state by pressing **space**.
Delta Values
-----
If you turn this switch on, some columns will display the value relative to the duplicate's reference instead of the absolute values. These delta values will also be displayed in a different color so you can spot them easily. For example, if a duplicate is 1.2 MB and its reference is 1.4 MB, the Size column will display -0.2 MB. This option is a killer feature when combined with the [Power Marker](power_marker.htm).
Filtering
-----
dupeGuru supports post-scan filtering. With it, you can narrow down your results so you can perform actions on a subset of it. For example, you could easily mark all duplicates with their filename containing "copy" from your results using the filter.
**Windows:** To use the filtering feature, click on Actions --> Apply Filter, write down the filter you want to apply and click OK. To go back to unfiltered results, click on Actions --> Cancel Filter.
**Mac OS X:** To use the filtering feature, type your filter in the "Filter" search field in the toolbar. To go back to unfiltered result, blank out the field, or click on the "X".
In simple mode (the default mode), whatever you type as the filter is the string used to perform the actual filtering, with the exception of one wildcard: **\***. Thus, if you type "[*]" as your filter, it will match anything with [] brackets in it, whatever is in between those brackets.
For more advanced filtering, you can turn "Use regular expressions when filtering" on. The filtering feature will then use **regular expressions**. A regular expression is a language for matching text. Explaining them is beyond the scope of this document. A good place to start learning it is <http://www.regular-expressions.info>.
Matches are case insensitive in both simple and regexp mode.
For the filter to match, your regular expression don't have to match the whole filename, it just have to contain a string matching the expression.
You might notice that not all duplicates in the filtered results will match your filter. That is because as soon as one single duplicate in a group matches the filter, the whole group stays in the results so you can have a better view of the duplicate's context. However, non-matching duplicates are in "reference mode". Therefore, you can perform actions like Mark All and be sure to only mark filtered duplicates.
Action Menu
-----
* **Start Duplicate Scan:** Starts a new duplicate scan.
* **Clear Ignore List:** Remove all ignored matches you added. You have to start a new scan for the newly cleared ignore list to be effective.
* **Export Results to XHTML:** Take the current results, and create an XHTML file out of it. The columns that are visible when you click on this button will be the columns present in the XHTML file. The file will automatically be opened in your default browser.
* **Send Marked to Trash:** Send all marked duplicates to trash, obviously.
* **Move Marked to...:** Prompt you for a destination, and then move all marked files to that destination. Source file's path might be re-created in destination, depending on the "Copy and Move" preference.
* **Copy Marked to...:** Prompt you for a destination, and then copy all marked files to that destination. Source file's path might be re-created in destination, depending on the "Copy and Move" preference.
* **Remove Marked from Results:** Remove all marked duplicates from results. The actual files will not be touched and will stay where they are.
* **Remove Selected from Results:** Remove all selected duplicates from results. Note that all selected reference files will be ignored, only duplicates can be removed with this action.
* **Make Selected Reference:** Promote all selected duplicates to reference. If a duplicate is a part of a group having a reference file coming from a reference directory (in blue color), no action will be taken for this duplicate. If more than one duplicate among the same group are selected, only the first of each group will be promoted.
* **Add Selected to Ignore List:** This first removes all selected duplicates from results, and then add the match of that duplicate and the current reference in the ignore list. This match will not come up again in further scan. The duplicate itself might come back, but it will be matched with another reference file. You can clear the ignore list with the Clear Ignore List command.
* **Open Selected with Default Application:** Open the file with the application associated with selected file's type.
* **Reveal Selected in Finder:** Open the folder containing selected file.
* **Rename Selected:** Prompts you for a new name, and then rename the selected file.

View File

@ -0,0 +1,9 @@
<%!
title = 'dupeGuru ME version history'
selected_menu_item = 'Version History'
%>
<%inherit file="/base_dg.mako"/>
A large part of this version history is not serious (especially before v3), but it is always interesting to remember that I learnt most of what I know about designing/implementing/supporting a software through that.
${self.output_changelogs(changelog)}

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

@ -0,0 +1,60 @@
#!/usr/bin/env python
# Unit Name: app
# Created By: Virgil Dupras
# Created On: 2009-05-21
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
import hsfs.phys.music
from dupeguru import data_me, scanner
from base.app import DupeGuru as DupeGuruBase
from details_dialog import DetailsDialog
from preferences import Preferences
from preferences_dialog import PreferencesDialog
class DupeGuru(DupeGuruBase):
LOGO_NAME = 'logo_me'
NAME = 'dupeGuru Music Edition'
VERSION = '5.6.1'
DELTA_COLUMNS = frozenset([2, 3, 4, 5, 7, 8])
def __init__(self):
DupeGuruBase.__init__(self, data_me, appid=1)
def _setup(self):
self.scanner = scanner.ScannerME()
self.directories.dirclass = hsfs.phys.music.Directory
DupeGuruBase._setup(self)
def _update_options(self):
DupeGuruBase._update_options(self)
self.scanner.min_match_percentage = self.prefs.filter_hardness
self.scanner.scan_type = self.prefs.scan_type
self.scanner.word_weighting = self.prefs.word_weighting
self.scanner.match_similar_words = self.prefs.match_similar
scanned_tags = set()
if self.prefs.scan_tag_track:
scanned_tags.add('track')
if self.prefs.scan_tag_artist:
scanned_tags.add('artist')
if self.prefs.scan_tag_album:
scanned_tags.add('album')
if self.prefs.scan_tag_title:
scanned_tags.add('title')
if self.prefs.scan_tag_genre:
scanned_tags.add('genre')
if self.prefs.scan_tag_year:
scanned_tags.add('year')
self.scanner.scanned_tags = scanned_tags
def _create_details_dialog(self, parent):
return DetailsDialog(parent, self)
def _create_preferences(self):
return Preferences()
def _create_preferences_dialog(self, parent):
return PreferencesDialog(parent, self)

16
me/qt/app_win.py Normal file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python
# Unit Name: app_win
# Created By: Virgil Dupras
# Created On: 2009-05-21
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
import winshell
import app
class DupeGuru(app.DupeGuru):
@staticmethod
def _recycle_dupe(dupe):
winshell.delete_file(unicode(dupe.path), no_confirm=True)

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

@ -0,0 +1,41 @@
#!/usr/bin/env python
# Unit Name: build
# Created By: Virgil Dupras
# Created On: 2009-05-22
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
# 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("xcopy /Y C:\\src\\vs_comp\\msvcrt dist")
print_and_do("xcopy /Y /S /I help\\dupeguru_me_help dist\\help")
aicom = '"\\Program Files\\Caphyon\\Advanced Installer\\AdvancedInstaller.com"'
shutil.copy('installer.aip', 'installer_tmp.aip') # this is so we don'a have to re-commit installer.aip at every version change
print_and_do('%s /edit installer_tmp.aip /SetVersion %s' % (aicom, version))
print_and_do('%s /build installer_tmp.aip -force' % aicom)
os.remove('installer_tmp.aip')

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

@ -0,0 +1,20 @@
#!/usr/bin/env python
# Unit Name: details_dialog
# Created By: Virgil Dupras
# Created On: 2009-04-27
# $Id$
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QDialog
from base.details_table import DetailsModel
from details_dialog_ui import Ui_DetailsDialog
class DetailsDialog(QDialog, Ui_DetailsDialog):
def __init__(self, parent, app):
QDialog.__init__(self, parent, Qt.Tool)
self.app = app
self.setupUi(self)
self.model = DetailsModel(app)
self.tableView.setModel(self.model)

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

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

Some files were not shown because too many files have changed in this diff Show More