Converted the result tree into a result table.

--HG--
rename : cocoa/base/PyResultTree.h => cocoa/base/PyResultTable.h
rename : cocoa/base/ResultOutline.h => cocoa/base/ResultTable.h
rename : cocoa/base/ResultOutline.m => cocoa/base/ResultTable.m
rename : core/gui/result_tree.py => core/gui/result_table.py
This commit is contained in:
Virgil Dupras 2010-09-24 15:48:59 +02:00
parent 9bd093a03c
commit 0d8ed92a68
15 changed files with 566 additions and 698 deletions

View File

@ -7,18 +7,18 @@ http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "PyOutline.h"
#import "PyTable.h"
@interface PyResultTree : PyOutline
@interface PyResultTable : PyTable
- (BOOL)powerMarkerMode;
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
- (BOOL)deltaValuesMode;
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
- (NSString *)valueForPath:(NSArray *)aPath column:(NSInteger)aColumn;
- (NSString *)valueForRow:(NSInteger)rowIndex column:(NSInteger)aColumn;
- (BOOL)renameSelected:(NSString *)aNewName;
- (void)sortBy:(NSInteger)aIdentifier ascending:(BOOL)aAscending;
- (void)markSelected;
- (void)removeSelected;
- (NSArray *)rootChildrenCounts;
- (NSInteger)selectedDupeCount;
@end

View File

@ -1,207 +0,0 @@
/*
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,15 +7,14 @@ http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "HSOutline.h"
#import "PyResultTree.h"
#import "HSTable.h"
#import "PyResultTable.h"
@interface ResultOutline : HSOutline
@interface ResultTable : HSTable
{
NSIndexSet *_deltaColumns;
NSArray *_rootChildrenCounts;
}
- (PyResultTree *)py;
- (PyResultTable *)py;
- (BOOL)powerMarkerMode;
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
- (BOOL)deltaValuesMode;

162
cocoa/base/ResultTable.m Normal file
View File

@ -0,0 +1,162 @@
/*
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 "ResultTable.h"
#import "Dialogs.h"
#import "Utils.h"
#import "Consts.h"
@implementation ResultTable
- (id)initWithPyParent:(id)aPyParent view:(NSTableView *)aTableView
{
self = [super initWithPyClassName:@"PyResultTable" pyParent:aPyParent view:aTableView];
[self connect];
return self;
}
- (void)dealloc
{
[self disconnect];
[_deltaColumns release];
[super dealloc];
}
- (PyResultTable *)py
{
return (PyResultTable *)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
{
return [[self py] selectedDupeCount];
}
- (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 */
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
{
NSString *identifier = [column identifier];
if ([identifier isEqual:@"marked"]) {
return [[self py] valueForColumn:@"marked" row:row];
}
NSInteger columnId = [identifier integerValue];
return [[self py] valueForRow:row column:columnId];
}
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row
{
NSString *identifier = [column identifier];
if ([identifier isEqual:@"marked"]) {
[[self py] setValue:object forColumn:identifier row:row];
}
else if ([identifier isEqual:@"0"]) {
NSString *oldName = [[self py] valueForRow:row 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 {
[tableView setNeedsDisplay:YES];
}
}
}
}
/* Delegate */
- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn
{
if ([[tableView sortDescriptors] count] < 1)
return;
NSSortDescriptor *sd = [[tableView sortDescriptors] objectAtIndex:0];
[[self py] sortBy:[[sd key] integerValue] ascending:[sd ascending]];
}
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
{
BOOL isMarkable = n2b([[self py] valueForColumn:@"markable" row:row]);
if ([[column identifier] isEqual:@"marked"]) {
[cell setEnabled:isMarkable];
// Low-tech solution, for indentation, but it works...
NSCellImagePosition pos = isMarkable ? NSImageRight : NSImageLeft;
[cell setImagePosition:pos];
}
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]) {
NSInteger i = [[column 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;
}
/* Python --> Cocoa */
- (void)invalidateMarkings
{
[tableView setNeedsDisplay:YES];
}
@end

View File

@ -7,10 +7,10 @@ http://www.hardcoded.net/licenses/hs_license
*/
#import <Cocoa/Cocoa.h>
#import "HSOutlineView.h"
#import "StatsLabel.h"
#import "ResultOutline.h"
#import "ResultTable.h"
#import "ProblemDialog.h"
#import "HSTableView.h"
#import "PyDupeGuru.h"
@interface ResultWindowBase : NSWindowController
@ -19,7 +19,7 @@ http://www.hardcoded.net/licenses/hs_license
IBOutlet PyDupeGuruBase *py;
IBOutlet id app;
IBOutlet NSSegmentedControl *deltaSwitch;
IBOutlet HSOutlineView *matches;
IBOutlet HSTableView *matches;
IBOutlet NSSegmentedControl *pmSwitch;
IBOutlet NSTextField *stats;
IBOutlet NSMenu *columnsMenu;
@ -27,7 +27,7 @@ http://www.hardcoded.net/licenses/hs_license
NSMutableArray *_resultColumns;
NSWindowController *preferencesPanel;
ResultOutline *outline;
ResultTable *table;
StatsLabel *statsLabel;
ProblemDialog *problemDialog;
}

View File

@ -19,7 +19,7 @@ http://www.hardcoded.net/licenses/hs_license
{
[self window];
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
outline = [[ResultOutline alloc] initWithPyParent:py view:matches];
table = [[ResultTable alloc] initWithPyParent:py view:matches];
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
problemDialog = [[ProblemDialog alloc] initWithPy:py];
[self initResultColumns];
@ -37,7 +37,7 @@ http://www.hardcoded.net/licenses/hs_license
- (void)dealloc
{
[outline release];
[table release];
[preferencesPanel release];
[statsLabel release];
[problemDialog release];
@ -137,12 +137,12 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)changeDelta:(id)sender
{
[outline setDeltaValuesMode:[deltaSwitch selectedSegment] == 1];
[table setDeltaValuesMode:[deltaSwitch selectedSegment] == 1];
}
- (IBAction)changePowerMarker:(id)sender
{
[outline setPowerMarkerMode:[pmSwitch selectedSegment] == 1];
[table setPowerMarkerMode:[pmSwitch selectedSegment] == 1];
}
- (IBAction)copyMarked:(id)sender
@ -191,7 +191,7 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)ignoreSelected:(id)sender
{
NSInteger selectedDupeCount = [outline selectedDupeCount];
NSInteger selectedDupeCount = [table selectedDupeCount];
if (!selectedDupeCount)
return;
NSString *msg = [NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",selectedDupeCount];
@ -293,7 +293,7 @@ http://www.hardcoded.net/licenses/hs_license
- (IBAction)removeSelected:(id)sender
{
[outline removeSelected];
[table removeSelected];
}
- (IBAction)renameSelected:(id)sender
@ -416,8 +416,8 @@ http://www.hardcoded.net/licenses/hs_license
}
}
else if ([lastAction isEqualTo:jobScan]) {
NSInteger groupCount = [outline intProperty:@"children_count" valueAtPath:nil];
if (groupCount == 0)
NSInteger rowCount = [[table py] numberOfRows];
if (rowCount == 0)
[Dialogs showMessage:@"No duplicates found."];
}

View File

@ -12,7 +12,8 @@
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="1204"/>
<integer value="1209"/>
<integer value="29"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
@ -82,11 +83,9 @@
<string key="NSToolbarItemPaletteLabel">Power Marker</string>
<nil key="NSToolbarItemToolTip"/>
<object class="NSSegmentedControl" key="NSToolbarItemView" id="35398541">
<reference key="NSNextResponder"/>
<nil key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{7, 14}, {67, 24}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSegmentedCell" key="NSCell" id="431579725">
<int key="NSCellFlags">67239424</int>
@ -177,11 +176,9 @@
<string key="NSToolbarItemPaletteLabel">Filter</string>
<nil key="NSToolbarItemToolTip"/>
<object class="NSSearchField" key="NSToolbarItemView" id="1013657232">
<reference key="NSNextResponder"/>
<nil key="NSNextResponder"/>
<int key="NSvFlags">258</int>
<string key="NSFrame">{{0, 14}, {81, 22}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSearchFieldCell" key="NSCell" id="484816507">
<int key="NSCellFlags">343014976</int>
@ -323,11 +320,9 @@
<string key="NSToolbarItemPaletteLabel">Action</string>
<nil key="NSToolbarItemToolTip"/>
<object class="NSPopUpButton" key="NSToolbarItemView" id="165812138">
<reference key="NSNextResponder"/>
<nil key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{0, 14}, {58, 26}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSPopUpButtonCell" key="NSCell" id="436420677">
<int key="NSCellFlags">-2076049856</int>
@ -530,11 +525,9 @@
<string key="NSToolbarItemPaletteLabel">Delta Values</string>
<nil key="NSToolbarItemToolTip"/>
<object class="NSSegmentedControl" key="NSToolbarItemView" id="311230297">
<reference key="NSNextResponder"/>
<nil key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<string key="NSFrame">{{4, 14}, {67, 24}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<bool key="NSEnabled">YES</bool>
<object class="NSSegmentedCell" key="NSCell" id="211272396">
<int key="NSCellFlags">67239424</int>
@ -681,52 +674,76 @@
<string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string>
<string key="NSWindowContentMinSize">{340, 340}</string>
<object class="NSView" key="NSWindowView" id="455829030">
<nil key="NSNextResponder"/>
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSScrollView" id="417210994">
<object class="NSTextField" id="895966510">
<reference key="NSNextResponder" ref="455829030"/>
<int key="NSvFlags">290</int>
<string key="NSFrame">{{17, 20}, {523, 17}}</string>
<reference key="NSSuperview" ref="455829030"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="839278531">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">138412032</int>
<string key="NSContents">Marked: 0 files, 0 B. Total: 0 files, 0 B.</string>
<reference key="NSSupport" ref="594927229"/>
<reference key="NSControlView" ref="895966510"/>
<object class="NSColor" key="NSBackgroundColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlColor</string>
<object class="NSColor" key="NSColor" id="713772391">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
</object>
</object>
<reference key="NSTextColor" ref="1058073270"/>
</object>
</object>
<object class="NSScrollView" id="516888538">
<reference key="NSNextResponder" ref="455829030"/>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSClipView" id="948758365">
<reference key="NSNextResponder" ref="417210994"/>
<object class="NSClipView" id="657398517">
<reference key="NSNextResponder" ref="516888538"/>
<int key="NSvFlags">2304</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSOutlineView" id="40047569">
<reference key="NSNextResponder" ref="948758365"/>
<int key="NSvFlags">274</int>
<object class="NSTableView" id="982695974">
<reference key="NSNextResponder" ref="657398517"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{515, 317}</string>
<reference key="NSSuperview" ref="948758365"/>
<reference key="NSSuperview" ref="657398517"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTableHeaderView" key="NSHeaderView" id="837301452">
<reference key="NSNextResponder" ref="1000298166"/>
<object class="NSTableHeaderView" key="NSHeaderView" id="437229738">
<reference key="NSNextResponder" ref="1021759594"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{515, 17}</string>
<reference key="NSSuperview" ref="1000298166"/>
<reference key="NSTableView" ref="40047569"/>
<reference key="NSSuperview" ref="1021759594"/>
<reference key="NSTableView" ref="982695974"/>
</object>
<object class="_NSCornerView" key="NSCornerView" id="860570967">
<reference key="NSNextResponder" ref="417210994"/>
<object class="_NSCornerView" key="NSCornerView" id="622871751">
<reference key="NSNextResponder" ref="516888538"/>
<int key="NSvFlags">-2147483392</int>
<string key="NSFrame">{{-26, 0}, {16, 17}}</string>
<reference key="NSSuperview" ref="417210994"/>
<string key="NSFrame">{{224, 0}, {16, 17}}</string>
<reference key="NSSuperview" ref="516888538"/>
</object>
<object class="NSMutableArray" key="NSTableColumns">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSTableColumn" id="430098394">
<object class="NSTableColumn" id="201009225">
<string key="NSIdentifier">marked</string>
<double key="NSWidth">47</double>
<double key="NSMinWidth">16</double>
<double key="NSMaxWidth">1000</double>
<double key="NSWidth">26</double>
<double key="NSMinWidth">26</double>
<double key="NSMaxWidth">26</double>
<object class="NSTableHeaderCell" key="NSHeaderCell">
<int key="NSCellFlags">75628096</int>
<int key="NSCellFlags2">2048</int>
<int key="NSCellFlags2">134219776</int>
<string key="NSContents"/>
<reference key="NSSupport" ref="26"/>
<object class="NSColor" key="NSBackgroundColor">
<object class="NSColor" key="NSBackgroundColor" id="589534335">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">headerColor</string>
@ -739,64 +756,63 @@
<reference key="NSColor" ref="951662694"/>
</object>
</object>
<object class="NSButtonCell" key="NSDataCell" id="705360835">
<object class="NSButtonCell" key="NSDataCell" id="267036250">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">131072</int>
<string key="NSContents"/>
<object class="NSFont" key="NSSupport">
<string key="NSName">LucidaGrande</string>
<double key="NSSize">12</double>
<int key="NSfFlags">16</int>
</object>
<reference key="NSControlView" ref="40047569"/>
<reference key="NSSupport" ref="26"/>
<reference key="NSControlView" ref="982695974"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<object class="NSCustomResource" key="NSNormalImage">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSSwitch</string>
</object>
<object class="NSButtonImageSource" key="NSAlternateImage">
<string key="NSImageName">NSSwitch</string>
</object>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">400</int>
<int key="NSPeriodicInterval">75</int>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
<bool key="NSIsEditable">YES</bool>
<reference key="NSTableView" ref="40047569"/>
<reference key="NSTableView" ref="982695974"/>
</object>
<object class="NSTableColumn" id="932540235">
<object class="NSTableColumn" id="146307356">
<string key="NSIdentifier">0</string>
<double key="NSWidth">195</double>
<double key="NSMinWidth">16</double>
<double key="NSMaxWidth">1000</double>
<double key="NSMaxWidth">3.4028234663852886e+38</double>
<object class="NSTableHeaderCell" key="NSHeaderCell">
<int key="NSCellFlags">75628096</int>
<int key="NSCellFlags2">2048</int>
<string key="NSContents">Name</string>
<reference key="NSSupport" ref="26"/>
<object class="NSColor" key="NSBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC4zMzMzMzI5OQA</bytes>
</object>
<reference key="NSBackgroundColor" ref="589534335"/>
<reference key="NSTextColor" ref="570076428"/>
</object>
<object class="NSTextFieldCell" key="NSDataCell" id="573658629">
<object class="NSTextFieldCell" key="NSDataCell" id="810890923">
<int key="NSCellFlags">337772096</int>
<int key="NSCellFlags2">2048</int>
<reference key="NSSupport" ref="26"/>
<reference key="NSControlView" ref="40047569"/>
<string key="NSContents">Text Cell</string>
<object class="NSFont" key="NSSupport">
<string key="NSName">LucidaGrande</string>
<double key="NSSize">11</double>
<int key="NSfFlags">16</int>
</object>
<reference key="NSControlView" ref="982695974"/>
<object class="NSColor" key="NSBackgroundColor" id="91259834">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlBackgroundColor</string>
<object class="NSColor" key="NSColor" id="713772391">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
</object>
<reference key="NSColor" ref="713772391"/>
</object>
<reference key="NSTextColor" ref="1058073270"/>
</object>
<int key="NSResizingMask">2</int>
<bool key="NSIsResizeable">YES</bool>
<reference key="NSTableView" ref="40047569"/>
<reference key="NSTableView" ref="982695974"/>
<object class="NSSortDescriptor" key="NSSortDescriptorPrototype">
<string key="NSKey">0</string>
<bool key="NSAscending">YES</bool>
@ -821,7 +837,7 @@
<reference key="NSDelegate"/>
<reference key="NSDataSource"/>
<int key="NSGridStyleMask">2</int>
<int key="NSColumnAutoresizingStyle">0</int>
<int key="NSColumnAutoresizingStyle">4</int>
<int key="NSDraggingSourceMaskForLocal">15</int>
<int key="NSDraggingSourceMaskForNonLocal">0</int>
<bool key="NSAllowsTypeSelect">YES</bool>
@ -829,78 +845,62 @@
</object>
</object>
<string key="NSFrame">{{1, 17}, {515, 317}}</string>
<reference key="NSSuperview" ref="417210994"/>
<reference key="NSDocView" ref="40047569"/>
<reference key="NSSuperview" ref="516888538"/>
<reference key="NSNextKeyView" ref="982695974"/>
<reference key="NSDocView" ref="982695974"/>
<reference key="NSBGColor" ref="91259834"/>
<int key="NScvFlags">4</int>
</object>
<object class="NSScroller" id="167459243">
<reference key="NSNextResponder" ref="417210994"/>
<object class="NSScroller" id="998740212">
<reference key="NSNextResponder" ref="516888538"/>
<int key="NSvFlags">-2147483392</int>
<string key="NSFrame">{{-30, 17}, {15, 302}}</string>
<reference key="NSSuperview" ref="417210994"/>
<reference key="NSTarget" ref="417210994"/>
<string key="NSFrame">{{224, 17}, {15, 102}}</string>
<reference key="NSSuperview" ref="516888538"/>
<reference key="NSTarget" ref="516888538"/>
<string key="NSAction">_doScroller:</string>
<double key="NSPercent">0.98739492893218994</double>
<double key="NSCurValue">37</double>
<double key="NSPercent">0.1947367936372757</double>
</object>
<object class="NSScroller" id="916628114">
<reference key="NSNextResponder" ref="417210994"/>
<object class="NSScroller" id="752400791">
<reference key="NSNextResponder" ref="516888538"/>
<int key="NSvFlags">-2147483392</int>
<string key="NSFrame">{{1, 319}, {515, 15}}</string>
<reference key="NSSuperview" ref="417210994"/>
<string key="NSFrame">{{1, 119}, {223, 15}}</string>
<reference key="NSSuperview" ref="516888538"/>
<int key="NSsFlags">1</int>
<reference key="NSTarget" ref="417210994"/>
<reference key="NSTarget" ref="516888538"/>
<string key="NSAction">_doScroller:</string>
<double key="NSPercent">0.85406301824212272</double>
<double key="NSPercent">0.57142859697341919</double>
</object>
<object class="NSClipView" id="1000298166">
<reference key="NSNextResponder" ref="417210994"/>
<object class="NSClipView" id="1021759594">
<reference key="NSNextResponder" ref="516888538"/>
<int key="NSvFlags">2304</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="837301452"/>
<reference ref="437229738"/>
</object>
<string key="NSFrame">{{1, 0}, {515, 17}}</string>
<reference key="NSSuperview" ref="417210994"/>
<reference key="NSDocView" ref="837301452"/>
<reference key="NSSuperview" ref="516888538"/>
<reference key="NSNextKeyView" ref="437229738"/>
<reference key="NSDocView" ref="437229738"/>
<reference key="NSBGColor" ref="91259834"/>
<int key="NScvFlags">4</int>
</object>
<reference ref="860570967"/>
<reference ref="622871751"/>
</object>
<string key="NSFrame">{{20, 45}, {517, 335}}</string>
<reference key="NSSuperview" ref="455829030"/>
<reference key="NSNextKeyView" ref="657398517"/>
<int key="NSsFlags">562</int>
<reference key="NSVScroller" ref="167459243"/>
<reference key="NSHScroller" ref="916628114"/>
<reference key="NSContentView" ref="948758365"/>
<reference key="NSHeaderClipView" ref="1000298166"/>
<reference key="NSCornerView" ref="860570967"/>
<reference key="NSVScroller" ref="998740212"/>
<reference key="NSHScroller" ref="752400791"/>
<reference key="NSContentView" ref="657398517"/>
<reference key="NSHeaderClipView" ref="1021759594"/>
<reference key="NSCornerView" ref="622871751"/>
<bytes key="NSScrollAmts">QSAAAEEgAABBgAAAQYAAAA</bytes>
</object>
<object class="NSTextField" id="895966510">
<reference key="NSNextResponder" ref="455829030"/>
<int key="NSvFlags">290</int>
<string key="NSFrame">{{17, 20}, {523, 17}}</string>
<reference key="NSSuperview" ref="455829030"/>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="839278531">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">138412032</int>
<string key="NSContents">Marked: 0 files, 0 B. Total: 0 files, 0 B.</string>
<reference key="NSSupport" ref="594927229"/>
<reference key="NSControlView" ref="895966510"/>
<object class="NSColor" key="NSBackgroundColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlColor</string>
<reference key="NSColor" ref="713772391"/>
</object>
<reference key="NSTextColor" ref="1058073270"/>
</object>
</object>
</object>
<string key="NSFrameSize">{557, 400}</string>
<reference key="NSSuperview"/>
</object>
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
<string key="NSMinSize">{340, 418}</string>
@ -1713,14 +1713,6 @@
</object>
<int key="connectionID">212</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">initialFirstResponder</string>
<reference key="source" ref="641929189"/>
<reference key="destination" ref="40047569"/>
</object>
<int key="connectionID">279</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">stats</string>
@ -1833,14 +1825,6 @@
</object>
<int key="connectionID">661</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">menu</string>
<reference key="source" ref="40047569"/>
<reference key="destination" ref="591769558"/>
</object>
<int key="connectionID">663</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">openSelected:</string>
@ -2241,14 +2225,6 @@
</object>
<int key="connectionID">1175</int>
</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 class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">invokeCustomCommand:</string>
@ -2273,6 +2249,22 @@
</object>
<int key="connectionID">1208</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">matches</string>
<reference key="source" ref="339936126"/>
<reference key="destination" ref="982695974"/>
</object>
<int key="connectionID">1225</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">menu</string>
<reference key="source" ref="982695974"/>
<reference key="destination" ref="591769558"/>
</object>
<int key="connectionID">1226</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@ -2317,56 +2309,11 @@
<reference key="object" ref="455829030"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="417210994"/>
<reference ref="895966510"/>
<reference ref="516888538"/>
</object>
<reference key="parent" ref="641929189"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">219</int>
<reference key="object" ref="417210994"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="40047569"/>
<reference ref="167459243"/>
<reference ref="916628114"/>
<reference ref="837301452"/>
</object>
<reference key="parent" ref="455829030"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">220</int>
<reference key="object" ref="40047569"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="932540235"/>
<reference ref="430098394"/>
</object>
<reference key="parent" ref="417210994"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">222</int>
<reference key="object" ref="932540235"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="573658629"/>
</object>
<reference key="parent" ref="40047569"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">406</int>
<reference key="object" ref="430098394"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="705360835"/>
</object>
<reference key="parent" ref="40047569"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">407</int>
<reference key="object" ref="705360835"/>
<reference key="parent" ref="430098394"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">291</int>
<reference key="object" ref="895966510"/>
@ -2870,26 +2817,6 @@
<reference key="object" ref="839278531"/>
<reference key="parent" ref="895966510"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1140</int>
<reference key="object" ref="573658629"/>
<reference key="parent" ref="932540235"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1144</int>
<reference key="object" ref="167459243"/>
<reference key="parent" ref="417210994"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1145</int>
<reference key="object" ref="916628114"/>
<reference key="parent" ref="417210994"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1146</int>
<reference key="object" ref="837301452"/>
<reference key="parent" ref="417210994"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1147</int>
<reference key="object" ref="307721027"/>
@ -3171,6 +3098,71 @@
<reference key="object" ref="630362403"/>
<reference key="parent" ref="948321368"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1209</int>
<reference key="object" ref="516888538"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="998740212"/>
<reference ref="752400791"/>
<reference ref="982695974"/>
<reference ref="437229738"/>
</object>
<reference key="parent" ref="455829030"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1210</int>
<reference key="object" ref="998740212"/>
<reference key="parent" ref="516888538"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1211</int>
<reference key="object" ref="752400791"/>
<reference key="parent" ref="516888538"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1212</int>
<reference key="object" ref="982695974"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="201009225"/>
<reference ref="146307356"/>
</object>
<reference key="parent" ref="516888538"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1213</int>
<reference key="object" ref="437229738"/>
<reference key="parent" ref="516888538"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1218</int>
<reference key="object" ref="201009225"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="267036250"/>
</object>
<reference key="parent" ref="982695974"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1222</int>
<reference key="object" ref="146307356"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="810890923"/>
</object>
<reference key="parent" ref="982695974"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1223</int>
<reference key="object" ref="810890923"/>
<reference key="parent" ref="146307356"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1224</int>
<reference key="object" ref="267036250"/>
<reference key="parent" ref="201009225"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
@ -3205,14 +3197,6 @@
<string>1137.IBPluginDependency</string>
<string>1138.IBPluginDependency</string>
<string>1139.IBPluginDependency</string>
<string>1140.IBPluginDependency</string>
<string>1140.IBShouldRemoveOnLegacySave</string>
<string>1144.IBPluginDependency</string>
<string>1144.IBShouldRemoveOnLegacySave</string>
<string>1145.IBPluginDependency</string>
<string>1145.IBShouldRemoveOnLegacySave</string>
<string>1146.IBPluginDependency</string>
<string>1146.IBShouldRemoveOnLegacySave</string>
<string>1147.IBEditorWindowLastContentRect</string>
<string>1147.IBPluginDependency</string>
<string>1156.IBPluginDependency</string>
@ -3229,6 +3213,16 @@
<string>1204.IBPluginDependency</string>
<string>1205.IBPluginDependency</string>
<string>1206.IBPluginDependency</string>
<string>1209.IBPluginDependency</string>
<string>1210.IBPluginDependency</string>
<string>1211.IBPluginDependency</string>
<string>1212.CustomClassName</string>
<string>1212.IBPluginDependency</string>
<string>1213.IBPluginDependency</string>
<string>1218.IBPluginDependency</string>
<string>1222.IBPluginDependency</string>
<string>1223.IBPluginDependency</string>
<string>1224.IBPluginDependency</string>
<string>134.IBPluginDependency</string>
<string>134.ImportedFromIB2</string>
<string>136.IBPluginDependency</string>
@ -3256,13 +3250,6 @@
<string>21.NSWindowTemplate.visibleAtLaunch</string>
<string>21.windowTemplate.hasMinSize</string>
<string>21.windowTemplate.minSize</string>
<string>219.IBPluginDependency</string>
<string>219.ImportedFromIB2</string>
<string>220.CustomClassName</string>
<string>220.IBPluginDependency</string>
<string>220.ImportedFromIB2</string>
<string>222.IBPluginDependency</string>
<string>222.ImportedFromIB2</string>
<string>23.IBPluginDependency</string>
<string>23.ImportedFromIB2</string>
<string>24.IBEditorWindowLastContentRect</string>
@ -3277,10 +3264,6 @@
<string>398.ImportedFromIB2</string>
<string>399.IBPluginDependency</string>
<string>399.ImportedFromIB2</string>
<string>406.IBPluginDependency</string>
<string>406.ImportedFromIB2</string>
<string>407.IBPluginDependency</string>
<string>407.ImportedFromIB2</string>
<string>497.ImportedFromIB2</string>
<string>5.IBPluginDependency</string>
<string>5.ImportedFromIB2</string>
@ -3452,14 +3435,6 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>{{409, 745}, {617, 0}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@ -3477,6 +3452,16 @@
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>HSTableView</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
@ -3496,22 +3481,15 @@
<boolean value="YES"/>
<boolean value="YES"/>
<boolean value="YES"/>
<string>{{439, 345}, {557, 400}}</string>
<string>{{324, 289}, {557, 400}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{439, 345}, {557, 400}}</string>
<string>{{324, 289}, {557, 400}}</string>
<boolean value="YES"/>
<boolean value="YES"/>
<boolean value="YES"/>
<string>{340, 340}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>HSOutlineView</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>{{531, 625}, {193, 143}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
@ -3524,10 +3502,6 @@
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
<boolean value="YES"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES"/>
@ -3686,7 +3660,7 @@
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">1208</int>
<int key="maxID">1226</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@ -3863,16 +3837,23 @@
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">HSOutlineView</string>
<string key="superclassName">NSOutlineView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="384069338">
<string key="className">HSTableView</string>
<string key="superclassName">NSTableView</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="1040579274">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../views/HSTableView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">../views/HSOutlineView.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<reference key="sourceIdentifier" ref="384069338"/>
<reference key="sourceIdentifier" ref="1040579274"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
@ -4311,7 +4292,7 @@
<string>NSMenu</string>
<string>NSSegmentedControl</string>
<string>NSSearchField</string>
<string>HSOutlineView</string>
<string>HSTableView</string>
<string>NSSegmentedControl</string>
<string>PyDupeGuruBase</string>
<string>NSTextField</string>
@ -4350,7 +4331,7 @@
</object>
<object class="IBToOneOutletInfo">
<string key="name">matches</string>
<string key="candidateClassName">HSOutlineView</string>
<string key="candidateClassName">HSTableView</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">pmSwitch</string>
@ -4576,7 +4557,7 @@
</object>
<object class="IBPartialClassDescription">
<string key="className">NSObject</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier" id="444201337">
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBFrameworkSource</string>
<string key="minorKey">AppKit.framework/Headers/NSOutlineView.h</string>
</object>
@ -4763,11 +4744,6 @@
<string key="minorKey">Sparkle.framework/Headers/SUUpdater.h</string>
</object>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSOutlineView</string>
<string key="superclassName">NSTableView</string>
<reference key="sourceIdentifier" ref="444201337"/>
</object>
<object class="IBPartialClassDescription">
<string key="className">NSPopUpButton</string>
<string key="superclassName">NSButton</string>
@ -5023,6 +4999,7 @@
<string>NSApplicationIcon</string>
<string>NSMenuCheckmark</string>
<string>NSMenuMixedState</string>
<string>NSSwitch</string>
<string>details32</string>
<string>folder32</string>
<string>preferences32</string>
@ -5033,6 +5010,7 @@
<string>{128, 128}</string>
<string>{9, 8}</string>
<string>{7, 2}</string>
<string>{15, 15}</string>
<string>{48, 48}</string>
<string>{32, 32}</string>
<string>{32, 32}</string>

View File

@ -20,7 +20,7 @@ http://www.hardcoded.net/licenses/hs_license
[super awakeFromNib];
NSMutableIndexSet *deltaColumns = [NSMutableIndexSet indexSetWithIndex:2];
[deltaColumns addIndex:4];
[outline setDeltaColumns:deltaColumns];
[table setDeltaColumns:deltaColumns];
}
/* Actions */

View File

@ -20,6 +20,8 @@
CE4557B40AE3BC50005A9546 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE45579A0AE3BC2B005A9546 /* Sparkle.framework */; };
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = CE647E551173024A006D28BA /* ProblemDialog.m */; };
CE647E591173026F006D28BA /* ProblemDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE647E581173026F006D28BA /* ProblemDialog.xib */; };
CE6DD4E7124CA3070089A48D /* ResultTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6DD4E6124CA3070089A48D /* ResultTable.m */; };
CE6DD547124CAF1F0089A48D /* HSTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6DD546124CAF1F0089A48D /* HSTableView.m */; };
CE6E0DFE1054E9EF008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */; };
CE76FDC4111EE37C006618EA /* HSOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDBF111EE37C006618EA /* HSOutlineView.m */; };
CE76FDC5111EE37C006618EA /* NSIndexPathAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDC1111EE37C006618EA /* NSIndexPathAdditions.m */; };
@ -29,7 +31,6 @@
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDDE111EE42F006618EA /* HSOutline.m */; };
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CE76FDF6111EE561006618EA /* NSEventAdditions.m */; };
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */ = {isa = PBXBuildFile; fileRef = CE8C53BB117324CE0011B41F /* HSTable.m */; };
CE91F215113BC22D0010360B /* ResultOutline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F212113BC22D0010360B /* ResultOutline.m */; };
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE91F214113BC22D0010360B /* StatsLabel.m */; };
CEAC6811109B0B7E00B43C85 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEAC6810109B0B7E00B43C85 /* Preferences.xib */; };
CEBC6C3912144A4B007B43AE /* registration.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEBC6C3712144A4B007B43AE /* registration.xib */; };
@ -89,6 +90,11 @@
CE647E551173024A006D28BA /* ProblemDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProblemDialog.m; path = ../base/ProblemDialog.m; sourceTree = SOURCE_ROOT; };
CE647E561173024A006D28BA /* PyProblemDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyProblemDialog.h; path = ../base/PyProblemDialog.h; sourceTree = SOURCE_ROOT; };
CE647E581173026F006D28BA /* ProblemDialog.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = ProblemDialog.xib; path = ../base/xib/ProblemDialog.xib; sourceTree = SOURCE_ROOT; };
CE6DD4E4124CA3070089A48D /* PyResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTable.h; path = ../base/PyResultTable.h; sourceTree = SOURCE_ROOT; };
CE6DD4E5124CA3070089A48D /* ResultTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultTable.h; path = ../base/ResultTable.h; sourceTree = SOURCE_ROOT; };
CE6DD4E6124CA3070089A48D /* ResultTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultTable.m; path = ../base/ResultTable.m; sourceTree = SOURCE_ROOT; };
CE6DD545124CAF1F0089A48D /* HSTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSTableView.h; path = ../../cocoalib/views/HSTableView.h; sourceTree = SOURCE_ROOT; };
CE6DD546124CAF1F0089A48D /* HSTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSTableView.m; path = ../../cocoalib/views/HSTableView.m; sourceTree = SOURCE_ROOT; };
CE6E0DFD1054E9EF008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; };
CE6E7407111C997500C350E3 /* PyDetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDetailsPanel.h; path = ../base/PyDetailsPanel.h; sourceTree = SOURCE_ROOT; };
CE76FDBE111EE37C006618EA /* HSOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HSOutlineView.h; sourceTree = "<group>"; };
@ -110,10 +116,7 @@
CE76FDF6111EE561006618EA /* NSEventAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NSEventAdditions.m; path = ../../cocoalib/NSEventAdditions.m; sourceTree = SOURCE_ROOT; };
CE8C53B61173248F0011B41F /* PyTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PyTable.h; sourceTree = "<group>"; };
CE8C53BB117324CE0011B41F /* HSTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HSTable.m; sourceTree = "<group>"; };
CE91F20F113BC22D0010360B /* PyResultTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyResultTree.h; path = ../base/PyResultTree.h; sourceTree = SOURCE_ROOT; };
CE91F210113BC22D0010360B /* PyStatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyStatsLabel.h; path = ../base/PyStatsLabel.h; sourceTree = SOURCE_ROOT; };
CE91F211113BC22D0010360B /* ResultOutline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultOutline.h; path = ../base/ResultOutline.h; sourceTree = SOURCE_ROOT; };
CE91F212113BC22D0010360B /* ResultOutline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultOutline.m; path = ../base/ResultOutline.m; sourceTree = SOURCE_ROOT; };
CE91F213113BC22D0010360B /* StatsLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StatsLabel.h; path = ../base/StatsLabel.h; sourceTree = SOURCE_ROOT; };
CE91F214113BC22D0010360B /* StatsLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StatsLabel.m; path = ../base/StatsLabel.m; sourceTree = SOURCE_ROOT; };
CEAC6810109B0B7E00B43C85 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Preferences.xib; path = xib/Preferences.xib; sourceTree = "<group>"; };
@ -260,6 +263,8 @@
CE76FDBD111EE37C006618EA /* views */ = {
isa = PBXGroup;
children = (
CE6DD545124CAF1F0089A48D /* HSTableView.h */,
CE6DD546124CAF1F0089A48D /* HSTableView.m */,
CE76FDBE111EE37C006618EA /* HSOutlineView.h */,
CE76FDBF111EE37C006618EA /* HSOutlineView.m */,
CE76FDC0111EE37C006618EA /* NSIndexPathAdditions.h */,
@ -362,10 +367,10 @@
CEFC7FB00FC9518F00CD5728 /* dgbase */ = {
isa = PBXGroup;
children = (
CE91F20F113BC22D0010360B /* PyResultTree.h */,
CE6DD4E4124CA3070089A48D /* PyResultTable.h */,
CE6DD4E5124CA3070089A48D /* ResultTable.h */,
CE6DD4E6124CA3070089A48D /* ResultTable.m */,
CE91F210113BC22D0010360B /* PyStatsLabel.h */,
CE91F211113BC22D0010360B /* ResultOutline.h */,
CE91F212113BC22D0010360B /* ResultOutline.m */,
CE91F213113BC22D0010360B /* StatsLabel.h */,
CE91F214113BC22D0010360B /* StatsLabel.m */,
CE76FDD1111EE3A7006618EA /* DirectoryOutline.h */,
@ -491,10 +496,11 @@
CE76FDDF111EE42F006618EA /* HSOutline.m in Sources */,
CE76FDF7111EE561006618EA /* NSEventAdditions.m in Sources */,
CEBE4D74111F0EE1009AAC6D /* HSWindowController.m in Sources */,
CE91F215113BC22D0010360B /* ResultOutline.m in Sources */,
CE91F216113BC22D0010360B /* StatsLabel.m in Sources */,
CE647E571173024A006D28BA /* ProblemDialog.m in Sources */,
CE8C53BC117324CE0011B41F /* HSTable.m in Sources */,
CE6DD4E7124CA3070089A48D /* ResultTable.m in Sources */,
CE6DD547124CAF1F0089A48D /* HSTableView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -15,7 +15,7 @@ from .gui.details_panel import DetailsPanel
from .gui.directory_tree import DirectoryTree
from .gui.problem_dialog import ProblemDialog
from .gui.problem_table import ProblemTable
from .gui.result_tree import ResultTree
from .gui.result_table import ResultTable
from .gui.stats_label import StatsLabel
# Fix py2app's problems on relative imports
@ -163,8 +163,8 @@ class PyDirectoryOutline(PyOutline):
self.py.add_directory(path)
class PyResultOutline(PyOutline):
py_class = ResultTree
class PyResultTable(PyTable):
py_class = ResultTable
@signature('c@:')
def powerMarkerMode(self):
@ -182,9 +182,9 @@ class PyResultOutline(PyOutline):
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('@@:ii')
def valueForRow_column_(self, row_index, column):
return self.py.get_row_value(row_index, column)
@signature('c@:@')
def renameSelected_(self, newname):
@ -200,8 +200,9 @@ class PyResultOutline(PyOutline):
def removeSelected(self):
self.py.app.remove_selected()
def rootChildrenCounts(self):
return self.py.root_children_counts()
@signature('i@:')
def selectedDupeCount(self):
return self.py.selected_dupe_count
# python --> cocoa
def invalidate_markings(self):

View File

@ -9,14 +9,14 @@
from operator import attrgetter
from hsgui.tree import Tree, Node
from hsgui.table import GUITable, Row
from .base import GUIObject
class DupeNode(Node):
def __init__(self, app, group, dupe):
Node.__init__(self, '')
self._app = app
class DupeRow(Row):
def __init__(self, table, group, dupe):
Row.__init__(self, table)
self._app = table.app
self._group = group
self._dupe = dupe
self._data = None
@ -34,6 +34,10 @@ class DupeNode(Node):
self._data_delta = self._app._get_display_info(self._dupe, self._group, True)
return self._data_delta
@property
def isref(self):
return self._dupe is self._group.ref
@property
def markable(self):
return self._app.results.is_markable(self._dupe)
@ -47,10 +51,10 @@ class DupeNode(Node):
self._app.mark_dupe(self._dupe, value)
class ResultTree(GUIObject, Tree):
class ResultTable(GUIObject, GUITable):
def __init__(self, view, app):
GUIObject.__init__(self, view, app)
Tree.__init__(self)
GUITable.__init__(self)
self._power_marker = False
self._delta_values = False
self._sort_descriptors = (0, True)
@ -58,60 +62,54 @@ class ResultTree(GUIObject, Tree):
#--- Override
def connect(self):
GUIObject.connect(self)
self._refresh()
self.refresh()
self.view.refresh()
def _select_nodes(self, nodes):
Tree._select_nodes(self, nodes)
self.app._select_dupes(list(map(attrgetter('_dupe'), nodes)))
def _restore_selection(self, previous_selection):
if self.app.selected_dupes:
to_find = set(self.app.selected_dupes)
indexes = [i for i, r in enumerate(self) if r._dupe in to_find]
self.selected_indexes = indexes
#--- Private
def _refresh(self):
self.clear()
def _update_selection(self):
rows = self.selected_rows
self.app._select_dupes(list(map(attrgetter('_dupe'), rows)))
def _fill(self):
if not self.power_marker:
for group in self.app.results.groups:
group_node = DupeNode(self.app, group, group.ref)
self.append(group_node)
self.append(DupeRow(self, group, group.ref))
for dupe in group.dupes:
group_node.append(DupeNode(self.app, group, dupe))
self.append(DupeRow(self, 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
self.append(DupeRow(self, group, dupe))
#--- Public
def get_node_value(self, path, column):
def get_row_value(self, index, column):
try:
node = self.get_node(path)
row = self[index]
except IndexError:
return '---'
if self.delta_values:
return node.data_delta[column]
return row.data_delta[column]
else:
return node.data[column]
return row.data[column]
def rename_selected(self, newname):
node = self.selected_node
node._data = None
node._data_delta = None
row = self.selected_row
row._data = None
row._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.refresh()
self.view.refresh()
#--- Properties
@ -126,7 +124,7 @@ class ResultTree(GUIObject, Tree):
self._power_marker = value
key, asc = self._sort_descriptors
self.sort(key, asc)
self._refresh()
self.refresh()
self.view.refresh()
@property
@ -138,22 +136,26 @@ class ResultTree(GUIObject, Tree):
if value == self._delta_values:
return
self._delta_values = value
self._refresh()
self.refresh()
self.view.refresh()
@property
def selected_dupe_count(self):
return sum(1 for row in self.selected_rows if not row.isref)
#--- Event Handlers
def marking_changed(self):
self.view.invalidate_markings()
def results_changed(self):
self._refresh()
self.refresh()
self.view.refresh()
def results_changed_but_keep_selection(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
indexes = self.selected_indexes
self.refresh()
self.select(indexes)
self.view.refresh()

View File

@ -23,7 +23,7 @@ from .. import app, fs, engine
from ..app import DupeGuru as DupeGuruBase
from ..gui.details_panel import DetailsPanel
from ..gui.directory_tree import DirectoryTree
from ..gui.result_tree import ResultTree
from ..gui.result_table import ResultTable
class DupeGuru(DupeGuruBase):
def __init__(self):
@ -166,11 +166,11 @@ class TCDupeGuruWithResults(TestCase):
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.rtable_gui = CallLogger()
self.rtable = ResultTable(self.rtable_gui, self.app)
self.dpanel.connect()
self.dtree.connect()
self.rtree.connect()
self.rtable.connect()
tmppath = self.tmppath()
io.mkdir(tmppath + 'foo')
io.mkdir(tmppath + 'bar')
@ -217,42 +217,35 @@ class TCDupeGuruWithResults(TestCase):
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]
r = self.rtable[0]
assert r._group is groups[0]
assert r._dupe is objects[0]
r = self.rtable[1]
assert r._group is groups[0]
assert r._dupe is objects[1]
r = self.rtable[4]
assert r._group is groups[1]
assert r._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)
self.rtable.sort(0, False) #0 = Filename
r = self.rtable[1]
assert r._group is groups[1]
assert r._dupe is objects[4]
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.rtable.select([1, 2, 3])
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, 0]]) # no exception
eq_(self.rtable.selected_indexes, [1]) # no exception
def test_selectResultNodePaths(self):
app = self.app
objects = self.objects
self.rtree.selected_paths = [[0, 0], [0, 1]]
self.rtable.select([1, 2])
eq_(len(app.selected_dupes), 2)
assert app.selected_dupes[0] is objects[1]
assert app.selected_dupes[1] is objects[2]
@ -260,7 +253,7 @@ class TCDupeGuruWithResults(TestCase):
def test_selectResultNodePaths_with_ref(self):
app = self.app
objects = self.objects
self.rtree.selected_paths = [[0, 0], [0, 1], [1]]
self.rtable.select([1, 2, 3])
eq_(len(app.selected_dupes), 3)
assert app.selected_dupes[0] is objects[1]
assert app.selected_dupes[1] is objects[2]
@ -270,9 +263,9 @@ class TCDupeGuruWithResults(TestCase):
app = self.app
objects = self.objects
groups = self.groups[:] #To keep the old order in memory
self.rtree.sort(0, False) #0 = Filename
self.rtable.sort(0, False) #0 = Filename
#Now, the group order is supposed to be reversed
self.rtree.selected_paths = [[0, 0], [1], [1, 0]]
self.rtable.select([1, 2, 3])
eq_(len(app.selected_dupes), 3)
assert app.selected_dupes[0] is objects[4]
assert app.selected_dupes[1] is groups[0].ref
@ -282,39 +275,26 @@ class TCDupeGuruWithResults(TestCase):
# 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]])
self.rtable.power_marker = True
self.rtable.select([0, 1, 2])
self.rtable.power_marker = False
eq_(self.rtable.selected_indexes, [1, 2, 4])
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]]
self.rtable.power_marker = True
self.rtable.select([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)
eq_(self.rtable.selected_indexes, []) # no exception
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]]
self.rtable.power_marker = True
self.rtable.sort(0, False) #0 = Filename
self.rtable.select([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]
@ -325,7 +305,7 @@ class TCDupeGuruWithResults(TestCase):
objects = self.objects
app.toggle_selected_mark_state()
eq_(app.results.mark_count, 0)
self.rtree.selected_paths = [[0, 0], [1, 0]]
self.rtable.select([1, 4])
app.toggle_selected_mark_state()
eq_(app.results.mark_count, 2)
assert not app.results.is_marked(objects[0])
@ -335,10 +315,10 @@ class TCDupeGuruWithResults(TestCase):
assert app.results.is_marked(objects[4])
def test_refreshDetailsWithSelected(self):
self.rtree.selected_paths = [[0, 0], [1, 0]]
self.rtable.select([1, 4])
eq_(self.dpanel.row(0), ('Filename', 'bar bleh', 'foo bar'))
self.check_gui_calls(self.dpanel_gui, ['refresh'])
self.rtree.selected_paths = []
self.rtable.select([])
eq_(self.dpanel.row(0), ('Filename', '---', '---'))
self.check_gui_calls(self.dpanel_gui, ['refresh'])
@ -346,7 +326,7 @@ class TCDupeGuruWithResults(TestCase):
app = self.app
objects = self.objects
groups = self.groups
self.rtree.selected_paths = [[0, 0], [1, 0]]
self.rtable.select([1, 4])
app.make_selected_reference()
assert groups[0].ref is objects[1]
assert groups[1].ref is objects[4]
@ -355,7 +335,7 @@ class TCDupeGuruWithResults(TestCase):
app = self.app
objects = self.objects
groups = self.groups
self.rtree.selected_paths = [[0, 0], [0, 1], [1, 0]]
self.rtable.select([1, 2, 4])
#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]
@ -363,7 +343,7 @@ class TCDupeGuruWithResults(TestCase):
def test_removeSelected(self):
app = self.app
self.rtree.selected_paths = [[0, 0], [1, 0]]
self.rtable.select([1, 4])
app.remove_selected()
eq_(len(app.results.dupes), 1) # the first path is now selected
app.remove_selected()
@ -386,10 +366,10 @@ class TCDupeGuruWithResults(TestCase):
def test_ignore(self):
app = self.app
self.rtree.selected_path = [1, 0] #The dupe of the second, 2 sized group
self.rtable.select([4]) #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
self.rtable.select([1]) #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)
@ -416,7 +396,7 @@ class TCDupeGuruWithResults(TestCase):
app = self.app
app.scanner.ignore_list.Ignore = FakeIgnore
self.rtree.selected_path = [1, 0]
self.rtable.select([4])
app.add_selected_to_ignore_list()
@ -440,14 +420,14 @@ class TCDupeGuru_renameSelected(TestCase):
self.groups = groups
self.p = p
self.files = files
self.rtree_gui = CallLogger()
self.rtree = ResultTree(self.rtree_gui, self.app)
self.rtree.connect()
self.rtable_gui = CallLogger()
self.rtable = ResultTable(self.rtable_gui, self.app)
self.rtable.connect()
def test_simple(self):
app = self.app
g = self.groups[0]
self.rtree.selected_path = [0, 0]
self.rtable.select([1])
assert app.rename_selected('renamed')
names = io.listdir(self.p)
assert 'renamed' in names
@ -457,7 +437,7 @@ class TCDupeGuru_renameSelected(TestCase):
def test_none_selected(self):
app = self.app
g = self.groups[0]
self.rtree.selected_paths = []
self.rtable.select([])
self.mock(logging, 'warning', log_calls(lambda msg: None))
assert not app.rename_selected('renamed')
msg = logging.warning.calls[0]['msg']
@ -470,7 +450,7 @@ class TCDupeGuru_renameSelected(TestCase):
def test_name_already_exists(self):
app = self.app
g = self.groups[0]
self.rtree.selected_path = [0, 0]
self.rtable.select([1])
self.mock(logging, 'warning', log_calls(lambda msg: None))
assert not app.rename_selected('foo bar 1')
msg = logging.warning.calls[0]['msg']

View File

@ -48,7 +48,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def _setupUi(self):
self.setupUi(self)
# Stuff that can't be setup in the Designer
h = self.resultsView.header()
h = self.resultsView.horizontalHeader()
h.setHighlightSections(False)
h.setMovable(True)
h.setStretchLastSection(False)
@ -110,7 +110,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return answer == QMessageBox.Yes
def _load_columns(self):
h = self.resultsView.header()
h = self.resultsView.horizontalHeader()
h.setResizeMode(QHeaderView.Interactive)
prefs = self.app.prefs
attrs = list(zip(prefs.columns_width, prefs.columns_visible))
@ -120,7 +120,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
h.setResizeMode(0, QHeaderView.Stretch)
def _update_column_actions_status(self):
h = self.resultsView.header()
h = self.resultsView.horizontalHeader()
for action in self._column_actions:
colid = action.column_index
action.setChecked(not h.isSectionHidden(colid))
@ -185,7 +185,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.show_directories()
def exportTriggered(self):
h = self.resultsView.header()
h = self.resultsView.horizontalHeader()
column_ids = []
for i in range(len(self.app.data.COLUMNS)):
if not h.isSectionHidden(i):
@ -278,7 +278,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
#--- Events
def appWillSavePrefs(self):
prefs = self.app.prefs
h = self.resultsView.header()
h = self.resultsView.horizontalHeader()
widths = []
visible = []
for i in range(len(self.app.data.COLUMNS)):
@ -295,7 +295,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.app.prefs.reset_columns()
self._load_columns()
else:
h = self.resultsView.header()
h = self.resultsView.horizontalHeader()
h.setSectionHidden(colid, not h.isSectionHidden(colid))
self._update_column_actions_status()

View File

@ -26,27 +26,21 @@
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="headerStretchLastSection">
<attribute name="verticalHeaderDefaultSectionSize">
<number>18</number>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>18</number>
</attribute>
</widget>
</item>
</layout>
@ -57,7 +51,7 @@
<x>0</x>
<y>0</y>
<width>630</width>
<height>20</height>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuColumns">
@ -462,7 +456,7 @@
<customwidgets>
<customwidget>
<class>ResultsView</class>
<extends>QTreeView</extends>
<extends>QTableView</extends>
<header>.results_model</header>
</customwidget>
</customwidgets>

View File

@ -7,62 +7,20 @@
# http://www.hardcoded.net/licenses/hs_license
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import (QBrush, QStyledItemDelegate, QFont, QTreeView, QColor, QItemSelectionModel,
QItemSelection)
from PyQt4.QtGui import QBrush, QFont, QTableView, QColor, QItemSelectionModel, QItemSelection
from qtlib.tree_model import TreeModel, RefNode
from qtlib.table import Table
from core.gui.result_tree import ResultTree as ResultTreeModel
from core.gui.result_table import ResultTable as ResultTableModel
class ResultsDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
QStyledItemDelegate.initStyleOption(self, option, index)
node = index.internalPointer()
ref = node.ref
if ref._group.ref is ref._dupe:
newfont = QFont(option.font)
newfont.setBold(True)
option.font = newfont
class ResultsModel(TreeModel):
class ResultsModel(Table):
def __init__(self, app, view):
TreeModel.__init__(self)
self.view = view
model = ResultTableModel(self, app)
self._app = app
self._data = app.data
self._delta_columns = app.DELTA_COLUMNS
self.resultsDelegate = ResultsDelegate()
self.model = ResultTreeModel(self, app)
self.view.setItemDelegate(self.resultsDelegate)
self.view.setModel(self)
Table.__init__(self, model, view)
self.model.connect()
self.connect(self.view.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
def _createNode(self, ref, row):
return RefNode(self, None, ref, row)
def _getChildren(self):
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):
return len(self._data.COLUMNS)
@ -70,22 +28,26 @@ class ResultsModel(TreeModel):
def data(self, index, role):
if not index.isValid():
return None
node = index.internalPointer()
ref = node.ref
row = self.model[index.row()]
if role == Qt.DisplayRole:
data = ref.data_delta if self.model.delta_values else ref.data
data = row.data_delta if self.model.delta_values else row.data
return data[index.column()]
elif role == Qt.CheckStateRole:
if index.column() == 0 and ref.markable:
return Qt.Checked if ref.marked else Qt.Unchecked
if index.column() == 0 and row.markable:
return Qt.Checked if row.marked else Qt.Unchecked
elif role == Qt.ForegroundRole:
if ref._dupe is ref._group.ref or ref._dupe.is_ref:
if row.isref:
return QBrush(Qt.blue)
elif self.model.delta_values and index.column() in self._delta_columns:
return QBrush(QColor(255, 142, 40)) # orange
elif role == Qt.FontRole:
isBold = row.isref
font = QFont(self.view.font())
font.setBold(isBold)
return font
elif role == Qt.EditRole:
if index.column() == 0:
return ref.data[index.column()]
return row.data[index.column()]
return None
def flags(self, index):
@ -93,7 +55,10 @@ class ResultsModel(TreeModel):
return Qt.ItemIsEnabled
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 0:
flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
flags |= Qt.ItemIsEditable
row = self.model[index.row()]
if row.markable:
flags |= Qt.ItemIsUserCheckable
return flags
def headerData(self, section, orientation, role):
@ -104,13 +69,12 @@ class ResultsModel(TreeModel):
def setData(self, index, value, role):
if not index.isValid():
return False
node = index.internalPointer()
ref = node.ref
row = self.model[index.row()]
if role == Qt.CheckStateRole:
if index.column() == 0:
self._app.mark_dupe(ref._dupe, value.toBool())
self._app.mark_dupe(row._dupe, value.toBool())
return True
if role == Qt.EditRole:
elif role == Qt.EditRole:
if index.column() == 0:
value = str(value.toString())
return self.model.rename_selected(value)
@ -136,18 +100,7 @@ class ResultsModel(TreeModel):
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.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
@ -155,13 +108,13 @@ class ResultsModel(TreeModel):
self.view.scroll(0, -1)
class ResultsView(QTreeView):
class ResultsView(QTableView):
#--- Override
def keyPressEvent(self, event):
if event.text() == ' ':
self.emit(SIGNAL('spacePressed()'))
return
QTreeView.keyPressEvent(self, event)
QTableView.keyPressEvent(self, event)
def mouseDoubleClickEvent(self, event):
self.emit(SIGNAL('doubleClicked()'))