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

Compare commits

..

29 Commits

Author SHA1 Message Date
Virgil Dupras
b182585d46 Fixed column reloading which was broken since the mark-->marked rename. 2010-02-13 14:08:37 +01:00
Virgil Dupras
e8f92535d3 me v5.7.2 2010-02-13 13:00:41 +01:00
Virgil Dupras
d62c3663e9 qt: scroll to selection on results refresh. 2010-02-13 12:34:36 +01:00
Virgil Dupras
6b0bfda9fb During Make Selected Reference, it's now the selection *paths* that are restored rather than the selected *dupes* 2010-02-13 10:39:54 +01:00
Virgil Dupras
7477330961 Fixed ResultOutline.selectedDupeCount(). 2010-02-12 21:58:50 +01:00
Virgil Dupras
1f71157063 Updated cocoalib subrepo. 2010-02-12 20:10:50 +01:00
Virgil Dupras
905988c592 Removed MatchesView and took advantage of HSOutlineView's delete and space triggered delegate methods. 2010-02-12 17:15:48 +01:00
Virgil Dupras
310951bfa8 Removed getSelectedPaths() from ResultsWindow. 2010-02-12 16:30:32 +01:00
Virgil Dupras
64c1087856 Fixed app_test which was broken since connext() calls aren't made by the gui themselves. 2010-02-12 16:28:15 +01:00
Virgil Dupras
cab6d924aa Adapted the Qt codebase to the addition of core.gui.result_tree and core.gui.stats_label. 2010-02-12 15:39:29 +01:00
Virgil Dupras
c3a972d39b Fixed renaming in results. 2010-02-12 13:52:40 +01:00
Virgil Dupras
33d44d4d24 Remove Marked now correctly updates the results. 2010-02-12 13:39:50 +01:00
Virgil Dupras
fd89cf2482 Pushed some code down from app_cocoa to app and re-organized test units. 2010-02-12 12:43:50 +01:00
Virgil Dupras
112ffb981f Cleaned up some cruft. 2010-02-12 12:30:00 +01:00
Virgil Dupras
514426b980 Re-added the root children count optimization in the results outline. 2010-02-12 11:34:00 +01:00
Virgil Dupras
a4bf1c8be6 Made marking changes much faster and also made data fetching lazy in dupe nodes. 2010-02-12 11:21:39 +01:00
Virgil Dupras
9b82e1478f Re-added multiple selection support in the results. 2010-02-12 11:07:33 +01:00
Virgil Dupras
d5f145d57e Fixed sorting. 2010-02-11 21:03:22 +01:00
Virgil Dupras
bab891ee74 Added the StatsLabel. 2010-02-11 20:54:06 +01:00
Virgil Dupras
a65fd7d0d0 Brought back delta values. 2010-02-11 19:22:31 +01:00
Virgil Dupras
46836cc805 Pushed down some result refresh calls to the core code. 2010-02-11 18:47:45 +01:00
Virgil Dupras
42559f13d8 Began the transition to a HSOutline based result outline. There's still a lot of glitches, the most glaring one being the lack of support for multiple selection. 2010-02-11 17:52:18 +01:00
Virgil Dupras
87351b5920 Removed Table from cocoalib and fixed the license of the newly added units. 2010-02-11 13:38:34 +01:00
Virgil Dupras
e68dcf189c Adapted the ME project to the latest structural changes. 2010-02-11 13:35:14 +01:00
Virgil Dupras
5d62b8389c Added tag pe1.8.3 for changeset 1cef6d39855f 2010-02-11 12:37:15 +01:00
Virgil Dupras
c50aebe76d pe v1.8.3 2010-02-11 10:04:54 +01:00
Virgil Dupras
a610f3fde7 Adapted the PE project to the latest structural changes. 2010-02-10 12:07:31 +01:00
Virgil Dupras
626391a1d9 [#94 state:fixed] Fixed bug in block_osx causing blocks containing nil values to be created. 2010-02-10 11:58:05 +01:00
Virgil Dupras
1bedfe75ea Added tag se2.9.2 for changeset 7b7c5a66ebee 2010-02-10 10:50:05 +01:00
42 changed files with 1530 additions and 1376 deletions

View File

@@ -9,3 +9,5 @@ cbcf9c80fee4c908ef2efbf1c143c9e47676c9b2 pe1.8.0
0e923897a3389331d4ab3debbc40b8dd616199d9 pe1.8.1 0e923897a3389331d4ab3debbc40b8dd616199d9 pe1.8.1
2c454eca9ebe93b6cf34916068f828a6a39e3eaf me5.7.1 2c454eca9ebe93b6cf34916068f828a6a39e3eaf me5.7.1
19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2 19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2
7b7c5a66ebee4e4b8125330d24fe9ce1a070ff25 se2.9.2
1cef6d39855f85d4be728646bc78b860e6d4e398 pe1.8.3

View File

@@ -8,11 +8,6 @@ http://www.hardcoded.net/licenses/hs_license
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
/* ResultsChangedNotification happens on major changes, which requires a complete reload of the data*/
#define ResultsChangedNotification @"ResultsChangedNotification"
/* ResultsChangedNotification happens on minor changes, which requires buffer flush*/
#define ResultsUpdatedNotification @"ResultsUpdatedNotification"
#define ResultsMarkingChangedNotification @"ResultsMarkingChangedNotification"
#define RegistrationRequired @"RegistrationRequired" #define RegistrationRequired @"RegistrationRequired"
#define JobStarted @"JobStarted" #define JobStarted @"JobStarted"
#define JobInProgress @"JobInProgress" #define JobInProgress @"JobInProgress"

View File

@@ -14,9 +14,16 @@ http://www.hardcoded.net/licenses/hs_license
{ {
self = [super initWithNibName:@"DetailsPanel" pyClassName:@"PyDetailsPanel" pyParent:aPy]; self = [super initWithNibName:@"DetailsPanel" pyClassName:@"PyDetailsPanel" pyParent:aPy];
[self window]; //So the detailsTable is initialized. [self window]; //So the detailsTable is initialized.
[self connect];
return self; return self;
} }
- (void)dealloc
{
[self disconnect];
[super dealloc];
}
- (PyDetailsPanel *)py - (PyDetailsPanel *)py
{ {
return (PyDetailsPanel *)py; return (PyDetailsPanel *)py;

View File

@@ -13,9 +13,16 @@ http://www.hardcoded.net/licenses/hs_license
{ {
self = [super initWithPyClassName:@"PyDirectoryOutline" pyParent:aPyParent view:aOutlineView]; self = [super initWithPyClassName:@"PyDirectoryOutline" pyParent:aPyParent view:aOutlineView];
[outlineView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; [outlineView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
[self connect];
return self; return self;
} }
- (void)dealloc
{
[self disconnect];
[super dealloc];
}
- (PyDirectoryOutline *)py - (PyDirectoryOutline *)py
{ {
return (PyDirectoryOutline *)py; return (PyDirectoryOutline *)py;

View File

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

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

@@ -0,0 +1,24 @@
/*
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "HS" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "PyOutline.h"
@interface PyResultTree : PyOutline
- (BOOL)powerMarkerMode;
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
- (BOOL)deltaValuesMode;
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
- (NSString *)valueForPath:(NSArray *)aPath column:(NSInteger)aColumn;
- (BOOL)renameSelected:(NSString *)aNewName;
- (void)sortBy:(NSInteger)aIdentifier ascending:(BOOL)aAscending;
- (void)markSelected;
- (void)removeSelected;
- (NSArray *)rootChildrenCounts;
@end

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

@@ -0,0 +1,14 @@
/*
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "HS" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "PyGUI.h"
@interface PyStatsLabel : PyGUI
- (NSString *)display;
@end

View File

@@ -0,0 +1,26 @@
/*
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "HS" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "HSOutline.h"
#import "PyResultTree.h"
@interface ResultOutline : HSOutline
{
NSIndexSet *_deltaColumns;
NSArray *_rootChildrenCounts;
}
- (PyResultTree *)py;
- (BOOL)powerMarkerMode;
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
- (BOOL)deltaValuesMode;
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns;
- (NSInteger)selectedDupeCount;
- (void)removeSelected;
@end;

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

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

View File

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

View File

@@ -14,66 +14,29 @@ http://www.hardcoded.net/licenses/hs_license
#import "AppDelegate.h" #import "AppDelegate.h"
#import "Consts.h" #import "Consts.h"
@implementation MatchesView
- (void)keyDown:(NSEvent *)theEvent
{
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
// get flags and strip the lower 16 (device dependant) bits
NSUInteger flags = ( [theEvent modifierFlags] & 0x00FF );
if (((key == NSDeleteFunctionKey) || (key == NSDeleteCharacter)) && (flags == 0))
[self sendAction:@selector(removeSelected:) to:[self delegate]];
else
if ((key == 0x20) && (flags == 0)) // Space
[self sendAction:@selector(markSelected:) to:[self delegate]];
else
[super keyDown:theEvent];
}
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if (![[tableColumn identifier] isEqual:@"0"])
return; //We only want to cover renames.
OVNode *node = item;
NSString *oldName = [[node buffer] objectAtIndex:0];
NSString *newName = object;
if (![newName isEqual:oldName])
{
BOOL renamed = n2b([(PyDupeGuruBase *)py renameSelected:newName]);
if (renamed)
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
else
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.",newName]];
}
}
@end
@implementation ResultWindowBase @implementation ResultWindowBase
- (void)awakeFromNib - (void)awakeFromNib
{ {
_displayDelta = NO;
_powerMode = NO;
[self window]; [self window];
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"]; preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
outline = [[ResultOutline alloc] initWithPyParent:py view:matches];
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
[self initResultColumns]; [self initResultColumns];
[self fillColumnsMenu]; [self fillColumnsMenu];
[deltaSwitch setSelectedSegment:0]; [deltaSwitch setSelectedSegment:0];
[pmSwitch setSelectedSegment:0]; [pmSwitch setSelectedSegment:0];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[matches setTarget:self]; [matches setTarget:self];
[matches setDoubleAction:@selector(openClicked:)]; [matches setDoubleAction:@selector(openClicked:)];
[self refreshStats];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsChanged:) name:ResultsChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsUpdated:) name:ResultsUpdatedNotification object:nil];
} }
- (void)dealloc - (void)dealloc
{ {
[outline release];
[preferencesPanel release]; [preferencesPanel release];
[super dealloc]; [super dealloc];
} }
@@ -130,34 +93,6 @@ http://www.hardcoded.net/licenses/hs_license
return result; return result;
} }
- (NSArray *)getSelected:(BOOL)aDupesOnly
{
if (_powerMode)
aDupesOnly = NO;
NSIndexSet *indexes = [matches selectedRowIndexes];
NSMutableArray *nodeList = [NSMutableArray array];
OVNode *node;
NSInteger i = [indexes firstIndex];
while (i != NSNotFound)
{
node = [matches itemAtRow:i];
if (!aDupesOnly || ([node level] > 1))
[nodeList addObject:node];
i = [indexes indexGreaterThanIndex:i];
}
return nodeList;
}
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly
{
NSMutableArray *r = [NSMutableArray array];
NSArray *selected = [self getSelected:aDupesOnly];
for (OVNode *node in selected) {
[r addObject:p2a([node indexPath])];
}
return r;
}
- (void)initResultColumns - (void)initResultColumns
{ {
// Virtual // Virtual
@@ -172,12 +107,13 @@ http://www.hardcoded.net/licenses/hs_license
} }
//Add columns and set widths //Add columns and set widths
for (NSString *colId in aColumnsOrder) { for (NSString *colId in aColumnsOrder) {
if ([colId isEqual:@"mark"]) { NSInteger colIndex = [colId integerValue];
if ((colIndex == 0) && (![colId isEqual:@"0"])) {
continue; continue;
} }
NSTableColumn *col = [_resultColumns objectAtIndex:[colId intValue]]; NSTableColumn *col = [_resultColumns objectAtIndex:colIndex];
NSNumber *width = [aColumnsWidth objectForKey:[col identifier]]; NSNumber *width = [aColumnsWidth objectForKey:[col identifier]];
NSMenuItem *mi = [columnsMenu itemWithTag:[colId intValue]]; NSMenuItem *mi = [columnsMenu itemWithTag:colIndex];
if (width) { if (width) {
[col setWidth:[width floatValue]]; [col setWidth:[width floatValue]];
} }
@@ -185,43 +121,6 @@ http://www.hardcoded.net/licenses/hs_license
} }
} }
- (void)updatePySelection
{
NSArray *selection;
if (_powerMode) {
selection = [py selectedPowerMarkerNodePaths];
}
else {
selection = [py selectedResultNodePaths];
}
[matches selectNodePaths:selection];
}
- (void)performPySelection:(NSArray *)aIndexPaths
{
if (_powerMode) {
[py selectPowerMarkerNodePaths:aIndexPaths];
}
else {
[py selectResultNodePaths:aIndexPaths];
}
}
- (void)refreshStats
{
[stats setStringValue:[py getStatLine]];
}
/* Reload the matches outline and restore selection from py */
- (void)reloadMatches
{
[matches setDelegate:nil];
[matches reloadData];
[matches expandItem:nil expandChildren:YES];
[matches setDelegate:self];
[self updatePySelection];
}
/* Actions */ /* Actions */
- (IBAction)clearIgnoreList:(id)sender - (IBAction)clearIgnoreList:(id)sender
{ {
@@ -235,21 +134,12 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)changeDelta:(id)sender - (IBAction)changeDelta:(id)sender
{ {
_displayDelta = [deltaSwitch selectedSegment] == 1; [outline setDeltaValuesMode:[deltaSwitch selectedSegment] == 1];
[py setDisplayDeltaValues:b2n(_displayDelta)];
[self reloadMatches];
} }
- (IBAction)changePowerMarker:(id)sender - (IBAction)changePowerMarker:(id)sender
{ {
_powerMode = [pmSwitch selectedSegment] == 1; [outline setPowerMarkerMode:[pmSwitch selectedSegment] == 1];
if (_powerMode)
[matches setTag:2];
else
[matches setTag:0];
[matches expandItem:nil expandChildren:YES];
[self outlineView:matches didClickTableColumn:nil];
[self updatePySelection];
} }
- (IBAction)copyMarked:(id)sender - (IBAction)copyMarked:(id)sender
@@ -294,52 +184,37 @@ http://www.hardcoded.net/licenses/hs_license
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))]; [py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
[py applyFilter:[filterField stringValue]]; [py applyFilter:[filterField stringValue]];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
} }
- (IBAction)ignoreSelected:(id)sender - (IBAction)ignoreSelected:(id)sender
{ {
NSArray *nodeList = [self getSelected:YES]; NSInteger selectedDupeCount = [outline selectedDupeCount];
if (![nodeList count]) if (!selectedDupeCount)
return; return;
NSString *msg = [NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",[nodeList count]]; NSString *msg = [NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",selectedDupeCount];
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
return; return;
[py addSelectedToIgnoreList]; [py addSelectedToIgnoreList];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
} }
- (IBAction)markAll:(id)sender - (IBAction)markAll:(id)sender
{ {
[py markAll]; [py markAll];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
} }
- (IBAction)markInvert:(id)sender - (IBAction)markInvert:(id)sender
{ {
[py markInvert]; [py markInvert];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
} }
- (IBAction)markNone:(id)sender - (IBAction)markNone:(id)sender
{ {
[py markNone]; [py markNone];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
} }
- (IBAction)markSelected:(id)sender - (IBAction)markSelected:(id)sender
{ {
[self performPySelection:[self getSelectedPaths:YES]];
[py toggleSelectedMark]; [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)moveMarked:(id)sender - (IBAction)moveMarked:(id)sender
@@ -373,15 +248,9 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)openSelected:(id)sender - (IBAction)openSelected:(id)sender
{ {
[self performPySelection:[self getSelectedPaths:NO]];
[py openSelected]; [py openSelected];
} }
- (IBAction)refresh:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
}
- (IBAction)removeMarked:(id)sender - (IBAction)removeMarked:(id)sender
{ {
int mark_count = [[py getMarkCount] intValue]; int mark_count = [[py getMarkCount] intValue];
@@ -390,19 +259,11 @@ http://www.hardcoded.net/licenses/hs_license
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
return; return;
[py removeMarked]; [py removeMarked];
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
} }
- (IBAction)removeSelected:(id)sender - (IBAction)removeSelected:(id)sender
{ {
NSArray *nodeList = [self getSelected:YES]; [outline removeSelected];
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 - (IBAction)renameSelected:(id)sender
@@ -419,7 +280,6 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)revealSelected:(id)sender - (IBAction)revealSelected:(id)sender
{ {
[self performPySelection:[self getSelectedPaths:NO]];
[py revealSelected]; [py revealSelected];
} }
@@ -428,21 +288,14 @@ http://www.hardcoded.net/licenses/hs_license
[preferencesPanel showWindow:sender]; [preferencesPanel showWindow:sender];
} }
- (IBAction)startDuplicateScan:(id)sender
{
// Virtual
}
- (IBAction)switchSelected:(id)sender - (IBAction)switchSelected:(id)sender
{ {
// It might look like a complicated way to get the length of the current dupe list on the py side
// but after a lot of fussing around, believe it or not, it actually is.
NSInteger matchesTag = _powerMode ? 2 : 0;
NSInteger startLen = [[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count];
[py makeSelectedReference]; [py makeSelectedReference];
[self performPySelection:[self getSelectedPaths:NO]];
// In some cases (when in a filtered view in Power Marker mode, it's possible that the demoted
// ref is not a part of the filter, making the table smaller. In those cases, we want to do a
// complete reload of the table to avoid a crash.
if ([[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count] == startLen)
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsUpdatedNotification object:self];
else
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
} }
- (IBAction)toggleColumn:(id)sender - (IBAction)toggleColumn:(id)sender
@@ -488,43 +341,6 @@ http://www.hardcoded.net/licenses/hs_license
[self changePowerMarker:sender]; [self changePowerMarker:sender];
} }
/* Delegate */
- (void)outlineView:(NSOutlineView *)outlineView didClickTableColumn:(NSTableColumn *)tableColumn
{
if ([[outlineView sortDescriptors] count] < 1)
return;
NSSortDescriptor *sd = [[outlineView sortDescriptors] objectAtIndex:0];
if (_powerMode)
[py sortDupesBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
else
[py sortGroupsBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
[self reloadMatches];
}
- (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))) {
NSInteger i = [[tableColumn identifier] integerValue];
if ([_deltaColumns containsIndex:i]) {
[textCell setTextColor:[NSColor orangeColor]];
}
}
}
}
/* Notifications */ /* Notifications */
- (void)windowWillClose:(NSNotification *)aNotification - (void)windowWillClose:(NSNotification *)aNotification
{ {
@@ -533,7 +349,6 @@ http://www.hardcoded.net/licenses/hs_license
- (void)jobCompleted:(NSNotification *)aNotification - (void)jobCompleted:(NSNotification *)aNotification
{ {
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
NSInteger r = n2i([py getOperationalErrorCount]); NSInteger r = n2i([py getOperationalErrorCount]);
id lastAction = [[ProgressController mainProgressController] jobId]; id lastAction = [[ProgressController mainProgressController] jobId];
if ([lastAction isEqualTo:jobCopy]) { if ([lastAction isEqualTo:jobCopy]) {
@@ -558,7 +373,7 @@ http://www.hardcoded.net/licenses/hs_license
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."]; [Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
} }
else if ([lastAction isEqualTo:jobScan]) { else if ([lastAction isEqualTo:jobScan]) {
NSInteger groupCount = [[py getOutlineView:0 childCountsForPath:[NSArray array]] count]; NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil];
if (groupCount == 0) if (groupCount == 0)
[Dialogs showMessage:@"No duplicates found."]; [Dialogs showMessage:@"No duplicates found."];
} }
@@ -589,29 +404,6 @@ http://www.hardcoded.net/licenses/hs_license
[Dialogs showMessage:msg]; [Dialogs showMessage:msg];
} }
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
[self performPySelection:[self getSelectedPaths:NO]];
}
- (void)resultsChanged:(NSNotification *)aNotification
{
[self reloadMatches];
[self refreshStats];
}
- (void)resultsMarkingChanged:(NSNotification *)aNotification
{
[matches invalidateMarkings];
[self refreshStats];
}
- (void)resultsUpdated:(NSNotification *)aNotification
{
[matches invalidateBuffers];
[matches invalidateMarkings];
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{ {
return ![[ProgressController mainProgressController] isShown]; return ![[ProgressController mainProgressController] isShown];

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

@@ -0,0 +1,19 @@
/*
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "HS" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "HSGUIController.h"
#import "PyStatsLabel.h"
@interface StatsLabel : HSGUIController
{
NSTextField *labelView;
}
- (id)initWithPyParent:(id)aPyParent labelView:(NSTextField *)aLabelView;
- (PyStatsLabel *)py;
@end

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

@@ -0,0 +1,38 @@
/*
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "HS" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/hs_license
*/
#import "StatsLabel.h"
#import "Utils.h"
@implementation StatsLabel
- (id)initWithPyParent:(id)aPyParent labelView:(NSTextField *)aLabelView
{
self = [super initWithPyClassName:@"PyStatsLabel" pyParent:aPyParent];
labelView = [aLabelView retain];
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[labelView release];
[super dealloc];
}
- (PyStatsLabel *)py
{
return (PyStatsLabel *)py;
}
/* Python --> Cocoa */
- (void)refresh
{
[labelView setStringValue:[[self py] display]];
}
@end

View File

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

View File

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

View File

@@ -7,14 +7,9 @@ http://www.hardcoded.net/licenses/hs_license
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "../../cocoalib/Outline.h"
#import "../base/ResultWindow.h" #import "../base/ResultWindow.h"
#import "DirectoryPanel.h" #import "DirectoryPanel.h"
@interface ResultWindow : ResultWindowBase @interface ResultWindow : ResultWindowBase {}
{
NSString *_lastAction;
}
- (IBAction)removeDeadTracks:(id)sender; - (IBAction)removeDeadTracks:(id)sender;
- (IBAction)startDuplicateScan:(id)sender;
@end @end

View File

@@ -20,8 +20,9 @@ http://www.hardcoded.net/licenses/hs_license
{ {
[super awakeFromNib]; [super awakeFromNib];
[[self window] setTitle:@"dupeGuru Music Edition"]; [[self window] setTitle:@"dupeGuru Music Edition"];
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain]; NSMutableIndexSet *deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
[_deltaColumns removeIndex:6]; [deltaColumns removeIndex:6];
[outline setDeltaColumns:deltaColumns];
} }
/* Actions */ /* Actions */
@@ -68,8 +69,6 @@ http://www.hardcoded.net/licenses/hs_license
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]]; [_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]]; [_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
NSInteger r = n2i([py doScan]); NSInteger r = n2i([py doScan]);
[matches reloadData];
[self refreshStats];
if (r == 1) if (r == 1)
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."]; [Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
if (r == 3) if (r == 3)

View File

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

View File

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

View File

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

View File

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

View File

@@ -104,6 +104,11 @@ class DupeGuru(RegistrableApplication, Broadcaster):
path = Path(str_path) path = Path(str_path)
return fs.get_file(path, self.directories.fileclasses) return fs.get_file(path, self.directories.fileclasses)
def _job_completed(self, jobid):
# Must be called by subclasses when they detect that an async job is completed.
if jobid in (JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_DELETE):
self.notify('results_changed')
@staticmethod @staticmethod
def _open_path(path): def _open_path(path):
raise NotImplementedError() raise NotImplementedError()
@@ -151,6 +156,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
filter = escape(filter, '()[]\\.|+?^') filter = escape(filter, '()[]\\.|+?^')
filter = escape(filter, '*', '.') filter = escape(filter, '*', '.')
self.results.apply_filter(filter) self.results.apply_filter(filter)
self.notify('results_changed')
def clean_empty_dirs(self, path): def clean_empty_dirs(self, path):
if self.options['clean_empty_dirs']: if self.options['clean_empty_dirs']:
@@ -233,11 +239,34 @@ class DupeGuru(RegistrableApplication, Broadcaster):
if g not in changed_groups: if g not in changed_groups:
self.results.make_ref(dupe) self.results.make_ref(dupe)
changed_groups.add(g) changed_groups.add(g)
self.notify('results_switched')
def mark_all(self):
self.results.mark_all()
self.notify('marking_changed')
def mark_none(self):
self.results.mark_none()
self.notify('marking_changed')
def mark_invert(self):
self.results.mark_invert()
self.notify('marking_changed')
def mark_dupe(self, dupe, marked):
if marked:
self.results.mark(dupe)
else:
self.results.unmark(dupe)
self.notify('marking_changed')
def open_selected(self): def open_selected(self):
if self.selected_dupes: if self.selected_dupes:
self._open_path(self.selected_dupes[0].path) self._open_path(self.selected_dupes[0].path)
def purge_ignore_list(self):
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
def remove_directory(self,index): def remove_directory(self,index):
try: try:
del self.directories[index] del self.directories[index]
@@ -246,11 +275,25 @@ class DupeGuru(RegistrableApplication, Broadcaster):
pass pass
def remove_duplicates(self, duplicates): def remove_duplicates(self, duplicates):
self.results.remove_duplicates(duplicates) self.results.remove_duplicates(self.without_ref(duplicates))
self.notify('results_changed')
def remove_marked(self):
self.results.perform_on_marked(lambda x:True, True)
self.notify('results_changed')
def remove_selected(self): def remove_selected(self):
self.remove_duplicates(self.selected_dupes) self.remove_duplicates(self.selected_dupes)
def rename_selected(self, newname):
try:
d = self.selected_dupes[0]
d.rename(newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def reveal_selected(self): def reveal_selected(self):
if self.selected_dupes: if self.selected_dupes:
self._reveal_path(self.selected_dupes[0].path) self._reveal_path(self.selected_dupes[0].path)
@@ -283,6 +326,11 @@ class DupeGuru(RegistrableApplication, Broadcaster):
self.results.groups = [] self.results.groups = []
self._start_job(JOB_SCAN, do) self._start_job(JOB_SCAN, do)
def toggle_selected_mark_state(self):
for dupe in self.selected_dupes:
self.results.mark_toggle(dupe)
self.notify('marking_changed')
def without_ref(self, dupes): def without_ref(self, dupes):
return [dupe for dupe in dupes if self.results.get_group_of_duplicate(dupe).ref is not dupe] return [dupe for dupe in dupes if self.results.get_group_of_duplicate(dupe).ref is not dupe]

View File

@@ -14,7 +14,6 @@ from hsutil.cocoa import install_exception_hook
from hsutil.cocoa.objcmin import (NSNotificationCenter, NSUserDefaults, from hsutil.cocoa.objcmin import (NSNotificationCenter, NSUserDefaults,
NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSUserDomainMask, NSSearchPathForDirectoriesInDomains, NSApplicationSupportDirectory, NSUserDomainMask,
NSWorkspace, NSWorkspaceRecycleOperation) NSWorkspace, NSWorkspaceRecycleOperation)
from hsutil.misc import stripnone
from hsutil.reg import RegistrationRequired from hsutil.reg import RegistrationRequired
from . import app, fs from . import app, fs
@@ -46,7 +45,6 @@ class DupeGuru(app.DupeGuru):
appdata = op.join(appsupport, appdata_subdir) appdata = op.join(appsupport, appdata_subdir)
app.DupeGuru.__init__(self, data_module, appdata, appid) app.DupeGuru.__init__(self, data_module, appdata, appid)
self.progress = cocoa.ThreadedJobPerformer() self.progress = cocoa.ThreadedJobPerformer()
self.display_delta_values = False
#--- Override #--- Override
@staticmethod @staticmethod
@@ -75,34 +73,10 @@ class DupeGuru(app.DupeGuru):
ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid} ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid}
NSNotificationCenter.defaultCenter().postNotificationName_object_userInfo_('JobStarted', self, ud) NSNotificationCenter.defaultCenter().postNotificationName_object_userInfo_('JobStarted', self, ud)
#---Helpers
def GetObjects(self,node_path):
#returns a tuple g,d
try:
g = self.results.groups[node_path[0]]
if len(node_path) == 2:
return (g,self.results.groups[node_path[0]].dupes[node_path[1]])
else:
return (g,None)
except IndexError:
return (None,None)
#---Public #---Public
copy_or_move_marked = demo_method(app.DupeGuru.copy_or_move_marked) copy_or_move_marked = demo_method(app.DupeGuru.copy_or_move_marked)
delete_marked = demo_method(app.DupeGuru.delete_marked) delete_marked = demo_method(app.DupeGuru.delete_marked)
def PurgeIgnoreList(self):
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
def RenameSelected(self, newname):
try:
d = self.selected_dupes[0]
d.rename(newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def start_scanning(self): def start_scanning(self):
self._select_dupes([]) self._select_dupes([])
try: try:
@@ -113,106 +87,3 @@ class DupeGuru(app.DupeGuru):
except app.AllFilesAreRefError: except app.AllFilesAreRefError:
return 1 return 1
def selected_result_node_paths(self):
def get_path(dupe):
try:
group = self.results.get_group_of_duplicate(dupe)
groupindex = self.results.groups.index(group)
if dupe is group.ref:
return [groupindex]
dupeindex = group.dupes.index(dupe)
return [groupindex, dupeindex]
except ValueError: # dupe not in there
return None
dupes = self.selected_dupes
return stripnone(get_path(dupe) for dupe in dupes)
def selected_powermarker_node_paths(self):
def get_path(dupe):
try:
dupeindex = self.results.dupes.index(dupe)
return [dupeindex]
except ValueError: # dupe not in there
return None
dupes = self.selected_dupes
return stripnone(get_path(dupe) for dupe in dupes)
def SelectResultNodePaths(self,node_paths):
def extract_dupe(t):
g,d = t
if d is not None:
return d
else:
if g is not None:
return g.ref
selected = [extract_dupe(self.GetObjects(p)) for p in node_paths]
self._select_dupes([dupe for dupe in selected if dupe is not None])
def SelectPowerMarkerNodePaths(self,node_paths):
rows = [p[0] for p in node_paths]
dupes = [self.results.dupes[row] for row in rows if row in xrange(len(self.results.dupes))]
self._select_dupes(dupes)
def sort_dupes(self,key,asc):
self.results.sort_dupes(key,asc,self.display_delta_values)
def sort_groups(self,key,asc):
self.results.sort_groups(key,asc)
def ToggleSelectedMarkState(self):
for dupe in self.selected_dupes:
self.results.mark_toggle(dupe)
#---Data
def GetOutlineViewMaxLevel(self, tag):
if tag == 0:
return 2
elif tag == 2:
return 1
def GetOutlineViewChildCounts(self, tag, node_path):
if self.progress._job_running:
return []
if tag == 0: #Normal results
assert not node_path # no other value is possible
return [len(g.dupes) for g in self.results.groups]
else: #Power Marker
assert not node_path # no other value is possible
return [0 for d in self.results.dupes]
def GetOutlineViewValues(self, tag, node_path):
if self.progress._job_running:
return
if not node_path:
return
if tag in (0,2): #Normal results / Power Marker
if tag == 0:
g, d = self.GetObjects(node_path)
if (d is None) and (g is not None):
d = g.ref
else:
d = self.results.dupes[node_path[0]]
g = self.results.get_group_of_duplicate(d)
result = self._get_display_info(d, g, self.display_delta_values)
return result
def GetOutlineViewMarked(self, tag, node_path):
# 0=unmarked 1=marked 2=unmarkable
if self.progress._job_running:
return
if not node_path:
return 2
if tag == 0: #Normal results
g, d = self.GetObjects(node_path)
else: #Power Marker
d = self.results.dupes[node_path[0]]
if (d is None) or (not self.results.is_markable(d)):
return 2
elif self.results.is_marked(d):
return 1
else:
return 0

View File

@@ -13,6 +13,8 @@ from hsutil.cocoa.inter import signature, PyOutline, PyGUIObject, PyRegistrable
from .gui.details_panel import DetailsPanel from .gui.details_panel import DetailsPanel
from .gui.directory_tree import DirectoryTree from .gui.directory_tree import DirectoryTree
from .gui.result_tree import ResultTree
from .gui.stats_label import StatsLabel
# Fix py2app's problems on relative imports # Fix py2app's problems on relative imports
from core import app, app_cocoa, data, directories, engine, export, ignore, results, fs, scanner from core import app, app_cocoa, data, directories, engine, export, ignore, results, fs, scanner
@@ -43,19 +45,19 @@ class PyDupeGuruBase(PyRegistrable):
self.py.load() self.py.load()
def markAll(self): def markAll(self):
self.py.results.mark_all() self.py.mark_all()
def markNone(self): def markNone(self):
self.py.results.mark_none() self.py.mark_none()
def markInvert(self): def markInvert(self):
self.py.results.mark_invert() self.py.mark_invert()
def purgeIgnoreList(self): def purgeIgnoreList(self):
self.py.PurgeIgnoreList() self.py.purge_ignore_list()
def toggleSelectedMark(self): def toggleSelectedMark(self):
self.py.ToggleSelectedMarkState() self.py.toggle_selected_mark_state()
def saveIgnoreList(self): def saveIgnoreList(self):
self.py.save_ignore_list() self.py.save_ignore_list()
@@ -63,18 +65,6 @@ class PyDupeGuruBase(PyRegistrable):
def saveResults(self): def saveResults(self):
self.py.save() self.py.save()
def selectedResultNodePaths(self):
return self.py.selected_result_node_paths()
def selectResultNodePaths_(self,node_paths):
self.py.SelectResultNodePaths(node_paths)
def selectedPowerMarkerNodePaths(self):
return self.py.selected_powermarker_node_paths()
def selectPowerMarkerNodePaths_(self,node_paths):
self.py.SelectPowerMarkerNodePaths(node_paths)
#---Actions #---Actions
def addSelectedToIgnoreList(self): def addSelectedToIgnoreList(self):
self.py.add_selected_to_ignore_list() self.py.add_selected_to_ignore_list()
@@ -95,24 +85,14 @@ class PyDupeGuruBase(PyRegistrable):
self.py.open_selected() self.py.open_selected()
def removeMarked(self): def removeMarked(self):
self.py.results.perform_on_marked(lambda x:True, True) self.py.remove_marked()
def removeSelected(self):
self.py.remove_selected()
def renameSelected_(self,newname): def renameSelected_(self,newname):
return self.py.RenameSelected(newname) return self.py.rename_selected(newname)
def revealSelected(self): def revealSelected(self):
self.py.reveal_selected() self.py.reveal_selected()
#---Misc
def sortDupesBy_ascending_(self, key, asc):
self.py.sort_dupes(key, asc)
def sortGroupsBy_ascending_(self, key, asc):
self.py.sort_groups(key, asc)
#---Information #---Information
def getIgnoreListCount(self): def getIgnoreListCount(self):
return len(self.py.scanner.ignore_list) return len(self.py.scanner.ignore_list)
@@ -120,34 +100,13 @@ class PyDupeGuruBase(PyRegistrable):
def getMarkCount(self): def getMarkCount(self):
return self.py.results.mark_count return self.py.results.mark_count
def getStatLine(self):
return self.py.stat_line
def getOperationalErrorCount(self): def getOperationalErrorCount(self):
return self.py.last_op_error_count return self.py.last_op_error_count
#---Data
@signature('i@:i')
def getOutlineViewMaxLevel_(self, tag):
return self.py.GetOutlineViewMaxLevel(tag)
@signature('@@:i@')
def getOutlineView_childCountsForPath_(self, tag, node_path):
return self.py.GetOutlineViewChildCounts(tag, node_path)
def getOutlineView_valuesForIndexes_(self, tag, node_path):
return self.py.GetOutlineViewValues(tag, node_path)
def getOutlineView_markedAtIndexes_(self, tag, node_path):
return self.py.GetOutlineViewMarked(tag, node_path)
#---Properties #---Properties
def setMixFileKind_(self, mix_file_kind): def setMixFileKind_(self, mix_file_kind):
self.py.scanner.mix_file_kind = mix_file_kind self.py.scanner.mix_file_kind = mix_file_kind
def setDisplayDeltaValues_(self, display_delta_values):
self.py.display_delta_values= display_delta_values
def setEscapeFilterRegexp_(self, escape_filter_regexp): def setEscapeFilterRegexp_(self, escape_filter_regexp):
self.py.options['escape_filter_regexp'] = escape_filter_regexp self.py.options['escape_filter_regexp'] = escape_filter_regexp
@@ -164,6 +123,9 @@ class PyDupeGuruBase(PyRegistrable):
def cancelJob(self): def cancelJob(self):
self.py.progress.job_cancelled = True self.py.progress.job_cancelled = True
def jobCompleted_(self, jobid):
self.py._job_completed(jobid)
class PyDetailsPanel(PyGUIObject): class PyDetailsPanel(PyGUIObject):
py_class = DetailsPanel py_class = DetailsPanel
@@ -182,3 +144,55 @@ class PyDirectoryOutline(PyOutline):
def addDirectory_(self, path): def addDirectory_(self, path):
self.py.add_directory(path) self.py.add_directory(path)
class PyResultOutline(PyOutline):
py_class = ResultTree
@signature('c@:')
def powerMarkerMode(self):
return self.py.power_marker
@signature('v@:c')
def setPowerMarkerMode_(self, value):
self.py.power_marker = value
@signature('c@:')
def deltaValuesMode(self):
return self.py.delta_values
@signature('v@:c')
def setDeltaValuesMode_(self, value):
self.py.delta_values = value
@signature('@@:@i')
def valueForPath_column_(self, path, column):
return self.py.get_node_value(path, column)
@signature('c@:@')
def renameSelected_(self, newname):
return self.py.rename_selected(newname)
@signature('v@:ic')
def sortBy_ascending_(self, key, asc):
self.py.sort(key, asc)
def markSelected(self):
self.py.app.toggle_selected_mark_state()
def removeSelected(self):
self.py.app.remove_selected()
def rootChildrenCounts(self):
return self.py.root_children_counts()
# python --> cocoa
def invalidate_markings(self):
self.cocoa.invalidateMarkings()
class PyStatsLabel(PyGUIObject):
py_class = StatsLabel
def display(self):
return self.py.display

View File

@@ -21,3 +21,12 @@ class GUIObject(Listener):
def dupes_selected(self): def dupes_selected(self):
pass pass
def marking_changed(self):
pass
def results_changed(self):
pass
def results_switched(self):
pass

View File

@@ -13,8 +13,11 @@ class DetailsPanel(GUIObject):
def __init__(self, view, app): def __init__(self, view, app):
GUIObject.__init__(self, view, app) GUIObject.__init__(self, view, app)
self._table = [] self._table = []
def connect(self):
GUIObject.connect(self)
self._refresh() self._refresh()
self.connect() self.view.refresh()
#--- Private #--- Private
def _refresh(self): def _refresh(self):

View File

@@ -53,7 +53,9 @@ class DirectoryTree(GUIObject, Tree):
def __init__(self, view, app): def __init__(self, view, app):
GUIObject.__init__(self, view, app) GUIObject.__init__(self, view, app)
Tree.__init__(self) Tree.__init__(self)
self.connect()
def connect(self):
GUIObject.connect(self)
self._refresh() self._refresh()
self.view.refresh() self.view.refresh()

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

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-11
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "HS" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license
from operator import attrgetter
from hsgui.tree import Tree, Node
from .base import GUIObject
class DupeNode(Node):
def __init__(self, app, group, dupe):
Node.__init__(self, '')
self._app = app
self._group = group
self._dupe = dupe
self._data = None
self._data_delta = None
@property
def data(self):
if self._data is None:
self._data = self._app._get_display_info(self._dupe, self._group, False)
return self._data
@property
def data_delta(self):
if self._data_delta is None:
self._data_delta = self._app._get_display_info(self._dupe, self._group, True)
return self._data_delta
@property
def markable(self):
return self._app.results.is_markable(self._dupe)
@property
def marked(self):
return self._app.results.is_marked(self._dupe)
@marked.setter
def marked(self, value):
self._app.mark_dupe(self._dupe, value)
class ResultTree(GUIObject, Tree):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
Tree.__init__(self)
self._power_marker = False
self._delta_values = False
self._sort_descriptors = (0, True)
#--- Override
def connect(self):
GUIObject.connect(self)
self._refresh()
self.view.refresh()
def _select_nodes(self, nodes):
Tree._select_nodes(self, nodes)
self.app._select_dupes(map(attrgetter('_dupe'), nodes))
#--- Private
def _refresh(self):
self.clear()
if not self.power_marker:
for group in self.app.results.groups:
group_node = DupeNode(self.app, group, group.ref)
self.append(group_node)
for dupe in group.dupes:
group_node.append(DupeNode(self.app, group, dupe))
else:
for dupe in self.app.results.dupes:
group = self.app.results.get_group_of_duplicate(dupe)
self.append(DupeNode(self.app, group, dupe))
if self.app.selected_dupes:
to_find = set(self.app.selected_dupes)
nodes = list(self.findall(lambda n: n is not self and n._dupe in to_find))
self.selected_nodes = nodes
#--- Public
def get_node_value(self, path, column):
try:
node = self.get_node(path)
except IndexError:
return '---'
if self.delta_values:
return node.data_delta[column]
else:
return node.data[column]
def rename_selected(self, newname):
node = self.selected_node
node._data = None
node._data_delta = None
return self.app.rename_selected(newname)
def root_children_counts(self):
# This is a speed optimization for cases where there's a lot of results so that there is
# not thousands of children_count queries when expandAll is called.
return [len(node) for node in self]
def sort(self, key, asc):
if self.power_marker:
self.app.results.sort_dupes(key, asc, self.delta_values)
else:
self.app.results.sort_groups(key, asc)
self._sort_descriptors = (key, asc)
self._refresh()
self.view.refresh()
#--- Properties
@property
def power_marker(self):
return self._power_marker
@power_marker.setter
def power_marker(self, value):
if value == self._power_marker:
return
self._power_marker = value
key, asc = self._sort_descriptors
self.sort(key, asc)
self._refresh()
self.view.refresh()
@property
def delta_values(self):
return self._delta_values
@delta_values.setter
def delta_values(self, value):
if value == self._delta_values:
return
self._delta_values = value
self._refresh()
self.view.refresh()
#--- Event Handlers
def marking_changed(self):
self.view.invalidate_markings()
def results_changed(self):
self._refresh()
self.view.refresh()
def results_switched(self):
# What we want to to here is that instead of restoring selected *dupes* after refresh, we
# restore selected *paths*.
paths = self.selected_paths
self._refresh()
self.selected_paths = paths
self.view.refresh()

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

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-11
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "HS" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license
from .base import GUIObject
class StatsLabel(GUIObject):
def connect(self):
GUIObject.connect(self)
self.view.refresh()
@property
def display(self):
return self.app.stat_line
def results_changed(self):
self.view.refresh()
marking_changed = results_changed

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,13 +12,12 @@ import logging
import os import os
import os.path as op import os.path as op
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox
from hsutil import job from hsutil import job
from hsutil.reg import RegistrationRequired from hsutil.reg import RegistrationRequired
from core import fs
from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
from qtlib.about_box import AboutBox from qtlib.about_box import AboutBox
@@ -148,10 +147,6 @@ class DupeGuru(DupeGuruBase, QObject):
if self.main_window._confirm(title, msg): if self.main_window._confirm(title, msg):
DupeGuruBase.add_selected_to_ignore_list(self) DupeGuruBase.add_selected_to_ignore_list(self)
def apply_filter(self, filter):
DupeGuruBase.apply_filter(self, filter)
self.emit(SIGNAL('resultsChanged()'))
@demo_method @demo_method
def copy_or_move_marked(self, copy): def copy_or_move_marked(self, copy):
opname = 'copy' if copy else 'move' opname = 'copy' if copy else 'move'
@@ -165,14 +160,6 @@ class DupeGuru(DupeGuruBase, QObject):
delete_marked = demo_method(DupeGuruBase.delete_marked) delete_marked = demo_method(DupeGuruBase.delete_marked)
def make_selected_reference(self):
DupeGuruBase.make_selected_reference(self)
self.emit(SIGNAL('resultsChanged()'))
def remove_duplicates(self, duplicates):
DupeGuruBase.remove_duplicates(self, duplicates)
self.emit(SIGNAL('resultsChanged()'))
def remove_selected(self): def remove_selected(self):
dupes = self.without_ref(self.selected_dupes) dupes = self.without_ref(self.selected_dupes)
if not dupes: if not dupes:
@@ -186,37 +173,10 @@ class DupeGuru(DupeGuruBase, QObject):
def askForRegCode(self): def askForRegCode(self):
self.reg.ask_for_code() self.reg.ask_for_code()
def mark_all(self):
self.results.mark_all()
self.emit(SIGNAL('dupeMarkingChanged()'))
def mark_invert(self):
self.results.mark_invert()
self.emit(SIGNAL('dupeMarkingChanged()'))
def mark_none(self):
self.results.mark_none()
self.emit(SIGNAL('dupeMarkingChanged()'))
def openDebugLog(self): def openDebugLog(self):
debugLogPath = op.join(self.appdata, 'debug.log') debugLogPath = op.join(self.appdata, 'debug.log')
self._open_path(debugLogPath) self._open_path(debugLogPath)
def remove_marked_duplicates(self):
marked = [d for d in self.results.dupes if self.results.is_marked(d)]
self.remove_duplicates(marked)
def rename_dupe(self, dupe, newname):
try:
dupe.rename(newname)
return True
except (IndexError, fs.FSError) as e:
logging.warning("dupeGuru Warning: %s" % unicode(e))
return False
def select_dupes(self, dupes):
self._select_dupes(dupes)
def show_about_box(self): def show_about_box(self):
self.about_box.show() self.about_box.show()
@@ -238,11 +198,6 @@ class DupeGuru(DupeGuruBase, QObject):
self.prefs.save() self.prefs.save()
self._update_options() self._update_options()
def toggle_marking_for_dupes(self, dupes):
for dupe in dupes:
self.results.mark_toggle(dupe)
self.emit(SIGNAL('dupeMarkingChanged()'))
#--- Events #--- Events
def application_will_terminate(self): def application_will_terminate(self):
self.save() self.save()
@@ -253,7 +208,7 @@ class DupeGuru(DupeGuruBase, QObject):
self.reg.show_nag() self.reg.show_nag()
def job_finished(self, jobid): def job_finished(self, jobid):
self.emit(SIGNAL('resultsChanged()')) self._job_completed(jobid)
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE) and self.last_op_error_count > 0: 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) msg = "{0} files could not be processed.".format(self.results.mark_count)
QMessageBox.warning(self.main_window, 'Warning', msg) QMessageBox.warning(self.main_window, 'Warning', msg)

View File

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

View File

@@ -64,6 +64,7 @@ class DirectoriesModel(TreeModel):
def __init__(self, app): def __init__(self, app):
TreeModel.__init__(self) TreeModel.__init__(self)
self.model = DirectoryTree(self, app) self.model = DirectoryTree(self, app)
self.model.connect()
def _createNode(self, ref, row): def _createNode(self, ref, row):
return RefNode(self, None, ref, row) return RefNode(self, None, ref, row)

View File

@@ -8,7 +8,7 @@
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView, from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
QMessageBox, QInputDialog, QLineEdit, QItemSelectionModel, QDesktopServices) QMessageBox, QInputDialog, QLineEdit, QDesktopServices)
from hsutil.misc import nonone from hsutil.misc import nonone
@@ -16,7 +16,8 @@ from core.app import NoScannableFileError, AllFilesAreRefError
import dg_rc import dg_rc
from main_window_ui import Ui_MainWindow from main_window_ui import Ui_MainWindow
from results_model import ResultsDelegate, ResultsModel from results_model import ResultsModel
from stats_label import StatsLabel
class MainWindow(QMainWindow, Ui_MainWindow): class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, app): def __init__(self, app):
@@ -24,23 +25,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app = app self.app = app
self._last_filter = None self._last_filter = None
self._setupUi() self._setupUi()
self.resultsDelegate = ResultsDelegate() self.resultsModel = ResultsModel(self.app, self.resultsView)
self.resultsModel = ResultsModel(self.app) self.stats = StatsLabel(app, self.statusLabel)
self.resultsView.setModel(self.resultsModel)
self.resultsView.setItemDelegate(self.resultsDelegate)
self._load_columns() self._load_columns()
self._update_column_actions_status() self._update_column_actions_status()
self.resultsView.expandAll()
self._update_status_line()
self.connect(self.app, SIGNAL('resultsChanged()'), self.resultsChanged)
self.connect(self.app, SIGNAL('dupeMarkingChanged()'), self.dupeMarkingChanged)
self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit) self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit)
self.connect(self.resultsView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled) self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate) self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
self.connect(self.resultsModel, SIGNAL('modelReset()'), self.resultsReset)
self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked) self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked)
self.connect(self.resultsView, SIGNAL('spacePressed()'), self.resultsSpacePressed)
def _setupUi(self): def _setupUi(self):
self.setupUi(self) self.setupUi(self)
@@ -108,11 +102,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
h.setSectionHidden(index, not visible) h.setSectionHidden(index, not visible)
h.setResizeMode(0, QHeaderView.Stretch) h.setResizeMode(0, QHeaderView.Stretch)
def _redraw_results(self):
# HACK. this is the only way I found to update the widget without reseting everything
self.resultsView.scroll(0, 1)
self.resultsView.scroll(0, -1)
def _save_columns(self): def _save_columns(self):
h = self.resultsView.header() h = self.resultsView.header()
widths = [] widths = []
@@ -131,9 +120,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
colid = action.column_index colid = action.column_index
action.setChecked(not h.isSectionHidden(colid)) action.setChecked(not h.isSectionHidden(colid))
def _update_status_line(self):
self.statusLabel.setText(self.app.stat_line)
#--- Actions #--- Actions
def aboutTriggered(self): def aboutTriggered(self):
self.app.show_about_box() self.app.show_about_box()
@@ -185,8 +171,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.delete_marked() self.app.delete_marked()
def deltaTriggered(self): def deltaTriggered(self):
self.resultsModel.delta = self.actionDelta.isChecked() self.resultsModel.delta_values = self.actionDelta.isChecked()
self._redraw_results()
def detailsTriggered(self): def detailsTriggered(self):
self.app.show_details() self.app.show_details()
@@ -217,8 +202,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.mark_none() self.app.mark_none()
def markSelectedTriggered(self): def markSelectedTriggered(self):
dupes = self.resultsView.selectedDupes() self.app.toggle_selected_mark_state()
self.app.toggle_marking_for_dupes(dupes)
def moveTriggered(self): def moveTriggered(self):
self.app.copy_or_move_marked(False) self.app.copy_or_move_marked(False)
@@ -245,7 +229,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
title = "Remove duplicates" title = "Remove duplicates"
msg = "You are about to remove {0} files from results. Continue?".format(count) msg = "You are about to remove {0} files from results. Continue?".format(count)
if self._confirm(title, msg): if self._confirm(title, msg):
self.app.remove_marked_duplicates() self.app.remove_marked()
def removeSelectedTriggered(self): def removeSelectedTriggered(self):
self.app.remove_selected() self.app.remove_selected()
@@ -292,25 +276,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.actionActions.menu().exec_(event.globalPos()) self.actionActions.menu().exec_(event.globalPos())
def dupeMarkingChanged(self):
self._redraw_results()
self._update_status_line()
def resultsChanged(self):
self.resultsView.model().reset()
def resultsDoubleClicked(self): def resultsDoubleClicked(self):
self.app.open_selected() self.app.open_selected()
def resultsReset(self): def resultsSpacePressed(self):
self.resultsView.expandAll() self.app.toggle_selected_mark_state()
if self.app.selected_dupes:
[modelIndex] = self.resultsModel.indexesForDupes(self.app.selected_dupes[:1])
if modelIndex.isValid():
flags = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
self.resultsView.selectionModel().setCurrentIndex(modelIndex, flags)
self._update_status_line()
def selectionChanged(self, selected, deselected):
self.app.select_dupes(self.resultsView.selectedDupes())

View File

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

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

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Created By: Virgil Dupras
# Created On: 2010-02-12
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "HS" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/hs_license
from core.gui.stats_label import StatsLabel as StatsLabelModel
class StatsLabel(object):
def __init__(self, app, view):
self.view = view
self.model = StatsLabelModel(self, app)
self.model.connect()
def refresh(self):
self.view.setText(self.model.display)

View File

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

View File

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