cocoa: merge se/me/pe into one single app

That merge has already been done in core and qt, we're following.

I broke picture scan details panel image loading. Will fix later.
This commit is contained in:
Virgil Dupras 2016-06-05 21:18:48 -04:00
parent b780816e3c
commit 8c1078aa71
67 changed files with 464 additions and 821 deletions

View File

@ -90,31 +90,27 @@ def build_xibless(dest='cocoa/autogen'):
('prioritize_dialog.py', 'PrioritizeDialog_UI'), ('prioritize_dialog.py', 'PrioritizeDialog_UI'),
('result_window.py', 'ResultWindow_UI'), ('result_window.py', 'ResultWindow_UI'),
('main_menu.py', 'MainMenu_UI'), ('main_menu.py', 'MainMenu_UI'),
('preferences_panel.py', 'PreferencesPanel_UI'), ('details_panel.py', 'DetailsPanel_UI'),
('details_panel_picture.py', 'DetailsPanelPicture_UI'),
] ]
for srcname, dstname in FNPAIRS: for srcname, dstname in FNPAIRS:
xibless.generate( xibless.generate(
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname), op.join('cocoa', 'ui', srcname), op.join(dest, dstname),
localizationTable='Localizable' localizationTable='Localizable'
) )
# XXX This is broken for appmode in ('standard', 'music', 'picture'):
assert False xibless.generate(
# if edition == 'pe': op.join('cocoa', 'ui', 'preferences_panel.py'),
# xibless.generate( op.join(dest, 'PreferencesPanel%s_UI' % appmode.capitalize()),
# 'cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'), localizationTable='Localizable',
# localizationTable='Localizable' args={'appmode': appmode},
# ) )
# else:
# xibless.generate(
# 'cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
# localizationTable='Localizable'
# )
def build_cocoa(dev): def build_cocoa(dev):
print("Creating OS X app structure") print("Creating OS X app structure")
app = cocoa_app() app = cocoa_app()
app_version = get_module_version('core') app_version = get_module_version('core')
cocoa_project_path = 'cocoa/se' cocoa_project_path = 'cocoa'
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version) filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
app.create(op.join('build', 'Info.plist')) app.create(op.join('build', 'Info.plist'))
print("Building localizations") print("Building localizations")
@ -156,8 +152,8 @@ def build_cocoa(dev):
app.copy_executable('cocoa/build/dupeGuru') app.copy_executable('cocoa/build/dupeGuru')
build_help() build_help()
print("Copying resources and frameworks") print("Copying resources and frameworks")
image_path = 'cocoa/se/dupeguru.icns' image_path = 'cocoa/dupeguru.icns'
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help'] resources = [image_path, 'cocoa/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
app.copy_resources(*resources, use_symlinks=dev) app.copy_resources(*resources, use_symlinks=dev)
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework') app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
print("Creating the run.py file") print("Creating the run.py file")
@ -197,7 +193,7 @@ def build_localizations(ui):
loc.compile_all_po('locale') loc.compile_all_po('locale')
if ui == 'cocoa': if ui == 'cocoa':
app = cocoa_app() app = cocoa_app()
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings')) loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'en.lproj', 'Localizable.strings'))
locale_dest = op.join(app.resources, 'locale') locale_dest = op.join(app.resources, 'locale')
elif ui == 'qt': elif ui == 'qt':
build_qt_localizations() build_qt_localizations()
@ -212,7 +208,7 @@ def build_updatepot():
build_cocoalib_xibless('cocoalib/autogen') build_cocoalib_xibless('cocoalib/autogen')
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj') loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
build_xibless('se', op.join('cocoa', 'autogen', 'se')) build_xibless('se', op.join('cocoa', 'autogen', 'se'))
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj') loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/en.lproj')
print("Building .pot files from source files") print("Building .pot files from source files")
print("Building core.pot") print("Building core.pot")
loc.generate_pot(['core'], op.join('locale', 'core.pot'), ['tr']) loc.generate_pot(['core'], op.join('locale', 'core.pot'), ['tr'])
@ -232,7 +228,7 @@ def build_updatepot():
loc.strings2pot(op.join('cocoalib', 'en.lproj', 'cocoalib.strings'), cocoalib_pot) loc.strings2pot(op.join('cocoalib', 'en.lproj', 'cocoalib.strings'), cocoalib_pot)
print("Enhancing ui.pot with Cocoa's strings files") print("Enhancing ui.pot with Cocoa's strings files")
loc.strings2pot( loc.strings2pot(
op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'), op.join('cocoa', 'en.lproj', 'Localizable.strings'),
op.join('locale', 'ui.pot') op.join('locale', 'ui.pot')
) )

View File

@ -14,11 +14,13 @@ http://www.gnu.org/licenses/gpl-3.0.html
#import "DetailsPanel.h" #import "DetailsPanel.h"
#import "DirectoryPanel.h" #import "DirectoryPanel.h"
#import "IgnoreListDialog.h" #import "IgnoreListDialog.h"
#import "ProblemDialog.h"
#import "DeletionOptions.h"
#import "HSAboutBox.h" #import "HSAboutBox.h"
#import "HSRecentFiles.h" #import "HSRecentFiles.h"
#import "HSProgressWindow.h" #import "HSProgressWindow.h"
@interface AppDelegateBase : NSObject <NSFileManagerDelegate> @interface AppDelegate : NSObject <NSFileManagerDelegate>
{ {
NSMenu *recentResultsMenu; NSMenu *recentResultsMenu;
NSMenu *columnsMenu; NSMenu *columnsMenu;
@ -29,6 +31,8 @@ http://www.gnu.org/licenses/gpl-3.0.html
DirectoryPanel *_directoryPanel; DirectoryPanel *_directoryPanel;
DetailsPanel *_detailsPanel; DetailsPanel *_detailsPanel;
IgnoreListDialog *_ignoreListDialog; IgnoreListDialog *_ignoreListDialog;
ProblemDialog *_problemDialog;
DeletionOptions *_deletionOptions;
HSProgressWindow *_progressWindow; HSProgressWindow *_progressWindow;
NSWindowController *_preferencesPanel; NSWindowController *_preferencesPanel;
HSAboutBox *_aboutBox; HSAboutBox *_aboutBox;
@ -43,9 +47,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
+ (NSDictionary *)defaultPreferences; + (NSDictionary *)defaultPreferences;
- (PyDupeGuru *)model; - (PyDupeGuru *)model;
- (DetailsPanel *)createDetailsPanel; - (DetailsPanel *)createDetailsPanel;
- (NSString *)homepageURL;
- (void)setScanOptions; - (void)setScanOptions;
- (void)initResultColumns:(ResultTable *)aTable;
/* Public */ /* Public */
- (void)finalizeInit; - (void)finalizeInit;
@ -53,6 +55,8 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (DirectoryPanel *)directoryPanel; - (DirectoryPanel *)directoryPanel;
- (DetailsPanel *)detailsPanel; - (DetailsPanel *)detailsPanel;
- (HSRecentFiles *)recentResults; - (HSRecentFiles *)recentResults;
- (NSInteger)getAppMode;
- (void)setAppMode:(NSInteger)appMode;
/* Delegate */ /* Delegate */
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@ -62,6 +66,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (void)recentFileClicked:(NSString *)path; - (void)recentFileClicked:(NSString *)path;
/* Actions */ /* Actions */
- (void)clearPictureCache;
- (void)loadResults; - (void)loadResults;
- (void)openWebsite; - (void)openWebsite;
- (void)openHelp; - (void)openHelp;

View File

@ -6,16 +6,19 @@ which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html http://www.gnu.org/licenses/gpl-3.0.html
*/ */
#import "AppDelegateBase.h" #import "AppDelegate.h"
#import "ProgressController.h" #import "ProgressController.h"
#import "HSPyUtil.h" #import "HSPyUtil.h"
#import "Consts.h" #import "Consts.h"
#import "Dialogs.h" #import "Dialogs.h"
#import "Utils.h" #import "Utils.h"
#import "ValueTransformers.h" #import "ValueTransformers.h"
#import "PreferencesPanel_UI.h" #import "DetailsPanelPicture.h"
#import "PreferencesPanelStandard_UI.h"
#import "PreferencesPanelMusic_UI.h"
#import "PreferencesPanelPicture_UI.h"
@implementation AppDelegateBase @implementation AppDelegate
@synthesize recentResultsMenu; @synthesize recentResultsMenu;
@synthesize columnsMenu; @synthesize columnsMenu;
@ -24,6 +27,21 @@ http://www.gnu.org/licenses/gpl-3.0.html
+ (NSDictionary *)defaultPreferences + (NSDictionary *)defaultPreferences
{ {
NSMutableDictionary *d = [NSMutableDictionary dictionary]; NSMutableDictionary *d = [NSMutableDictionary dictionary];
[d setObject:i2n(1) forKey:@"scanTypeStandard"];
[d setObject:i2n(3) forKey:@"scanTypeMusic"];
[d setObject:i2n(0) forKey:@"scanTypePicture"];
[d setObject:i2n(95) forKey:@"minMatchPercentage"];
[d setObject:i2n(30) forKey:@"smallFileThreshold"];
[d setObject:b2n(YES) forKey:@"wordWeighting"];
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"];
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
[d setObject:b2n(NO) forKey:@"scanTagYear"];
[d setObject:b2n(NO) forKey:@"matchScaled"];
[d setObject:i2n(1) forKey:@"recreatePathType"]; [d setObject:i2n(1) forKey:@"recreatePathType"];
[d setObject:i2n(11) forKey:TableFontSize]; [d setObject:i2n(11) forKey:TableFontSize];
[d setObject:b2n(YES) forKey:@"mixFileKind"]; [d setObject:b2n(YES) forKey:@"mixFileKind"];
@ -53,6 +71,20 @@ http://www.gnu.org/licenses/gpl-3.0.html
model = [[PyDupeGuru alloc] init]; model = [[PyDupeGuru alloc] init];
[model bindCallback:createCallback(@"DupeGuruView", self)]; [model bindCallback:createCallback(@"DupeGuruView", self)];
[self setUpdater:[SUUpdater sharedUpdater]]; [self setUpdater:[SUUpdater sharedUpdater]];
NSMutableIndexSet *contentsIndexes = [NSMutableIndexSet indexSet];
[contentsIndexes addIndex:1];
[contentsIndexes addIndex:2];
VTIsIntIn *vt = [[[VTIsIntIn alloc] initWithValues:contentsIndexes reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vt forName:@"vtScanTypeIsNotContent"];
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0];
VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"];
i = [NSMutableIndexSet indexSetWithIndex:4];
[i addIndex:5];
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeMusicIsNotContent"];
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"];
return self; return self;
} }
@ -69,14 +101,17 @@ http://www.gnu.org/licenses/gpl-3.0.html
} }
_recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu]; _recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu];
[_recentResults setDelegate:self]; [_recentResults setDelegate:self];
_resultWindow = [[ResultWindow alloc] initWithParentApp:self];
_directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self]; _directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self];
_detailsPanel = [self createDetailsPanel];
_ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]]; _ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]];
_problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]];
_deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]];
_progressWindow = [[HSProgressWindow alloc] initWithPyRef:[[self model] progressWindow] view:nil]; _progressWindow = [[HSProgressWindow alloc] initWithPyRef:[[self model] progressWindow] view:nil];
[_progressWindow setParentWindow:[_directoryPanel window]]; [_progressWindow setParentWindow:[_directoryPanel window]];
_aboutBox = nil; // Lazily loaded // Lazily loaded
_preferencesPanel = nil; // Lazily loaded _aboutBox = nil;
_preferencesPanel = nil;
_resultWindow = nil;
_detailsPanel = nil;
[[[self directoryPanel] window] makeKeyAndOrderFront:self]; [[[self directoryPanel] window] makeKeyAndOrderFront:self];
} }
@ -89,20 +124,45 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (DetailsPanel *)createDetailsPanel - (DetailsPanel *)createDetailsPanel
{ {
return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]]; NSInteger appMode = [self getAppMode];
} if (appMode == AppModePicture) {
return [[DetailsPanelPicture alloc] initWithPyRef:[model detailsPanel]];
- (NSString *)homepageURL }
{ else {
return @""; // must be overriden by all editions return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]];
}
} }
- (void)setScanOptions - (void)setScanOptions
{ {
} NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSString *scanTypeOptionName;
- (void)initResultColumns:(ResultTable *)aTable NSInteger appMode = [self getAppMode];
{ if (appMode == AppModePicture) {
scanTypeOptionName = @"scanTypePicture";
}
else if (appMode == AppModeMusic) {
scanTypeOptionName = @"scanTypeMusic";
}
else {
scanTypeOptionName = @"scanTypeStandard";
}
[model setScanType:n2i([ud objectForKey:scanTypeOptionName])];
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])];
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
[model setMatchSimilarWords:n2b([ud objectForKey:@"matchSimilarWords"])];
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes
[model setSizeThreshold:sizeThreshold];
[model enable:n2b([ud objectForKey:@"scanTagTrack"]) scanForTag:@"track"];
[model enable:n2b([ud objectForKey:@"scanTagArtist"]) scanForTag:@"artist"];
[model enable:n2b([ud objectForKey:@"scanTagAlbum"]) scanForTag:@"album"];
[model enable:n2b([ud objectForKey:@"scanTagTitle"]) scanForTag:@"title"];
[model enable:n2b([ud objectForKey:@"scanTagGenre"]) scanForTag:@"genre"];
[model enable:n2b([ud objectForKey:@"scanTagYear"]) scanForTag:@"year"];
[model setMatchScaled:n2b([ud objectForKey:@"matchScaled"])];
} }
/* Public */ /* Public */
@ -126,7 +186,29 @@ http://www.gnu.org/licenses/gpl-3.0.html
return _recentResults; return _recentResults;
} }
- (NSInteger)getAppMode
{
return [model getAppMode];
}
- (void)setAppMode:(NSInteger)appMode
{
[model setAppMode:appMode];
if (_preferencesPanel != nil) {
[_preferencesPanel release];
_preferencesPanel = nil;
}
}
/* Actions */ /* Actions */
- (void)clearPictureCache
{
NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @"");
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
return;
[model clearPictureCache];
}
- (void)loadResults - (void)loadResults
{ {
NSOpenPanel *op = [NSOpenPanel openPanel]; NSOpenPanel *op = [NSOpenPanel openPanel];
@ -145,7 +227,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (void)openWebsite - (void)openWebsite
{ {
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[self homepageURL]]]; [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru/"]];
} }
- (void)openHelp - (void)openHelp
@ -172,7 +254,18 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (void)showPreferencesPanel - (void)showPreferencesPanel
{ {
if (_preferencesPanel == nil) { if (_preferencesPanel == nil) {
_preferencesPanel = [[NSWindowController alloc] initWithWindow:createPreferencesPanel_UI(nil)]; NSWindow *window;
NSInteger appMode = [model getAppMode];
if (appMode == AppModePicture) {
window = createPreferencesPanelPicture_UI(nil);
}
else if (appMode == AppModeMusic) {
window = createPreferencesPanelMusic_UI(nil);
}
else {
window = createPreferencesPanelStandard_UI(nil);
}
_preferencesPanel = [[NSWindowController alloc] initWithWindow:window];
} }
[_preferencesPanel showWindow:nil]; [_preferencesPanel showWindow:nil];
} }
@ -252,6 +345,17 @@ http://www.gnu.org/licenses/gpl-3.0.html
return [Dialogs askYesNo:prompt] == NSAlertFirstButtonReturn; return [Dialogs askYesNo:prompt] == NSAlertFirstButtonReturn;
} }
- (void)createResultsWindow
{
if (_resultWindow != nil) {
[_resultWindow release];
}
if (_detailsPanel != nil) {
[_detailsPanel release];
}
_resultWindow = [[ResultWindow alloc] initWithParentApp:self];
_detailsPanel = [self createDetailsPanel];
}
- (void)showResultsWindow - (void)showResultsWindow
{ {
[[[self resultWindow] window] makeKeyAndOrderFront:nil]; [[[self resultWindow] window] makeKeyAndOrderFront:nil];
@ -259,7 +363,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (void)showProblemDialog - (void)showProblemDialog
{ {
[[self resultWindow] showProblemDialog]; [_problemDialog showWindow:self];
} }
- (NSString *)selectDestFolderWithPrompt:(NSString *)prompt - (NSString *)selectDestFolderWithPrompt:(NSString *)prompt

View File

@ -17,3 +17,8 @@ http://www.gnu.org/licenses/gpl-3.0.html
#define jobDelete @"job_delete" #define jobDelete @"job_delete"
#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType" #define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType"
#define ImageLoadedNotification @"ImageLoadedNotification"
#define AppModeStandard 0
#define AppModeMusic 1
#define AppModePicture 2

View File

@ -10,7 +10,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
#import <Python.h> #import <Python.h>
#import "PyDetailsPanel.h" #import "PyDetailsPanel.h"
@interface DetailsPanelBase : NSWindowController <NSTableViewDataSource> @interface DetailsPanel : NSWindowController <NSTableViewDataSource>
{ {
NSTableView *detailsTable; NSTableView *detailsTable;

View File

@ -6,10 +6,11 @@ which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html http://www.gnu.org/licenses/gpl-3.0.html
*/ */
#import "DetailsPanelBase.h" #import "DetailsPanel.h"
#import "HSPyUtil.h" #import "HSPyUtil.h"
#import "DetailsPanel_UI.h"
@implementation DetailsPanelBase @implementation DetailsPanel
@synthesize detailsTable; @synthesize detailsTable;
@ -35,7 +36,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (NSWindow *)createWindow - (NSWindow *)createWindow
{ {
return nil; // Virtual return createDetailsPanel_UI(self);
} }
- (void)refreshDetails - (void)refreshDetails

View File

@ -7,10 +7,10 @@ http://www.gnu.org/licenses/gpl-3.0.html
*/ */
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import "DetailsPanelBase.h" #import "DetailsPanel.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
@interface DetailsPanel : DetailsPanelBase @interface DetailsPanelPicture : DetailsPanel
{ {
NSImageView *dupeImage; NSImageView *dupeImage;
NSProgressIndicator *dupeProgressIndicator; NSProgressIndicator *dupeProgressIndicator;

View File

@ -10,11 +10,11 @@ http://www.gnu.org/licenses/gpl-3.0.html
#import "NSNotificationAdditions.h" #import "NSNotificationAdditions.h"
#import "NSImageAdditions.h" #import "NSImageAdditions.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
#import "DetailsPanel.h" #import "DetailsPanelPicture.h"
#import "Consts.h" #import "Consts.h"
#import "DetailsPanel_UI.h" #import "DetailsPanelPicture_UI.h"
@implementation DetailsPanel @implementation DetailsPanelPicture
@synthesize dupeImage; @synthesize dupeImage;
@synthesize dupeProgressIndicator; @synthesize dupeProgressIndicator;
@ -32,7 +32,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
- (NSWindow *)createWindow - (NSWindow *)createWindow
{ {
return createDetailsPanel_UI(self); return createDetailsPanelPicture_UI(self);
} }
- (void)loadImageAsync:(NSString *)imagePath - (void)loadImageAsync:(NSString *)imagePath

View File

@ -12,15 +12,16 @@ http://www.gnu.org/licenses/gpl-3.0.html
#import "DirectoryOutline.h" #import "DirectoryOutline.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
@class AppDelegateBase; @class AppDelegate;
@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate> @interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate>
{ {
AppDelegateBase *_app; AppDelegate *_app;
PyDupeGuru *model; PyDupeGuru *model;
HSRecentFiles *_recentDirectories; HSRecentFiles *_recentDirectories;
DirectoryOutline *outline; DirectoryOutline *outline;
BOOL _alwaysShowPopUp; BOOL _alwaysShowPopUp;
NSSegmentedControl *appModeSelector;
NSPopUpButton *scanTypePopup; NSPopUpButton *scanTypePopup;
NSPopUpButton *addButtonPopUp; NSPopUpButton *addButtonPopUp;
NSPopUpButton *loadRecentButtonPopUp; NSPopUpButton *loadRecentButtonPopUp;
@ -29,6 +30,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
NSButton *loadResultsButton; NSButton *loadResultsButton;
} }
@property (readwrite, retain) NSSegmentedControl *appModeSelector;
@property (readwrite, retain) NSPopUpButton *scanTypePopup; @property (readwrite, retain) NSPopUpButton *scanTypePopup;
@property (readwrite, retain) NSPopUpButton *addButtonPopUp; @property (readwrite, retain) NSPopUpButton *addButtonPopUp;
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp; @property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp;
@ -36,9 +38,10 @@ http://www.gnu.org/licenses/gpl-3.0.html
@property (readwrite, retain) NSButton *removeButton; @property (readwrite, retain) NSButton *removeButton;
@property (readwrite, retain) NSButton *loadResultsButton; @property (readwrite, retain) NSButton *loadResultsButton;
- (id)initWithParentApp:(AppDelegateBase *)aParentApp; - (id)initWithParentApp:(AppDelegate *)aParentApp;
- (void)fillPopUpMenu; // Virtual - (void)fillPopUpMenu;
- (void)fillScanTypeMenu;
- (void)adjustUIToLocalization; - (void)adjustUIToLocalization;
- (void)askForDirectory; - (void)askForDirectory;

View File

@ -11,9 +11,11 @@ http://www.gnu.org/licenses/gpl-3.0.html
#import "Dialogs.h" #import "Dialogs.h"
#import "Utils.h" #import "Utils.h"
#import "AppDelegate.h" #import "AppDelegate.h"
#import "Consts.h"
@implementation DirectoryPanel @implementation DirectoryPanel
@synthesize appModeSelector;
@synthesize scanTypePopup; @synthesize scanTypePopup;
@synthesize addButtonPopUp; @synthesize addButtonPopUp;
@synthesize loadRecentButtonPopUp; @synthesize loadRecentButtonPopUp;
@ -21,15 +23,15 @@ http://www.gnu.org/licenses/gpl-3.0.html
@synthesize removeButton; @synthesize removeButton;
@synthesize loadResultsButton; @synthesize loadResultsButton;
- (id)initWithParentApp:(AppDelegateBase *)aParentApp - (id)initWithParentApp:(AppDelegate *)aParentApp
{ {
self = [super initWithWindow:nil]; self = [super initWithWindow:nil];
[self setWindow:createDirectoryPanel_UI(self)]; [self setWindow:createDirectoryPanel_UI(self)];
_app = aParentApp; _app = aParentApp;
model = [_app model]; model = [_app model];
[[self window] setTitle:[model appName]]; [[self window] setTitle:[model appName]];
[[self scanTypePopup] addItemsWithTitles:[[aParentApp model] getScanOptions]]; self.appModeSelector.selectedSegment = 0;
[[self scanTypePopup] bind:@"selectedIndex" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.scanType" options:nil]; [self fillScanTypeMenu];
_alwaysShowPopUp = NO; _alwaysShowPopUp = NO;
[self fillPopUpMenu]; [self fillPopUpMenu];
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]]; _recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
@ -62,6 +64,25 @@ http://www.gnu.org/licenses/gpl-3.0.html
[m addItem:[NSMenuItem separatorItem]]; [m addItem:[NSMenuItem separatorItem]];
} }
- (void)fillScanTypeMenu
{
[[self scanTypePopup] unbind:@"selectedIndex"];
[[self scanTypePopup] removeAllItems];
[[self scanTypePopup] addItemsWithTitles:[[_app model] getScanOptions]];
NSString *keypath;
NSInteger appMode = [_app getAppMode];
if (appMode == AppModePicture) {
keypath = @"values.scanTypePicture";
}
else if (appMode == AppModeMusic) {
keypath = @"values.scanTypeMusic";
}
else {
keypath = @"values.scanTypeStandard";
}
[[self scanTypePopup] bind:@"selectedIndex" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:keypath options:nil];
}
- (void)adjustUIToLocalization - (void)adjustUIToLocalization
{ {
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0]; NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
@ -100,6 +121,23 @@ http://www.gnu.org/licenses/gpl-3.0.html
} }
} }
- (void)changeAppMode:(id)sender
{
NSInteger appMode;
NSUInteger selectedSegment = self.appModeSelector.selectedSegment;
if (selectedSegment == 2) {
appMode = AppModePicture;
}
else if (selectedSegment == 1) {
appMode = AppModeMusic;
}
else {
appMode = AppModeStandard;
}
[_app setAppMode:appMode];
[self fillScanTypeMenu];
}
- (void)popupAddDirectoryMenu:(id)sender - (void)popupAddDirectoryMenu:(id)sender
{ {
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) { if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {

View File

@ -10,12 +10,10 @@ http://www.gnu.org/licenses/gpl-3.0.html
#import <Quartz/Quartz.h> #import <Quartz/Quartz.h>
#import "StatsLabel.h" #import "StatsLabel.h"
#import "ResultTable.h" #import "ResultTable.h"
#import "ProblemDialog.h"
#import "DeletionOptions.h"
#import "HSTableView.h" #import "HSTableView.h"
#import "PyDupeGuru.h" #import "PyDupeGuru.h"
@class AppDelegateBase; @class AppDelegate;
@interface ResultWindow : NSWindowController @interface ResultWindow : NSWindowController
{ {
@ -26,12 +24,10 @@ http://www.gnu.org/licenses/gpl-3.0.html
NSTextField *stats; NSTextField *stats;
NSSearchField *filterField; NSSearchField *filterField;
AppDelegateBase *app; AppDelegate *app;
PyDupeGuru *model; PyDupeGuru *model;
ResultTable *table; ResultTable *table;
StatsLabel *statsLabel; StatsLabel *statsLabel;
ProblemDialog *problemDialog;
DeletionOptions *deletionOptions;
QLPreviewPanel* previewPanel; QLPreviewPanel* previewPanel;
} }
@ -41,13 +37,13 @@ http://www.gnu.org/licenses/gpl-3.0.html
@property (readwrite, retain) NSTextField *stats; @property (readwrite, retain) NSTextField *stats;
@property (readwrite, retain) NSSearchField *filterField; @property (readwrite, retain) NSSearchField *filterField;
- (id)initWithParentApp:(AppDelegateBase *)app; - (id)initWithParentApp:(AppDelegate *)app;
/* Helpers */ /* Helpers */
- (void)fillColumnsMenu; - (void)fillColumnsMenu;
- (void)updateOptionSegments; - (void)updateOptionSegments;
- (void)showProblemDialog;
- (void)adjustUIToLocalization; - (void)adjustUIToLocalization;
- (void)initResultColumns:(ResultTable *)aTable;
/* Actions */ /* Actions */
- (void)changeOptions; - (void)changeOptions;

View File

@ -23,7 +23,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
@synthesize stats; @synthesize stats;
@synthesize filterField; @synthesize filterField;
- (id)initWithParentApp:(AppDelegateBase *)aApp; - (id)initWithParentApp:(AppDelegate *)aApp;
{ {
self = [super initWithWindow:nil]; self = [super initWithWindow:nil];
app = aApp; app = aApp;
@ -34,9 +34,7 @@ http://www.gnu.org/licenses/gpl-3.0.html
[[self window] setContentBorderThickness:28 forEdge:NSMinYEdge]; [[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];
table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches]; table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches];
statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats]; statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats];
problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]]; [self initResultColumns:table];
deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]];
[aApp initResultColumns:table];
[[table columns] setColumnsAsReadOnly]; [[table columns] setColumnsAsReadOnly];
[self fillColumnsMenu]; [self fillColumnsMenu];
[matches setTarget:self]; [matches setTarget:self];
@ -49,7 +47,6 @@ http://www.gnu.org/licenses/gpl-3.0.html
{ {
[table release]; [table release];
[statsLabel release]; [statsLabel release];
[problemDialog release];
[super dealloc]; [super dealloc];
} }
@ -80,11 +77,6 @@ http://www.gnu.org/licenses/gpl-3.0.html
[optionsSwitch setSelected:[table deltaValuesMode] forSegment:2]; [optionsSwitch setSelected:[table deltaValuesMode] forSegment:2];
} }
- (void)showProblemDialog
{
[problemDialog showWindow:self];
}
- (void)adjustUIToLocalization - (void)adjustUIToLocalization
{ {
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0]; NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
@ -109,6 +101,87 @@ http://www.gnu.org/licenses/gpl-3.0.html
} }
} }
- (void)initResultColumns:(ResultTable *)aTable
{
NSInteger appMode = [app getAppMode];
if (appMode == AppModePicture) {
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 162, 16, 0, YES, nil},
{@"folder_path", 142, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"dimensions", 73, 16, 0, YES, nil},
{@"exif_timestamp", 120, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"percentage", 58, 16, 0, YES, nil},
{@"dupe_count", 80, 16, 0, YES, nil},
nil
};
[[aTable columns] initializeColumns:defs];
NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
[[c dataCell] setButtonType:NSSwitchButton];
[[c dataCell] setControlSize:NSSmallControlSize];
c = [[aTable view] tableColumnWithIdentifier:@"size"];
[[c dataCell] setAlignment:NSRightTextAlignment];
}
else if (appMode == AppModeMusic) {
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 235, 16, 0, YES, nil},
{@"folder_path", 120, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"duration", 50, 16, 0, YES, nil},
{@"bitrate", 50, 16, 0, YES, nil},
{@"samplerate", 60, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"title", 120, 16, 0, YES, nil},
{@"artist", 120, 16, 0, YES, nil},
{@"album", 120, 16, 0, YES, nil},
{@"genre", 80, 16, 0, YES, nil},
{@"year", 40, 16, 0, YES, nil},
{@"track", 40, 16, 0, YES, nil},
{@"comment", 120, 16, 0, YES, nil},
{@"percentage", 57, 16, 0, YES, nil},
{@"words", 120, 16, 0, YES, nil},
{@"dupe_count", 80, 16, 0, YES, nil},
nil
};
[[aTable columns] initializeColumns:defs];
NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
[[c dataCell] setButtonType:NSSwitchButton];
[[c dataCell] setControlSize:NSSmallControlSize];
c = [[aTable view] tableColumnWithIdentifier:@"size"];
[[c dataCell] setAlignment:NSRightTextAlignment];
c = [[aTable view] tableColumnWithIdentifier:@"duration"];
[[c dataCell] setAlignment:NSRightTextAlignment];
c = [[aTable view] tableColumnWithIdentifier:@"bitrate"];
[[c dataCell] setAlignment:NSRightTextAlignment];
}
else {
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 195, 16, 0, YES, nil},
{@"folder_path", 183, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"percentage", 60, 16, 0, YES, nil},
{@"words", 120, 16, 0, YES, nil},
{@"dupe_count", 80, 16, 0, YES, nil},
nil
};
[[aTable columns] initializeColumns:defs];
NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
[[c dataCell] setButtonType:NSSwitchButton];
[[c dataCell] setControlSize:NSSmallControlSize];
c = [[aTable view] tableColumnWithIdentifier:@"size"];
[[c dataCell] setAlignment:NSRightTextAlignment];
}
[[aTable columns] restoreColumns];
}
/* Actions */ /* Actions */
- (void)changeOptions - (void)changeOptions
{ {

View File

@ -6,6 +6,7 @@ from cocoa.inter import PyBaseApp, BaseAppView
class DupeGuruView(BaseAppView): class DupeGuruView(BaseAppView):
def askYesNoWithPrompt_(self, prompt: str) -> bool: pass def askYesNoWithPrompt_(self, prompt: str) -> bool: pass
def createResultsWindow(self): pass
def showResultsWindow(self): pass def showResultsWindow(self): pass
def showProblemDialog(self): pass def showProblemDialog(self): pass
def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass
@ -123,6 +124,9 @@ class PyDupeGuruBase(PyBaseApp):
def showIgnoreList(self): def showIgnoreList(self):
self.model.ignore_list_dialog.show() self.model.ignore_list_dialog.show()
def clearPictureCache(self):
self.model.clear_picture_cache()
#---Information #---Information
def getScanOptions(self) -> list: def getScanOptions(self) -> list:
return [o.label for o in self.model.SCANNER_CLASS.get_scan_options()] return [o.label for o in self.model.SCANNER_CLASS.get_scan_options()]
@ -130,7 +134,50 @@ class PyDupeGuruBase(PyBaseApp):
def resultsAreModified(self) -> bool: def resultsAreModified(self) -> bool:
return self.model.results.is_modified return self.model.results.is_modified
def getSelectedDupePath(self) -> str:
return str(self.model.selected_dupe_path())
def getSelectedDupeRefPath(self) -> str:
return str(self.model.selected_dupe_ref_path())
#---Properties #---Properties
def getAppMode(self) -> int:
return self.model.app_mode
def setAppMode_(self, app_mode: int):
self.model.app_mode = app_mode
def setScanType_(self, scan_type_index: int):
scan_options = self.model.SCANNER_CLASS.get_scan_options()
try:
so = scan_options[scan_type_index]
self.model.options['scan_type'] = so.scan_type
except IndexError:
pass
def setMinMatchPercentage_(self, percentage: int):
self.model.options['min_match_percentage'] = int(percentage)
def setWordWeighting_(self, words_are_weighted: bool):
self.model.options['word_weighting'] = words_are_weighted
def setMatchSimilarWords_(self, match_similar_words: bool):
self.model.options['match_similar_words'] = match_similar_words
def setSizeThreshold_(self, size_threshold: int):
self.model.options['size_threshold'] = size_threshold
def enable_scanForTag_(self, enable: bool, scan_tag: str):
if 'scanned_tags' not in self.model.options:
self.model.options['scanned_tags'] = set()
if enable:
self.model.options['scanned_tags'].add(scan_tag)
else:
self.model.options['scanned_tags'].discard(scan_tag)
def setMatchScaled_(self, match_scaled: bool):
self.model.options['match_scaled'] = match_scaled
def setMixFileKind_(self, mix_file_kind: bool): def setMixFileKind_(self, mix_file_kind: bool):
self.model.options['mix_file_kind'] = mix_file_kind self.model.options['mix_file_kind'] = mix_file_kind
@ -151,6 +198,10 @@ class PyDupeGuruBase(PyBaseApp):
def ask_yes_no(self, prompt): def ask_yes_no(self, prompt):
return self.callback.askYesNoWithPrompt_(prompt) return self.callback.askYesNoWithPrompt_(prompt)
@dontwrap
def create_results_window(self):
self.callback.createResultsWindow()
@dontwrap @dontwrap
def show_results_window(self): def show_results_window(self):
self.callback.showResultsWindow() self.callback.showResultsWindow()

View File

@ -1,48 +0,0 @@
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from hscommon.trans import trget
from core.scanner import ScanType
from core_me.app import DupeGuru as DupeGuruME
from .app import PyDupeGuruBase
tr = trget('ui')
class PyDupeGuru(PyDupeGuruBase):
def __init__(self):
self._init(DupeGuruME)
#---Properties
def setMinMatchPercentage_(self, percentage: int):
self.model.options['min_match_percentage'] = percentage
def setScanType_(self, scan_type: int):
try:
self.model.options['scan_type'] = [
ScanType.Filename,
ScanType.Fields,
ScanType.FieldsNoOrder,
ScanType.Tag,
ScanType.Contents,
ScanType.ContentsAudio,
][scan_type]
except IndexError:
pass
def setWordWeighting_(self, words_are_weighted: bool):
self.model.options['word_weighting'] = words_are_weighted
def setMatchSimilarWords_(self, match_similar_words: bool):
self.model.options['match_similar_words'] = match_similar_words
def enable_scanForTag_(self, enable: bool, scan_tag: str):
if 'scanned_tags' not in self.model.options:
self.model.options['scanned_tags'] = set()
if enable:
self.model.options['scanned_tags'].add(scan_tag)
else:
self.model.options['scanned_tags'].discard(scan_tag)

View File

@ -1,90 +0,0 @@
# Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from cocoa import proxy
from core.scanner import ScanType
from core_pe import _block_osx
from core_pe.photo import Photo as PhotoBase
from core_pe.app import DupeGuru as DupeGuruBase
from .app import PyDupeGuruBase
class Photo(PhotoBase):
HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy()
HANDLED_EXTS.update({'psd', 'nef', 'cr2', 'orf'})
def _plat_get_dimensions(self):
return _block_osx.get_image_size(str(self.path))
def _plat_get_blocks(self, block_count_per_side, orientation):
try:
blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation)
except Exception as e:
raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e)))
if not blocks:
raise IOError('The picture %s could not be read' % str(self.path))
return blocks
def _get_exif_timestamp(self):
exifdata = proxy.readExifData_(str(self.path))
if exifdata:
try:
return exifdata['{Exif}']['DateTimeOriginal']
except KeyError:
return ''
else:
return ''
class DupeGuruPE(DupeGuruBase):
def __init__(self, view):
DupeGuruBase.__init__(self, view)
self.fileclasses = [Photo]
def selected_dupe_path(self):
if not self.selected_dupes:
return None
return self.selected_dupes[0].path
def selected_dupe_ref_path(self):
if not self.selected_dupes:
return None
ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref
if ref is self.selected_dupes[0]: # we don't want the same pic to be displayed on both sides
return None
return ref.path
class PyDupeGuru(PyDupeGuruBase):
def __init__(self):
self._init(DupeGuruPE)
def clearPictureCache(self):
self.model.clear_picture_cache()
#---Information
def getSelectedDupePath(self) -> str:
return str(self.model.selected_dupe_path())
def getSelectedDupeRefPath(self) -> str:
return str(self.model.selected_dupe_ref_path())
#---Properties
def setScanType_(self, scan_type: int):
try:
self.model.options['scan_type'] = [
ScanType.FuzzyBlock,
ScanType.ExifTimestamp,
][scan_type]
except IndexError:
pass
def setMatchScaled_(self, match_scaled: bool):
self.model.options['match_scaled'] = match_scaled
def setMinMatchPercentage_(self, percentage: int):
self.model.options['threshold'] = percentage

View File

@ -12,10 +12,12 @@ import os.path as op
from hscommon.path import Path, pathify from hscommon.path import Path, pathify
from cocoa import proxy from cocoa import proxy
from core.scanner import ScanType
from core.directories import Directories as DirectoriesBase, DirectoryState from core.directories import Directories as DirectoriesBase, DirectoryState
from core_se.app import DupeGuru as DupeGuruBase import core.pe.photo
from core_se import fs from core.pe import _block_osx
from core.pe.photo import Photo as PhotoBase
from core.app import DupeGuru as DupeGuruBase, AppMode
from core.se import fs
from .app import PyDupeGuruBase from .app import PyDupeGuruBase
def is_bundle(str_path): def is_bundle(str_path):
@ -31,13 +33,37 @@ class Bundle(fs.Folder):
return not path.islink() and path.isdir() and is_bundle(str(path)) return not path.islink() and path.isdir() and is_bundle(str(path))
class Photo(PhotoBase):
HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy()
HANDLED_EXTS.update({'psd', 'nef', 'cr2', 'orf'})
def _plat_get_dimensions(self):
return _block_osx.get_image_size(str(self.path))
def _plat_get_blocks(self, block_count_per_side, orientation):
try:
blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation)
except Exception as e:
raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e)))
if not blocks:
raise IOError('The picture %s could not be read' % str(self.path))
return blocks
def _get_exif_timestamp(self):
exifdata = proxy.readExifData_(str(self.path))
if exifdata:
try:
return exifdata['{Exif}']['DateTimeOriginal']
except KeyError:
return ''
else:
return ''
class Directories(DirectoriesBase): class Directories(DirectoriesBase):
ROOT_PATH_TO_EXCLUDE = list(map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev'])) ROOT_PATH_TO_EXCLUDE = list(map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev']))
HOME_PATH_TO_EXCLUDE = [Path('Library')] HOME_PATH_TO_EXCLUDE = [Path('Library')]
def __init__(self):
DirectoriesBase.__init__(self)
self.folderclass = fs.Folder
def _default_state_for_path(self, path): def _default_state_for_path(self, path):
result = DirectoriesBase._default_state_for_path(self, path) result = DirectoriesBase._default_state_for_path(self, path)
if result is not None: if result is not None:
@ -58,8 +84,7 @@ class Directories(DirectoriesBase):
yield from_folder yield from_folder
return return
else: else:
for folder in DirectoriesBase._get_folders(self, from_folder, j): yield from DirectoriesBase._get_folders(self, from_folder, j)
yield folder
@staticmethod @staticmethod
def get_subfolders(path): def get_subfolders(path):
@ -69,37 +94,31 @@ class Directories(DirectoriesBase):
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
def __init__(self, view): def __init__(self, view):
# appdata = op.join(appdata, 'dupeGuru')
# print(repr(appdata))
DupeGuruBase.__init__(self, view) DupeGuruBase.__init__(self, view)
self.fileclasses = [Bundle, fs.File]
self.directories = Directories() self.directories = Directories()
def selected_dupe_path(self):
if not self.selected_dupes:
return None
return self.selected_dupes[0].path
def selected_dupe_ref_path(self):
if not self.selected_dupes:
return None
ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref
if ref is self.selected_dupes[0]: # we don't want the same pic to be displayed on both sides
return None
return ref.path
def _get_fileclasses(self):
result = DupeGuruBase._get_fileclasses(self)
if self.app_mode == AppMode.Standard:
result = [Bundle] + result
return result
class PyDupeGuru(PyDupeGuruBase): class PyDupeGuru(PyDupeGuruBase):
def __init__(self): def __init__(self):
core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = Photo
self._init(DupeGuru) self._init(DupeGuru)
#---Properties
def setMinMatchPercentage_(self, percentage: int):
self.model.options['min_match_percentage'] = int(percentage)
def setScanType_(self, scan_type: int):
try:
self.model.options['scan_type'] = [
ScanType.Filename,
ScanType.Contents,
ScanType.Folders,
][scan_type]
except IndexError:
pass
def setWordWeighting_(self, words_are_weighted: bool):
self.model.options['word_weighting'] = words_are_weighted
def setMatchSimilarWords_(self, match_similar_words: bool):
self.model.options['match_similar_words'] = match_similar_words
def setSizeThreshold_(self, size_threshold: int):
self.model.options['size_threshold'] = size_threshold

View File

@ -1,15 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "AppDelegateBase.h"
#import "ResultWindow.h"
#import "PyDupeGuru.h"
@interface AppDelegate : AppDelegateBase {}
@end

View File

@ -1,106 +0,0 @@
/*
Copyright 2016 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "AppDelegate.h"
#import "ProgressController.h"
#import "Utils.h"
#import "ValueTransformers.h"
#import "Dialogs.h"
#import "DetailsPanel.h"
#import "ResultWindow.h"
#import "Consts.h"
@implementation AppDelegate
+ (NSDictionary *)defaultPreferences
{
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
[d setObject:i2n(3) forKey:@"scanType"];
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
[d setObject:b2n(NO) forKey:@"wordWeighting"];
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
[d setObject:b2n(NO) forKey:@"scanTagYear"];
return d;
}
- (id)init
{
self = [super init];
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:4];
[i addIndex:5];
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeIsNotContent"];
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"];
_directoryPanel = nil;
return self;
}
- (NSString *)homepageURL
{
return @"https://www.hardcoded.net/dupeguru_me/";
}
- (void)setScanOptions
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[model setScanType:n2i([ud objectForKey:@"scanType"])];
[model enable:n2b([ud objectForKey:@"scanTagTrack"]) scanForTag:@"track"];
[model enable:n2b([ud objectForKey:@"scanTagArtist"]) scanForTag:@"artist"];
[model enable:n2b([ud objectForKey:@"scanTagAlbum"]) scanForTag:@"album"];
[model enable:n2b([ud objectForKey:@"scanTagTitle"]) scanForTag:@"title"];
[model enable:n2b([ud objectForKey:@"scanTagGenre"]) scanForTag:@"genre"];
[model enable:n2b([ud objectForKey:@"scanTagYear"]) scanForTag:@"year"];
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])];
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
[model setMatchSimilarWords:n2b([ud objectForKey:@"matchSimilarWords"])];
}
- (void)initResultColumns:(ResultTable *)aTable
{
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 235, 16, 0, YES, nil},
{@"folder_path", 120, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"duration", 50, 16, 0, YES, nil},
{@"bitrate", 50, 16, 0, YES, nil},
{@"samplerate", 60, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"title", 120, 16, 0, YES, nil},
{@"artist", 120, 16, 0, YES, nil},
{@"album", 120, 16, 0, YES, nil},
{@"genre", 80, 16, 0, YES, nil},
{@"year", 40, 16, 0, YES, nil},
{@"track", 40, 16, 0, YES, nil},
{@"comment", 120, 16, 0, YES, nil},
{@"percentage", 57, 16, 0, YES, nil},
{@"words", 120, 16, 0, YES, nil},
{@"dupe_count", 80, 16, 0, YES, nil},
nil
};
[[aTable columns] initializeColumns:defs];
NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
[[c dataCell] setButtonType:NSSwitchButton];
[[c dataCell] setControlSize:NSSmallControlSize];
c = [[aTable view] tableColumnWithIdentifier:@"size"];
[[c dataCell] setAlignment:NSRightTextAlignment];
c = [[aTable view] tableColumnWithIdentifier:@"duration"];
[[c dataCell] setAlignment:NSRightTextAlignment];
c = [[aTable view] tableColumnWithIdentifier:@"bitrate"];
[[c dataCell] setAlignment:NSRightTextAlignment];
[[aTable columns] restoreColumns];
}
@end

View File

@ -1,13 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "DetailsPanelBase.h"
@interface DetailsPanel : DetailsPanelBase
@end

View File

@ -1,17 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "DetailsPanel.h"
#import "DetailsPanel_UI.h"
@implementation DetailsPanel
- (NSWindow *)createWindow
{
return createDetailsPanel_UI(self);
}
@end

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>dupeGuru</string>
<key>CFBundleHelpBookFolder</key>
<string>dupeguru_me_help</string>
<key>CFBundleHelpBookName</key>
<string>dupeGuru ME Help</string>
<key>CFBundleIconFile</key>
<string>dupeguru</string>
<key>CFBundleIdentifier</key>
<string>com.hardcoded-software.dupeguru-me</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>dupeGuru ME</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>hsft</string>
<key>CFBundleShortVersionString</key>
<string>{version}</string>
<key>CFBundleVersion</key>
<string>{version}</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHumanReadableCopyright</key>
<string>© Hardcoded Software, 2016</string>
<key>SUFeedURL</key>
<string>https://www.hardcoded.net/updates/dupeguru_me.appcast</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
</dict>
</plist>

View File

@ -1,17 +0,0 @@
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from hscommon.trans import install_gettext_trans_under_cocoa
install_gettext_trans_under_cocoa()
from cocoa.inter import PySelectableList, PyColumns, PyTable
from inter.all import *
from inter.app_me import PyDupeGuru
# When built under virtualenv, the dependency collector misses this module, so we have to force it
# to see the module.
import distutils.sysconfig

Binary file not shown.

View File

@ -1,14 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "AppDelegateBase.h"
@interface AppDelegate : AppDelegateBase {}
- (void)clearPictureCache;
@end

View File

@ -1,88 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "AppDelegate.h"
#import "ProgressController.h"
#import "Utils.h"
#import "Dialogs.h"
#import "ValueTransformers.h"
#import "Consts.h"
#import "DetailsPanel.h"
#import "ResultWindow.h"
@implementation AppDelegate
+ (NSDictionary *)defaultPreferences
{
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
[d setObject:i2n(0) forKey:@"scanType"];
[d setObject:i2n(95) forKey:@"minMatchPercentage"];
[d setObject:b2n(NO) forKey:@"matchScaled"];
return d;
}
- (id)init
{
self = [super init];
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0];
VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"];
return self;
}
- (NSString *)homepageURL
{
return @"https://www.hardcoded.net/dupeguru_pe/";
}
- (DetailsPanel *)createDetailsPanel
{
return [[DetailsPanel alloc] initWithApp:model];
}
- (void)setScanOptions
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[model setScanType:n2i([ud objectForKey:@"scanType"])];
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
[model setMatchScaled:n2b([ud objectForKey:@"matchScaled"])];
}
- (void)initResultColumns:(ResultTable *)aTable
{
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 162, 16, 0, YES, nil},
{@"folder_path", 142, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"dimensions", 73, 16, 0, YES, nil},
{@"exif_timestamp", 120, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"percentage", 58, 16, 0, YES, nil},
{@"dupe_count", 80, 16, 0, YES, nil},
nil
};
[[aTable columns] initializeColumns:defs];
NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
[[c dataCell] setButtonType:NSSwitchButton];
[[c dataCell] setControlSize:NSSmallControlSize];
c = [[aTable view] tableColumnWithIdentifier:@"size"];
[[c dataCell] setAlignment:NSRightTextAlignment];
[[aTable columns] restoreColumns];
}
- (void)clearPictureCache
{
NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @"");
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
return;
[model clearPictureCache];
}
@end

View File

@ -1,11 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "../base/Consts.h"
#define ImageLoadedNotification @"ImageLoadedNotification"

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>dupeGuru</string>
<key>CFBundleHelpBookFolder</key>
<string>dupeguru_pe_help</string>
<key>CFBundleHelpBookName</key>
<string>dupeGuru PE Help</string>
<key>CFBundleIconFile</key>
<string>dupeguru</string>
<key>CFBundleIdentifier</key>
<string>com.hardcoded-software.dupeguru-pe</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>dupeGuru PE</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>hsft</string>
<key>CFBundleShortVersionString</key>
<string>{version}</string>
<key>CFBundleVersion</key>
<string>{version}</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHumanReadableCopyright</key>
<string>© Hardcoded Software, 2016</string>
<key>SUFeedURL</key>
<string>https://www.hardcoded.net/updates/dupeguru_pe.appcast</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
</dict>
</plist>

View File

@ -1,17 +0,0 @@
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
from hscommon.trans import install_gettext_trans_under_cocoa
install_gettext_trans_under_cocoa()
from cocoa.inter import PySelectableList, PyColumns, PyTable
from inter.all import *
from inter.app_pe import PyDupeGuru
# When built under virtualenv, the dependency collector misses this module, so we have to force it
# to see the module.
import distutils.sysconfig

Binary file not shown.

View File

@ -1,14 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "AppDelegateBase.h"
#import "PyDupeGuru.h"
@interface AppDelegate : AppDelegateBase {}
@end

View File

@ -1,85 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "AppDelegate.h"
#import "ProgressController.h"
#import "Utils.h"
#import "ValueTransformers.h"
#import "DetailsPanel.h"
#import "DirectoryPanel.h"
#import "ResultWindow.h"
#import "Consts.h"
@implementation AppDelegate
+ (NSDictionary *)defaultPreferences
{
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
[d setObject:i2n(1) forKey:@"scanType"];
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
[d setObject:i2n(30) forKey:@"smallFileThreshold"];
[d setObject:b2n(YES) forKey:@"wordWeighting"];
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"];
return d;
}
- (id)init
{
self = [super init];
NSMutableIndexSet *contentsIndexes = [NSMutableIndexSet indexSet];
[contentsIndexes addIndex:1];
[contentsIndexes addIndex:2];
VTIsIntIn *vt = [[[VTIsIntIn alloc] initWithValues:contentsIndexes reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vt forName:@"vtScanTypeIsNotContent"];
_directoryPanel = nil;
return self;
}
- (NSString *)homepageURL
{
return @"http://www.hardcoded.net/dupeguru/";
}
- (void)setScanOptions
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[model setScanType:n2i([ud objectForKey:@"scanType"])];
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])];
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
[model setMatchSimilarWords:n2b([ud objectForKey:@"matchSimilarWords"])];
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes
[model setSizeThreshold:sizeThreshold];
}
- (void)initResultColumns:(ResultTable *)aTable
{
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 195, 16, 0, YES, nil},
{@"folder_path", 183, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"percentage", 60, 16, 0, YES, nil},
{@"words", 120, 16, 0, YES, nil},
{@"dupe_count", 80, 16, 0, YES, nil},
nil
};
[[aTable columns] initializeColumns:defs];
NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
[[c dataCell] setButtonType:NSSwitchButton];
[[c dataCell] setControlSize:NSSmallControlSize];
c = [[aTable view] tableColumnWithIdentifier:@"size"];
[[c dataCell] setAlignment:NSRightTextAlignment];
[[aTable columns] restoreColumns];
}
@end

View File

@ -1,13 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "DetailsPanelBase.h"
@interface DetailsPanel : DetailsPanelBase
@end

View File

@ -1,17 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "DetailsPanel.h"
#import "DetailsPanel_UI.h"
@implementation DetailsPanel
- (NSWindow *)createWindow
{
return createDetailsPanel_UI(self);
}
@end

View File

@ -1,5 +1,5 @@
ownerclass = 'DetailsPanel' ownerclass = 'DetailsPanelPicture'
ownerimport = 'DetailsPanel.h' ownerimport = 'DetailsPanelPicture.h'
result = Panel(593, 398, "Details of Selected File") result = Panel(593, 398, "Details of Selected File")
table = TableView(result) table = TableView(result)

View File

@ -5,6 +5,8 @@ result = Window(425, 300, "dupeGuru")
promptLabel = Label(result, "Select folders to scan and press \"Scan\".") promptLabel = Label(result, "Select folders to scan and press \"Scan\".")
directoryOutline = OutlineView(result) directoryOutline = OutlineView(result)
directoryOutline.OBJC_CLASS = 'HSOutlineView' directoryOutline.OBJC_CLASS = 'HSOutlineView'
appModeSelector = SegmentedControl(result)
appModeLabel = Label(result, "Application Mode:")
scanTypePopup = Popup(result) scanTypePopup = Popup(result)
scanTypeLabel = Label(result, "Scan Type:") scanTypeLabel = Label(result, "Scan Type:")
addButton = Button(result, "") addButton = Button(result, "")
@ -15,6 +17,7 @@ addPopup = Popup(None)
loadRecentPopup = Popup(None) loadRecentPopup = Popup(None)
owner.outlineView = directoryOutline owner.outlineView = directoryOutline
owner.appModeSelector = appModeSelector
owner.scanTypePopup = scanTypePopup owner.scanTypePopup = scanTypePopup
owner.removeButton = removeButton owner.removeButton = removeButton
owner.loadResultsButton = loadResultsButton owner.loadResultsButton = loadResultsButton
@ -23,7 +26,9 @@ owner.loadRecentButtonPopUp = loadRecentPopup
result.autosaveName = 'DirectoryPanel' result.autosaveName = 'DirectoryPanel'
result.canMinimize = False result.canMinimize = False
result.minSize = Size(370, 270) result.minSize = Size(400, 270)
for label in ["Standard", "Music", "Picture"]:
appModeSelector.addSegment(label, 80)
addButton.bezelStyle = removeButton.bezelStyle = const.NSTexturedRoundedBezelStyle addButton.bezelStyle = removeButton.bezelStyle = const.NSTexturedRoundedBezelStyle
addButton.image = 'NSAddTemplate' addButton.image = 'NSAddTemplate'
removeButton.image = 'NSRemoveTemplate' removeButton.image = 'NSRemoveTemplate'
@ -31,6 +36,7 @@ for button in (addButton, removeButton):
button.style = const.NSTexturedRoundedBezelStyle button.style = const.NSTexturedRoundedBezelStyle
button.imagePosition = const.NSImageOnly button.imagePosition = const.NSImageOnly
scanButton.keyEquivalent = '\\r' scanButton.keyEquivalent = '\\r'
appModeSelector.action = Action(owner, 'changeAppMode:')
addButton.action = Action(owner, 'popupAddDirectoryMenu:') addButton.action = Action(owner, 'popupAddDirectoryMenu:')
removeButton.action = Action(owner, 'removeSelectedDirectory') removeButton.action = Action(owner, 'removeSelectedDirectory')
loadResultsButton.action = Action(owner, 'popupLoadRecentMenu:') loadResultsButton.action = Action(owner, 'popupLoadRecentMenu:')
@ -49,8 +55,10 @@ directoryOutline.allowsColumnReordering = False
directoryOutline.allowsColumnSelection = False directoryOutline.allowsColumnSelection = False
directoryOutline.allowsMultipleSelection = True directoryOutline.allowsMultipleSelection = True
scanTypeLabel.width = 90 appModeLabel.width = scanTypeLabel.width = 110
scanTypeLayout = HLayout([scanTypeLabel, scanTypePopup], filler=scanTypePopup) scanTypePopup.width = 248
appModeLayout = HLayout([appModeLabel, appModeSelector])
scanTypeLayout = HLayout([scanTypeLabel, scanTypePopup])
for button in (addButton, removeButton): for button in (addButton, removeButton):
button.width = 28 button.width = 28
@ -58,15 +66,11 @@ for button in (loadResultsButton, scanButton):
button.width = 118 button.width = 118
buttonLayout = HLayout([addButton, removeButton, None, loadResultsButton, scanButton]) buttonLayout = HLayout([addButton, removeButton, None, loadResultsButton, scanButton])
bottomLayout = VLayout([None, scanTypeLayout, buttonLayout]) mainLayout = VLayout([appModeLayout, scanTypeLayout, promptLabel, directoryOutline, buttonLayout], filler=directoryOutline)
promptLabel.packToCorner(Pack.UpperLeft) mainLayout.packToCorner(Pack.UpperLeft)
promptLabel.fill(Pack.Right) mainLayout.fill(Pack.LowerRight)
directoryOutline.packRelativeTo(promptLabel, Pack.Below) directoryOutline.packRelativeTo(promptLabel, Pack.Below)
bottomLayout.packRelativeTo(directoryOutline, Pack.Below, margin=8)
directoryOutline.fill(Pack.LowerRight)
bottomLayout.fill(Pack.Right)
promptLabel.setAnchor(Pack.UpperLeft, growX=True) promptLabel.setAnchor(Pack.UpperLeft, growX=True)
directoryOutline.setAnchor(Pack.UpperLeft, growX=True, growY=True) directoryOutline.setAnchor(Pack.UpperLeft, growX=True, growY=True)
scanTypeLayout.setAnchor(Pack.Below)
buttonLayout.setAnchor(Pack.Below) buttonLayout.setAnchor(Pack.Below)

View File

@ -1,6 +1,5 @@
ownerclass = 'AppDelegateBase' ownerclass = 'AppDelegate'
ownerimport = 'AppDelegateBase.h' ownerimport = 'AppDelegate.h'
edition = args.get('edition', 'se')
result = Menu("") result = Menu("")
appMenu = result.addMenu("dupeGuru") appMenu = result.addMenu("dupeGuru")
@ -30,8 +29,7 @@ owner.recentResultsMenu = fileMenu.addMenu("Load Recent Results")
fileMenu.addItem("Save Results...", Action(None, 'saveResults'), 'cmd+s') fileMenu.addItem("Save Results...", Action(None, 'saveResults'), 'cmd+s')
fileMenu.addItem("Export Results to XHTML", Action(owner.model, 'exportToXHTML'), 'cmd+shift+e') fileMenu.addItem("Export Results to XHTML", Action(owner.model, 'exportToXHTML'), 'cmd+shift+e')
fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV')) fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV'))
if edition == 'pe': fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p')
fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p')
editMenu.addItem("Mark All", Action(None, 'markAll'), 'cmd+a') editMenu.addItem("Mark All", Action(None, 'markAll'), 'cmd+a')
editMenu.addItem("Mark None", Action(None, 'markNone'), 'cmd+shift+a') editMenu.addItem("Mark None", Action(None, 'markNone'), 'cmd+shift+a')

View File

@ -1,16 +1,11 @@
edition = args.get('edition', 'se') appmode = args.get('appmode', 'standard')
dialogTitles = {
'se': "dupeGuru Preferences",
'me': "dupeGuru ME Preferences",
'pe': "dupeGuru PE Preferences",
}
dialogHeights = { dialogHeights = {
'se': 325, 'standard': 325,
'me': 345, 'music': 345,
'pe': 255, 'picture': 255,
} }
result = Window(410, dialogHeights[edition], dialogTitles[edition]) result = Window(410, dialogHeights[appmode], "dupeGuru Preferences")
tabView = TabView(result) tabView = TabView(result)
basicTab = tabView.addTab("Basic") basicTab = tabView.addTab("Basic")
advancedTab = tabView.addTab("Advanced") advancedTab = tabView.addTab("Advanced")
@ -21,19 +16,19 @@ fewerResultsLabel = Label(basicTab.view, "Fewer results")
thresholdValueLabel = Label(basicTab.view, "") thresholdValueLabel = Label(basicTab.view, "")
fontSizeCombo = Combobox(basicTab.view, ["11", "12", "13", "14", "18", "24"]) fontSizeCombo = Combobox(basicTab.view, ["11", "12", "13", "14", "18", "24"])
fontSizeLabel = Label(basicTab.view, "Font Size:") fontSizeLabel = Label(basicTab.view, "Font Size:")
if edition in ('se', 'me'): if appmode in ('standard', 'music'):
wordWeightingBox = Checkbox(basicTab.view, "Word weighting") wordWeightingBox = Checkbox(basicTab.view, "Word weighting")
matchSimilarWordsBox = Checkbox(basicTab.view, "Match similar words") matchSimilarWordsBox = Checkbox(basicTab.view, "Match similar words")
elif edition == 'pe': elif appmode == 'picture':
matchDifferentDimensionsBox = Checkbox(basicTab.view, "Match pictures of different dimensions") matchDifferentDimensionsBox = Checkbox(basicTab.view, "Match pictures of different dimensions")
mixKindBox = Checkbox(basicTab.view, "Can mix file kind") mixKindBox = Checkbox(basicTab.view, "Can mix file kind")
removeEmptyFoldersBox = Checkbox(basicTab.view, "Remove empty folders on delete or move") removeEmptyFoldersBox = Checkbox(basicTab.view, "Remove empty folders on delete or move")
checkForUpdatesBox = Checkbox(basicTab.view, "Automatically check for updates") checkForUpdatesBox = Checkbox(basicTab.view, "Automatically check for updates")
if edition == 'se': if appmode == 'standard':
ignoreSmallFilesBox = Checkbox(basicTab.view, "Ignore files smaller than:") ignoreSmallFilesBox = Checkbox(basicTab.view, "Ignore files smaller than:")
smallFilesThresholdText = TextField(basicTab.view, "") smallFilesThresholdText = TextField(basicTab.view, "")
smallFilesThresholdSuffixLabel = Label(basicTab.view, "KB") smallFilesThresholdSuffixLabel = Label(basicTab.view, "KB")
elif edition == 'me': elif appmode == 'music':
tagsToScanLabel = Label(basicTab.view, "Tags to scan:") tagsToScanLabel = Label(basicTab.view, "Tags to scan:")
trackBox = Checkbox(basicTab.view, "Track") trackBox = Checkbox(basicTab.view, "Track")
artistBox = Checkbox(basicTab.view, "Artist") artistBox = Checkbox(basicTab.view, "Artist")
@ -63,27 +58,29 @@ ignoreHardlinksBox.bind('value', defaults, 'values.ignoreHardlinkMatches')
debugModeCheckbox.bind('value', defaults, 'values.DebugMode') debugModeCheckbox.bind('value', defaults, 'values.DebugMode')
customCommandText.bind('value', defaults, 'values.CustomCommand') customCommandText.bind('value', defaults, 'values.CustomCommand')
copyMovePopup.bind('selectedIndex', defaults, 'values.recreatePathType') copyMovePopup.bind('selectedIndex', defaults, 'values.recreatePathType')
if edition in ('se', 'me'): if appmode in ('standard', 'music'):
wordWeightingBox.bind('value', defaults, 'values.wordWeighting') wordWeightingBox.bind('value', defaults, 'values.wordWeighting')
matchSimilarWordsBox.bind('value', defaults, 'values.matchSimilarWords') matchSimilarWordsBox.bind('value', defaults, 'values.matchSimilarWords')
disableWhenContentScan = [thresholdSlider, wordWeightingBox, matchSimilarWordsBox] disableWhenContentScan = [thresholdSlider, wordWeightingBox, matchSimilarWordsBox]
for control in disableWhenContentScan: for control in disableWhenContentScan:
control.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsNotContent') vtname = 'vtScanTypeMusicIsNotContent' if appmode == 'music' else 'vtScanTypeIsNotContent'
if edition == 'se': prefname = 'values.scanTypeMusic' if appmode == 'music' else 'values.scanTypeStandard'
control.bind('enabled', defaults, prefname, valueTransformer=vtname)
if appmode == 'standard':
ignoreSmallFilesBox.bind('value', defaults, 'values.ignoreSmallFiles') ignoreSmallFilesBox.bind('value', defaults, 'values.ignoreSmallFiles')
smallFilesThresholdText.bind('value', defaults, 'values.smallFileThreshold') smallFilesThresholdText.bind('value', defaults, 'values.smallFileThreshold')
elif edition == 'me': elif appmode == 'music':
for box in tagBoxes: for box in tagBoxes:
box.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsTag') box.bind('enabled', defaults, 'values.scanTypeMusic', valueTransformer='vtScanTypeIsTag')
trackBox.bind('value', defaults, 'values.scanTagTrack') trackBox.bind('value', defaults, 'values.scanTagTrack')
artistBox.bind('value', defaults, 'values.scanTagArtist') artistBox.bind('value', defaults, 'values.scanTagArtist')
albumBox.bind('value', defaults, 'values.scanTagAlbum') albumBox.bind('value', defaults, 'values.scanTagAlbum')
titleBox.bind('value', defaults, 'values.scanTagTitle') titleBox.bind('value', defaults, 'values.scanTagTitle')
genreBox.bind('value', defaults, 'values.scanTagGenre') genreBox.bind('value', defaults, 'values.scanTagGenre')
yearBox.bind('value', defaults, 'values.scanTagYear') yearBox.bind('value', defaults, 'values.scanTagYear')
elif edition == 'pe': elif appmode == 'picture':
matchDifferentDimensionsBox.bind('value', defaults, 'values.matchScaled') matchDifferentDimensionsBox.bind('value', defaults, 'values.matchScaled')
thresholdSlider.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsFuzzy') thresholdSlider.bind('enabled', defaults, 'values.scanTypePicture', valueTransformer='vtScanTypeIsFuzzy')
result.canResize = False result.canResize = False
result.canMinimize = False result.canMinimize = False
@ -93,13 +90,13 @@ allLabels = [thresholdValueLabel, moreResultsLabel, fewerResultsLabel,
thresholdLabel, fontSizeLabel, customCommandLabel, copyMoveLabel] thresholdLabel, fontSizeLabel, customCommandLabel, copyMoveLabel]
allCheckboxes = [mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox, regexpCheckbox, allCheckboxes = [mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox, regexpCheckbox,
ignoreHardlinksBox, debugModeCheckbox] ignoreHardlinksBox, debugModeCheckbox]
if edition == 'se': if appmode == 'standard':
allLabels += [smallFilesThresholdSuffixLabel] allLabels += [smallFilesThresholdSuffixLabel]
allCheckboxes += [ignoreSmallFilesBox, wordWeightingBox, matchSimilarWordsBox] allCheckboxes += [ignoreSmallFilesBox, wordWeightingBox, matchSimilarWordsBox]
elif edition == 'me': elif appmode == 'music':
allLabels += [tagsToScanLabel] allLabels += [tagsToScanLabel]
allCheckboxes += tagBoxes + [wordWeightingBox, matchSimilarWordsBox] allCheckboxes += tagBoxes + [wordWeightingBox, matchSimilarWordsBox]
elif edition == 'pe': elif appmode == 'picture':
allCheckboxes += [matchDifferentDimensionsBox] allCheckboxes += [matchDifferentDimensionsBox]
for label in allLabels: for label in allLabels:
label.controlSize = ControlSize.Small label.controlSize = ControlSize.Small
@ -112,10 +109,10 @@ thresholdLabel.width = fontSizeLabel.width = 94
fontSizeCombo.width = 66 fontSizeCombo.width = 66
thresholdValueLabel.width = 25 thresholdValueLabel.width = 25
resetToDefaultsButton.width = 136 resetToDefaultsButton.width = 136
if edition == 'se': if appmode == 'standard':
smallFilesThresholdText.width = 60 smallFilesThresholdText.width = 60
smallFilesThresholdSuffixLabel.width = 40 smallFilesThresholdSuffixLabel.width = 40
elif edition == 'me': elif appmode == 'music':
for box in tagBoxes: for box in tagBoxes:
box.width = 70 box.width = 70
@ -135,7 +132,7 @@ fewerResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Right,
fontSizeCombo.packRelativeTo(moreResultsLabel, Pack.Below) fontSizeCombo.packRelativeTo(moreResultsLabel, Pack.Below)
fontSizeLabel.packRelativeTo(fontSizeCombo, Pack.Left) fontSizeLabel.packRelativeTo(fontSizeCombo, Pack.Left)
if edition == 'me': if appmode == 'music':
tagsToScanLabel.packRelativeTo(fontSizeCombo, Pack.Below) tagsToScanLabel.packRelativeTo(fontSizeCombo, Pack.Below)
tagsToScanLabel.fill(Pack.Left) tagsToScanLabel.fill(Pack.Left)
tagsToScanLabel.fill(Pack.Right) tagsToScanLabel.fill(Pack.Right)
@ -150,13 +147,13 @@ if edition == 'me':
else: else:
viewToPackCheckboxesUnder = fontSizeCombo viewToPackCheckboxesUnder = fontSizeCombo
if edition == 'se': if appmode == 'standard':
checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox, checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox,
ignoreSmallFilesBox] ignoreSmallFilesBox]
elif edition == 'me': elif appmode == 'music':
checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox, checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox,
checkForUpdatesBox] checkForUpdatesBox]
elif edition == 'pe': elif appmode == 'picture':
checkboxesToLayout = [matchDifferentDimensionsBox, mixKindBox, removeEmptyFoldersBox, checkboxesToLayout = [matchDifferentDimensionsBox, mixKindBox, removeEmptyFoldersBox,
checkForUpdatesBox] checkForUpdatesBox]
checkboxLayout = VLayout(checkboxesToLayout) checkboxLayout = VLayout(checkboxesToLayout)
@ -164,7 +161,7 @@ checkboxLayout.packRelativeTo(viewToPackCheckboxesUnder, Pack.Below)
checkboxLayout.fill(Pack.Left) checkboxLayout.fill(Pack.Left)
checkboxLayout.fill(Pack.Right) checkboxLayout.fill(Pack.Right)
if edition == 'se': if appmode == 'standard':
smallFilesThresholdText.packRelativeTo(ignoreSmallFilesBox, Pack.Below, margin=4) smallFilesThresholdText.packRelativeTo(ignoreSmallFilesBox, Pack.Below, margin=4)
checkForUpdatesBox.packRelativeTo(smallFilesThresholdText, Pack.Below, margin=4) checkForUpdatesBox.packRelativeTo(smallFilesThresholdText, Pack.Below, margin=4)
checkForUpdatesBox.fill(Pack.Right) checkForUpdatesBox.fill(Pack.Right)

View File

@ -9,13 +9,8 @@ out = 'build'
def options(opt): def options(opt):
opt.load('compiler_c python') opt.load('compiler_c python')
opt.add_option('--edition', default='se', help="dupeGuru edition to build (se, me pe)")
def configure(conf): def configure(conf):
if conf.options.edition not in ('se', 'me', 'pe'):
conf.options.edition = 'se'
print("Building dupeGuru {}".format(conf.options.edition.upper()))
conf.env.DGEDITION = conf.options.edition
# We use clang to compile our app # We use clang to compile our app
conf.env.CC = 'clang' conf.env.CC = 'clang'
# WAF has a "pyembed" feature allowing us to automatically find Python and compile by linking # WAF has a "pyembed" feature allowing us to automatically find Python and compile by linking
@ -31,7 +26,7 @@ def configure(conf):
os.symlink('../build/Python', versioned_dylib_path) os.symlink('../build/Python', versioned_dylib_path)
# The rest is standard WAF code that you can find the the python and macapp demos. # The rest is standard WAF code that you can find the the python and macapp demos.
conf.load('compiler_c python') conf.load('compiler_c python')
conf.check_python_version((3,3,0)) conf.check_python_version((3,4,0))
conf.check_python_headers() conf.check_python_headers()
conf.env.FRAMEWORK_COCOA = 'Cocoa' conf.env.FRAMEWORK_COCOA = 'Cocoa'
conf.env.ARCH_COCOA = ['x86_64'] conf.env.ARCH_COCOA = ['x86_64']
@ -53,8 +48,8 @@ def build(ctx):
'controllers/HSOutline', 'controllers/HSPopUpList', 'controllers/HSSelectableList', 'controllers/HSOutline', 'controllers/HSPopUpList', 'controllers/HSSelectableList',
'controllers/HSTextField', 'controllers/HSProgressWindow'] 'controllers/HSTextField', 'controllers/HSProgressWindow']
cocoalib_src = [cocoalib_node.find_node(usename + '.m') for usename in cocoalib_uses] + cocoalib_node.ant_glob('autogen/*.m') cocoalib_src = [cocoalib_node.find_node(usename + '.m') for usename in cocoalib_uses] + cocoalib_node.ant_glob('autogen/*.m')
project_folders = ['autogen', 'base', ctx.env.DGEDITION] project_folders = [ctx.srcnode, ctx.srcnode.find_dir('autogen')]
project_src = sum([ctx.srcnode.ant_glob('%s/*.m' % folder) for folder in project_folders], []) project_src = ctx.srcnode.ant_glob('autogen/*.m') + ctx.srcnode.ant_glob('*.m')
# Compile # Compile
ctx.program( ctx.program(

View File

@ -323,6 +323,14 @@ class DupeGuru(Broadcaster):
self.notify('dupes_selected') self.notify('dupes_selected')
#--- Protected #--- Protected
def _get_fileclasses(self):
if self.app_mode == AppMode.Picture:
return [pe.photo.PLAT_SPECIFIC_PHOTO_CLASS]
elif self.app_mode == AppMode.Music:
return [me.fs.MusicFile]
else:
return [se.fs.File]
def _prioritization_categories(self): def _prioritization_categories(self):
if self.app_mode == AppMode.Picture: if self.app_mode == AppMode.Picture:
return pe.prioritize.all_categories() return pe.prioritize.all_categories()
@ -743,7 +751,7 @@ class DupeGuru(Broadcaster):
def do(j): def do(j):
j.set_progress(0, tr("Collecting files to scan")) j.set_progress(0, tr("Collecting files to scan"))
if scanner.scan_type == ScanType.Folders: if scanner.scan_type == ScanType.Folders:
files = list(self.directories.get_folders(folderclass=se.fs.folder, j=j)) files = list(self.directories.get_folders(folderclass=se.fs.Folder, j=j))
else: else:
files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j)) files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j))
if self.options['ignore_hardlink_matches']: if self.options['ignore_hardlink_matches']:
@ -794,12 +802,7 @@ class DupeGuru(Broadcaster):
@property @property
def fileclasses(self): def fileclasses(self):
if self.app_mode == AppMode.Picture: return self._get_fileclasses()
return [pe.photo.PLAT_SPECIFIC_PHOTO_CLASS]
elif self.app_mode == AppMode.Music:
return [me.fs.MusicFile]
else:
return [se.fs.File]
@property @property
def SCANNER_CLASS(self): def SCANNER_CLASS(self):