From 8c1078aa711ba8b7df96ecc8a9008d9f0db3e545 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sun, 5 Jun 2016 21:18:48 -0400 Subject: [PATCH] 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. --- build.py | 36 ++--- .../{base/AppDelegateBase.h => AppDelegate.h} | 11 +- .../{base/AppDelegateBase.m => AppDelegate.m} | 144 +++++++++++++++--- cocoa/{base => }/Consts.h | 5 + cocoa/{base => }/DeletionOptions.h | 0 cocoa/{base => }/DeletionOptions.m | 0 .../DetailsPanelBase.h => DetailsPanel.h} | 2 +- .../DetailsPanelBase.m => DetailsPanel.m} | 7 +- .../DetailsPanel.h => DetailsPanelPicture.h} | 4 +- .../DetailsPanel.m => DetailsPanelPicture.m} | 8 +- cocoa/{base => }/DirectoryOutline.h | 0 cocoa/{base => }/DirectoryOutline.m | 0 cocoa/{base => }/DirectoryPanel.h | 11 +- cocoa/{base => }/DirectoryPanel.m | 44 +++++- cocoa/{base => }/IgnoreListDialog.h | 0 cocoa/{base => }/IgnoreListDialog.m | 0 cocoa/{se => }/InfoTemplate.plist | 0 cocoa/{base => }/PrioritizeDialog.h | 0 cocoa/{base => }/PrioritizeDialog.m | 0 cocoa/{base => }/PrioritizeList.h | 0 cocoa/{base => }/PrioritizeList.m | 0 cocoa/{base => }/ProblemDialog.h | 0 cocoa/{base => }/ProblemDialog.m | 0 cocoa/{base => }/ResultTable.h | 0 cocoa/{base => }/ResultTable.m | 0 cocoa/{base => }/ResultWindow.h | 12 +- cocoa/{base => }/ResultWindow.m | 93 +++++++++-- cocoa/{base => }/StatsLabel.h | 0 cocoa/{base => }/StatsLabel.m | 0 cocoa/{se => }/dg_cocoa.py | 0 cocoa/{base => }/dsa_pub.pem | 0 cocoa/{se => }/dupeguru.icns | Bin cocoa/{base => }/en.lproj/Localizable.strings | 0 cocoa/inter/app.py | 51 +++++++ cocoa/inter/app_me.py | 48 ------ cocoa/inter/app_pe.py | 90 ----------- cocoa/inter/app_se.py | 89 ++++++----- cocoa/{base => }/main.m | 0 cocoa/me/AppDelegate.h | 15 -- cocoa/me/AppDelegate.m | 106 ------------- cocoa/me/DetailsPanel.h | 13 -- cocoa/me/DetailsPanel.m | 17 --- cocoa/me/InfoTemplate.plist | 40 ----- cocoa/me/dg_cocoa.py | 17 --- cocoa/me/dupeguru.icns | Bin 58956 -> 0 bytes cocoa/pe/AppDelegate.h | 14 -- cocoa/pe/AppDelegate.m | 88 ----------- cocoa/pe/Consts.h | 11 -- cocoa/pe/InfoTemplate.plist | 40 ----- cocoa/pe/dg_cocoa.py | 17 --- cocoa/pe/dupeguru.icns | Bin 59921 -> 0 bytes cocoa/se/AppDelegate.h | 14 -- cocoa/se/AppDelegate.m | 85 ----------- cocoa/se/DetailsPanel.h | 13 -- cocoa/se/DetailsPanel.m | 17 --- cocoa/{base => }/ui/deletion_options.py | 0 cocoa/{base => }/ui/details_panel.py | 0 .../details_panel_picture.py} | 4 +- cocoa/{base => }/ui/directory_panel.py | 24 +-- cocoa/{base => }/ui/ignore_list_dialog.py | 0 cocoa/{base => }/ui/main_menu.py | 8 +- cocoa/{base => }/ui/preferences_panel.py | 59 ++++--- cocoa/{base => }/ui/prioritize_dialog.py | 0 cocoa/{base => }/ui/problem_dialog.py | 0 cocoa/{base => }/ui/result_window.py | 0 cocoa/wscript | 11 +- core/app.py | 17 ++- 67 files changed, 464 insertions(+), 821 deletions(-) rename cocoa/{base/AppDelegateBase.h => AppDelegate.h} (88%) rename cocoa/{base/AppDelegateBase.m => AppDelegate.m} (58%) rename cocoa/{base => }/Consts.h (82%) rename cocoa/{base => }/DeletionOptions.h (100%) rename cocoa/{base => }/DeletionOptions.m (100%) rename cocoa/{base/DetailsPanelBase.h => DetailsPanel.h} (89%) rename cocoa/{base/DetailsPanelBase.m => DetailsPanel.m} (92%) rename cocoa/{pe/DetailsPanel.h => DetailsPanelPicture.h} (92%) rename cocoa/{pe/DetailsPanel.m => DetailsPanelPicture.m} (94%) rename cocoa/{base => }/DirectoryOutline.h (100%) rename cocoa/{base => }/DirectoryOutline.m (100%) rename cocoa/{base => }/DirectoryPanel.h (85%) rename cocoa/{base => }/DirectoryPanel.m (84%) rename cocoa/{base => }/IgnoreListDialog.h (100%) rename cocoa/{base => }/IgnoreListDialog.m (100%) rename cocoa/{se => }/InfoTemplate.plist (100%) rename cocoa/{base => }/PrioritizeDialog.h (100%) rename cocoa/{base => }/PrioritizeDialog.m (100%) rename cocoa/{base => }/PrioritizeList.h (100%) rename cocoa/{base => }/PrioritizeList.m (100%) rename cocoa/{base => }/ProblemDialog.h (100%) rename cocoa/{base => }/ProblemDialog.m (100%) rename cocoa/{base => }/ResultTable.h (100%) rename cocoa/{base => }/ResultTable.m (100%) rename cocoa/{base => }/ResultWindow.h (87%) rename cocoa/{base => }/ResultWindow.m (69%) rename cocoa/{base => }/StatsLabel.h (100%) rename cocoa/{base => }/StatsLabel.m (100%) rename cocoa/{se => }/dg_cocoa.py (100%) rename cocoa/{base => }/dsa_pub.pem (100%) rename cocoa/{se => }/dupeguru.icns (100%) rename cocoa/{base => }/en.lproj/Localizable.strings (100%) delete mode 100644 cocoa/inter/app_me.py delete mode 100644 cocoa/inter/app_pe.py rename cocoa/{base => }/main.m (100%) delete mode 100644 cocoa/me/AppDelegate.h delete mode 100644 cocoa/me/AppDelegate.m delete mode 100644 cocoa/me/DetailsPanel.h delete mode 100644 cocoa/me/DetailsPanel.m delete mode 100644 cocoa/me/InfoTemplate.plist delete mode 100644 cocoa/me/dg_cocoa.py delete mode 100755 cocoa/me/dupeguru.icns delete mode 100644 cocoa/pe/AppDelegate.h delete mode 100644 cocoa/pe/AppDelegate.m delete mode 100644 cocoa/pe/Consts.h delete mode 100644 cocoa/pe/InfoTemplate.plist delete mode 100644 cocoa/pe/dg_cocoa.py delete mode 100755 cocoa/pe/dupeguru.icns delete mode 100644 cocoa/se/AppDelegate.h delete mode 100644 cocoa/se/AppDelegate.m delete mode 100644 cocoa/se/DetailsPanel.h delete mode 100644 cocoa/se/DetailsPanel.m rename cocoa/{base => }/ui/deletion_options.py (100%) rename cocoa/{base => }/ui/details_panel.py (100%) rename cocoa/{pe/ui/details_panel.py => ui/details_panel_picture.py} (96%) rename cocoa/{base => }/ui/directory_panel.py (77%) rename cocoa/{base => }/ui/ignore_list_dialog.py (100%) rename cocoa/{base => }/ui/main_menu.py (95%) rename cocoa/{base => }/ui/preferences_panel.py (87%) rename cocoa/{base => }/ui/prioritize_dialog.py (100%) rename cocoa/{base => }/ui/problem_dialog.py (100%) rename cocoa/{base => }/ui/result_window.py (100%) diff --git a/build.py b/build.py index 56de46de..2b34668e 100644 --- a/build.py +++ b/build.py @@ -90,31 +90,27 @@ def build_xibless(dest='cocoa/autogen'): ('prioritize_dialog.py', 'PrioritizeDialog_UI'), ('result_window.py', 'ResultWindow_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: xibless.generate( - op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname), + op.join('cocoa', 'ui', srcname), op.join(dest, dstname), localizationTable='Localizable' ) - # XXX This is broken - assert False - # if edition == 'pe': - # xibless.generate( - # 'cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'), - # localizationTable='Localizable' - # ) - # else: - # xibless.generate( - # 'cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'), - # localizationTable='Localizable' - # ) + for appmode in ('standard', 'music', 'picture'): + xibless.generate( + op.join('cocoa', 'ui', 'preferences_panel.py'), + op.join(dest, 'PreferencesPanel%s_UI' % appmode.capitalize()), + localizationTable='Localizable', + args={'appmode': appmode}, + ) def build_cocoa(dev): print("Creating OS X app structure") app = cocoa_app() 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) app.create(op.join('build', 'Info.plist')) print("Building localizations") @@ -156,8 +152,8 @@ def build_cocoa(dev): app.copy_executable('cocoa/build/dupeGuru') build_help() print("Copying resources and frameworks") - image_path = 'cocoa/se/dupeguru.icns' - resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help'] + image_path = 'cocoa/dupeguru.icns' + resources = [image_path, 'cocoa/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help'] app.copy_resources(*resources, use_symlinks=dev) app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework') print("Creating the run.py file") @@ -197,7 +193,7 @@ def build_localizations(ui): loc.compile_all_po('locale') if ui == 'cocoa': 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') elif ui == 'qt': build_qt_localizations() @@ -212,7 +208,7 @@ def build_updatepot(): build_cocoalib_xibless('cocoalib/autogen') loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj') 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 core.pot") 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) print("Enhancing ui.pot with Cocoa's strings files") loc.strings2pot( - op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'), + op.join('cocoa', 'en.lproj', 'Localizable.strings'), op.join('locale', 'ui.pot') ) diff --git a/cocoa/base/AppDelegateBase.h b/cocoa/AppDelegate.h similarity index 88% rename from cocoa/base/AppDelegateBase.h rename to cocoa/AppDelegate.h index 038123f9..43df44c8 100644 --- a/cocoa/base/AppDelegateBase.h +++ b/cocoa/AppDelegate.h @@ -14,11 +14,13 @@ http://www.gnu.org/licenses/gpl-3.0.html #import "DetailsPanel.h" #import "DirectoryPanel.h" #import "IgnoreListDialog.h" +#import "ProblemDialog.h" +#import "DeletionOptions.h" #import "HSAboutBox.h" #import "HSRecentFiles.h" #import "HSProgressWindow.h" -@interface AppDelegateBase : NSObject +@interface AppDelegate : NSObject { NSMenu *recentResultsMenu; NSMenu *columnsMenu; @@ -29,6 +31,8 @@ http://www.gnu.org/licenses/gpl-3.0.html DirectoryPanel *_directoryPanel; DetailsPanel *_detailsPanel; IgnoreListDialog *_ignoreListDialog; + ProblemDialog *_problemDialog; + DeletionOptions *_deletionOptions; HSProgressWindow *_progressWindow; NSWindowController *_preferencesPanel; HSAboutBox *_aboutBox; @@ -43,9 +47,7 @@ http://www.gnu.org/licenses/gpl-3.0.html + (NSDictionary *)defaultPreferences; - (PyDupeGuru *)model; - (DetailsPanel *)createDetailsPanel; -- (NSString *)homepageURL; - (void)setScanOptions; -- (void)initResultColumns:(ResultTable *)aTable; /* Public */ - (void)finalizeInit; @@ -53,6 +55,8 @@ http://www.gnu.org/licenses/gpl-3.0.html - (DirectoryPanel *)directoryPanel; - (DetailsPanel *)detailsPanel; - (HSRecentFiles *)recentResults; +- (NSInteger)getAppMode; +- (void)setAppMode:(NSInteger)appMode; /* Delegate */ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification; @@ -62,6 +66,7 @@ http://www.gnu.org/licenses/gpl-3.0.html - (void)recentFileClicked:(NSString *)path; /* Actions */ +- (void)clearPictureCache; - (void)loadResults; - (void)openWebsite; - (void)openHelp; diff --git a/cocoa/base/AppDelegateBase.m b/cocoa/AppDelegate.m similarity index 58% rename from cocoa/base/AppDelegateBase.m rename to cocoa/AppDelegate.m index 46e7b04b..77fc6b68 100644 --- a/cocoa/base/AppDelegateBase.m +++ b/cocoa/AppDelegate.m @@ -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 */ -#import "AppDelegateBase.h" +#import "AppDelegate.h" #import "ProgressController.h" #import "HSPyUtil.h" #import "Consts.h" #import "Dialogs.h" #import "Utils.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 columnsMenu; @@ -24,6 +27,21 @@ http://www.gnu.org/licenses/gpl-3.0.html + (NSDictionary *)defaultPreferences { 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(11) forKey:TableFontSize]; [d setObject:b2n(YES) forKey:@"mixFileKind"]; @@ -53,6 +71,20 @@ http://www.gnu.org/licenses/gpl-3.0.html model = [[PyDupeGuru alloc] init]; [model bindCallback:createCallback(@"DupeGuruView", self)]; [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; } @@ -69,14 +101,17 @@ http://www.gnu.org/licenses/gpl-3.0.html } _recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu]; [_recentResults setDelegate:self]; - _resultWindow = [[ResultWindow alloc] initWithParentApp:self]; _directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self]; - _detailsPanel = [self createDetailsPanel]; _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 setParentWindow:[_directoryPanel window]]; - _aboutBox = nil; // Lazily loaded - _preferencesPanel = nil; // Lazily loaded + // Lazily loaded + _aboutBox = nil; + _preferencesPanel = nil; + _resultWindow = nil; + _detailsPanel = nil; [[[self directoryPanel] window] makeKeyAndOrderFront:self]; } @@ -89,20 +124,45 @@ http://www.gnu.org/licenses/gpl-3.0.html - (DetailsPanel *)createDetailsPanel { - return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]]; -} - -- (NSString *)homepageURL -{ - return @""; // must be overriden by all editions + NSInteger appMode = [self getAppMode]; + if (appMode == AppModePicture) { + return [[DetailsPanelPicture alloc] initWithPyRef:[model detailsPanel]]; + } + else { + return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]]; + } } - (void)setScanOptions { -} - -- (void)initResultColumns:(ResultTable *)aTable -{ + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + NSString *scanTypeOptionName; + 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 */ @@ -126,7 +186,29 @@ http://www.gnu.org/licenses/gpl-3.0.html return _recentResults; } +- (NSInteger)getAppMode +{ + return [model getAppMode]; +} + +- (void)setAppMode:(NSInteger)appMode +{ + [model setAppMode:appMode]; + if (_preferencesPanel != nil) { + [_preferencesPanel release]; + _preferencesPanel = nil; + } +} + /* 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 { NSOpenPanel *op = [NSOpenPanel openPanel]; @@ -145,7 +227,7 @@ http://www.gnu.org/licenses/gpl-3.0.html - (void)openWebsite { - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[self homepageURL]]]; + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru/"]]; } - (void)openHelp @@ -172,7 +254,18 @@ http://www.gnu.org/licenses/gpl-3.0.html - (void)showPreferencesPanel { 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]; } @@ -252,6 +345,17 @@ http://www.gnu.org/licenses/gpl-3.0.html 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 { [[[self resultWindow] window] makeKeyAndOrderFront:nil]; @@ -259,7 +363,7 @@ http://www.gnu.org/licenses/gpl-3.0.html - (void)showProblemDialog { - [[self resultWindow] showProblemDialog]; + [_problemDialog showWindow:self]; } - (NSString *)selectDestFolderWithPrompt:(NSString *)prompt diff --git a/cocoa/base/Consts.h b/cocoa/Consts.h similarity index 82% rename from cocoa/base/Consts.h rename to cocoa/Consts.h index ef1c504f..ff0c54fc 100644 --- a/cocoa/base/Consts.h +++ b/cocoa/Consts.h @@ -17,3 +17,8 @@ http://www.gnu.org/licenses/gpl-3.0.html #define jobDelete @"job_delete" #define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType" +#define ImageLoadedNotification @"ImageLoadedNotification" + +#define AppModeStandard 0 +#define AppModeMusic 1 +#define AppModePicture 2 \ No newline at end of file diff --git a/cocoa/base/DeletionOptions.h b/cocoa/DeletionOptions.h similarity index 100% rename from cocoa/base/DeletionOptions.h rename to cocoa/DeletionOptions.h diff --git a/cocoa/base/DeletionOptions.m b/cocoa/DeletionOptions.m similarity index 100% rename from cocoa/base/DeletionOptions.m rename to cocoa/DeletionOptions.m diff --git a/cocoa/base/DetailsPanelBase.h b/cocoa/DetailsPanel.h similarity index 89% rename from cocoa/base/DetailsPanelBase.h rename to cocoa/DetailsPanel.h index 8ed6cc65..1c11f728 100644 --- a/cocoa/base/DetailsPanelBase.h +++ b/cocoa/DetailsPanel.h @@ -10,7 +10,7 @@ http://www.gnu.org/licenses/gpl-3.0.html #import #import "PyDetailsPanel.h" -@interface DetailsPanelBase : NSWindowController +@interface DetailsPanel : NSWindowController { NSTableView *detailsTable; diff --git a/cocoa/base/DetailsPanelBase.m b/cocoa/DetailsPanel.m similarity index 92% rename from cocoa/base/DetailsPanelBase.m rename to cocoa/DetailsPanel.m index 0f2c9d19..2efc7796 100644 --- a/cocoa/base/DetailsPanelBase.m +++ b/cocoa/DetailsPanel.m @@ -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 */ -#import "DetailsPanelBase.h" +#import "DetailsPanel.h" #import "HSPyUtil.h" +#import "DetailsPanel_UI.h" -@implementation DetailsPanelBase +@implementation DetailsPanel @synthesize detailsTable; @@ -35,7 +36,7 @@ http://www.gnu.org/licenses/gpl-3.0.html - (NSWindow *)createWindow { - return nil; // Virtual + return createDetailsPanel_UI(self); } - (void)refreshDetails diff --git a/cocoa/pe/DetailsPanel.h b/cocoa/DetailsPanelPicture.h similarity index 92% rename from cocoa/pe/DetailsPanel.h rename to cocoa/DetailsPanelPicture.h index 67d9b709..ff6b70d7 100644 --- a/cocoa/pe/DetailsPanel.h +++ b/cocoa/DetailsPanelPicture.h @@ -7,10 +7,10 @@ http://www.gnu.org/licenses/gpl-3.0.html */ #import -#import "DetailsPanelBase.h" +#import "DetailsPanel.h" #import "PyDupeGuru.h" -@interface DetailsPanel : DetailsPanelBase +@interface DetailsPanelPicture : DetailsPanel { NSImageView *dupeImage; NSProgressIndicator *dupeProgressIndicator; diff --git a/cocoa/pe/DetailsPanel.m b/cocoa/DetailsPanelPicture.m similarity index 94% rename from cocoa/pe/DetailsPanel.m rename to cocoa/DetailsPanelPicture.m index dfaa60df..c8287a6a 100644 --- a/cocoa/pe/DetailsPanel.m +++ b/cocoa/DetailsPanelPicture.m @@ -10,11 +10,11 @@ http://www.gnu.org/licenses/gpl-3.0.html #import "NSNotificationAdditions.h" #import "NSImageAdditions.h" #import "PyDupeGuru.h" -#import "DetailsPanel.h" +#import "DetailsPanelPicture.h" #import "Consts.h" -#import "DetailsPanel_UI.h" +#import "DetailsPanelPicture_UI.h" -@implementation DetailsPanel +@implementation DetailsPanelPicture @synthesize dupeImage; @synthesize dupeProgressIndicator; @@ -32,7 +32,7 @@ http://www.gnu.org/licenses/gpl-3.0.html - (NSWindow *)createWindow { - return createDetailsPanel_UI(self); + return createDetailsPanelPicture_UI(self); } - (void)loadImageAsync:(NSString *)imagePath diff --git a/cocoa/base/DirectoryOutline.h b/cocoa/DirectoryOutline.h similarity index 100% rename from cocoa/base/DirectoryOutline.h rename to cocoa/DirectoryOutline.h diff --git a/cocoa/base/DirectoryOutline.m b/cocoa/DirectoryOutline.m similarity index 100% rename from cocoa/base/DirectoryOutline.m rename to cocoa/DirectoryOutline.m diff --git a/cocoa/base/DirectoryPanel.h b/cocoa/DirectoryPanel.h similarity index 85% rename from cocoa/base/DirectoryPanel.h rename to cocoa/DirectoryPanel.h index 19318e42..b475f2a1 100644 --- a/cocoa/base/DirectoryPanel.h +++ b/cocoa/DirectoryPanel.h @@ -12,15 +12,16 @@ http://www.gnu.org/licenses/gpl-3.0.html #import "DirectoryOutline.h" #import "PyDupeGuru.h" -@class AppDelegateBase; +@class AppDelegate; @interface DirectoryPanel : NSWindowController { - AppDelegateBase *_app; + AppDelegate *_app; PyDupeGuru *model; HSRecentFiles *_recentDirectories; DirectoryOutline *outline; BOOL _alwaysShowPopUp; + NSSegmentedControl *appModeSelector; NSPopUpButton *scanTypePopup; NSPopUpButton *addButtonPopUp; NSPopUpButton *loadRecentButtonPopUp; @@ -29,6 +30,7 @@ http://www.gnu.org/licenses/gpl-3.0.html NSButton *loadResultsButton; } +@property (readwrite, retain) NSSegmentedControl *appModeSelector; @property (readwrite, retain) NSPopUpButton *scanTypePopup; @property (readwrite, retain) NSPopUpButton *addButtonPopUp; @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 *loadResultsButton; -- (id)initWithParentApp:(AppDelegateBase *)aParentApp; +- (id)initWithParentApp:(AppDelegate *)aParentApp; -- (void)fillPopUpMenu; // Virtual +- (void)fillPopUpMenu; +- (void)fillScanTypeMenu; - (void)adjustUIToLocalization; - (void)askForDirectory; diff --git a/cocoa/base/DirectoryPanel.m b/cocoa/DirectoryPanel.m similarity index 84% rename from cocoa/base/DirectoryPanel.m rename to cocoa/DirectoryPanel.m index e3b10daa..731b11b5 100644 --- a/cocoa/base/DirectoryPanel.m +++ b/cocoa/DirectoryPanel.m @@ -11,9 +11,11 @@ http://www.gnu.org/licenses/gpl-3.0.html #import "Dialogs.h" #import "Utils.h" #import "AppDelegate.h" +#import "Consts.h" @implementation DirectoryPanel +@synthesize appModeSelector; @synthesize scanTypePopup; @synthesize addButtonPopUp; @synthesize loadRecentButtonPopUp; @@ -21,15 +23,15 @@ http://www.gnu.org/licenses/gpl-3.0.html @synthesize removeButton; @synthesize loadResultsButton; -- (id)initWithParentApp:(AppDelegateBase *)aParentApp +- (id)initWithParentApp:(AppDelegate *)aParentApp { self = [super initWithWindow:nil]; [self setWindow:createDirectoryPanel_UI(self)]; _app = aParentApp; model = [_app model]; [[self window] setTitle:[model appName]]; - [[self scanTypePopup] addItemsWithTitles:[[aParentApp model] getScanOptions]]; - [[self scanTypePopup] bind:@"selectedIndex" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.scanType" options:nil]; + self.appModeSelector.selectedSegment = 0; + [self fillScanTypeMenu]; _alwaysShowPopUp = NO; [self fillPopUpMenu]; _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]]; } +- (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 { 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 { if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) { diff --git a/cocoa/base/IgnoreListDialog.h b/cocoa/IgnoreListDialog.h similarity index 100% rename from cocoa/base/IgnoreListDialog.h rename to cocoa/IgnoreListDialog.h diff --git a/cocoa/base/IgnoreListDialog.m b/cocoa/IgnoreListDialog.m similarity index 100% rename from cocoa/base/IgnoreListDialog.m rename to cocoa/IgnoreListDialog.m diff --git a/cocoa/se/InfoTemplate.plist b/cocoa/InfoTemplate.plist similarity index 100% rename from cocoa/se/InfoTemplate.plist rename to cocoa/InfoTemplate.plist diff --git a/cocoa/base/PrioritizeDialog.h b/cocoa/PrioritizeDialog.h similarity index 100% rename from cocoa/base/PrioritizeDialog.h rename to cocoa/PrioritizeDialog.h diff --git a/cocoa/base/PrioritizeDialog.m b/cocoa/PrioritizeDialog.m similarity index 100% rename from cocoa/base/PrioritizeDialog.m rename to cocoa/PrioritizeDialog.m diff --git a/cocoa/base/PrioritizeList.h b/cocoa/PrioritizeList.h similarity index 100% rename from cocoa/base/PrioritizeList.h rename to cocoa/PrioritizeList.h diff --git a/cocoa/base/PrioritizeList.m b/cocoa/PrioritizeList.m similarity index 100% rename from cocoa/base/PrioritizeList.m rename to cocoa/PrioritizeList.m diff --git a/cocoa/base/ProblemDialog.h b/cocoa/ProblemDialog.h similarity index 100% rename from cocoa/base/ProblemDialog.h rename to cocoa/ProblemDialog.h diff --git a/cocoa/base/ProblemDialog.m b/cocoa/ProblemDialog.m similarity index 100% rename from cocoa/base/ProblemDialog.m rename to cocoa/ProblemDialog.m diff --git a/cocoa/base/ResultTable.h b/cocoa/ResultTable.h similarity index 100% rename from cocoa/base/ResultTable.h rename to cocoa/ResultTable.h diff --git a/cocoa/base/ResultTable.m b/cocoa/ResultTable.m similarity index 100% rename from cocoa/base/ResultTable.m rename to cocoa/ResultTable.m diff --git a/cocoa/base/ResultWindow.h b/cocoa/ResultWindow.h similarity index 87% rename from cocoa/base/ResultWindow.h rename to cocoa/ResultWindow.h index d61ab430..509b042b 100644 --- a/cocoa/base/ResultWindow.h +++ b/cocoa/ResultWindow.h @@ -10,12 +10,10 @@ http://www.gnu.org/licenses/gpl-3.0.html #import #import "StatsLabel.h" #import "ResultTable.h" -#import "ProblemDialog.h" -#import "DeletionOptions.h" #import "HSTableView.h" #import "PyDupeGuru.h" -@class AppDelegateBase; +@class AppDelegate; @interface ResultWindow : NSWindowController { @@ -26,12 +24,10 @@ http://www.gnu.org/licenses/gpl-3.0.html NSTextField *stats; NSSearchField *filterField; - AppDelegateBase *app; + AppDelegate *app; PyDupeGuru *model; ResultTable *table; StatsLabel *statsLabel; - ProblemDialog *problemDialog; - DeletionOptions *deletionOptions; QLPreviewPanel* previewPanel; } @@ -41,13 +37,13 @@ http://www.gnu.org/licenses/gpl-3.0.html @property (readwrite, retain) NSTextField *stats; @property (readwrite, retain) NSSearchField *filterField; -- (id)initWithParentApp:(AppDelegateBase *)app; +- (id)initWithParentApp:(AppDelegate *)app; /* Helpers */ - (void)fillColumnsMenu; - (void)updateOptionSegments; -- (void)showProblemDialog; - (void)adjustUIToLocalization; +- (void)initResultColumns:(ResultTable *)aTable; /* Actions */ - (void)changeOptions; diff --git a/cocoa/base/ResultWindow.m b/cocoa/ResultWindow.m similarity index 69% rename from cocoa/base/ResultWindow.m rename to cocoa/ResultWindow.m index 9afc6723..f0cc860e 100644 --- a/cocoa/base/ResultWindow.m +++ b/cocoa/ResultWindow.m @@ -23,7 +23,7 @@ http://www.gnu.org/licenses/gpl-3.0.html @synthesize stats; @synthesize filterField; -- (id)initWithParentApp:(AppDelegateBase *)aApp; +- (id)initWithParentApp:(AppDelegate *)aApp; { self = [super initWithWindow:nil]; app = aApp; @@ -34,9 +34,7 @@ http://www.gnu.org/licenses/gpl-3.0.html [[self window] setContentBorderThickness:28 forEdge:NSMinYEdge]; table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches]; statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats]; - problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]]; - deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]]; - [aApp initResultColumns:table]; + [self initResultColumns:table]; [[table columns] setColumnsAsReadOnly]; [self fillColumnsMenu]; [matches setTarget:self]; @@ -49,7 +47,6 @@ http://www.gnu.org/licenses/gpl-3.0.html { [table release]; [statsLabel release]; - [problemDialog release]; [super dealloc]; } @@ -80,11 +77,6 @@ http://www.gnu.org/licenses/gpl-3.0.html [optionsSwitch setSelected:[table deltaValuesMode] forSegment:2]; } -- (void)showProblemDialog -{ - [problemDialog showWindow:self]; -} - - (void)adjustUIToLocalization { 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 */ - (void)changeOptions { diff --git a/cocoa/base/StatsLabel.h b/cocoa/StatsLabel.h similarity index 100% rename from cocoa/base/StatsLabel.h rename to cocoa/StatsLabel.h diff --git a/cocoa/base/StatsLabel.m b/cocoa/StatsLabel.m similarity index 100% rename from cocoa/base/StatsLabel.m rename to cocoa/StatsLabel.m diff --git a/cocoa/se/dg_cocoa.py b/cocoa/dg_cocoa.py similarity index 100% rename from cocoa/se/dg_cocoa.py rename to cocoa/dg_cocoa.py diff --git a/cocoa/base/dsa_pub.pem b/cocoa/dsa_pub.pem similarity index 100% rename from cocoa/base/dsa_pub.pem rename to cocoa/dsa_pub.pem diff --git a/cocoa/se/dupeguru.icns b/cocoa/dupeguru.icns similarity index 100% rename from cocoa/se/dupeguru.icns rename to cocoa/dupeguru.icns diff --git a/cocoa/base/en.lproj/Localizable.strings b/cocoa/en.lproj/Localizable.strings similarity index 100% rename from cocoa/base/en.lproj/Localizable.strings rename to cocoa/en.lproj/Localizable.strings diff --git a/cocoa/inter/app.py b/cocoa/inter/app.py index 91da591a..2efd47c9 100644 --- a/cocoa/inter/app.py +++ b/cocoa/inter/app.py @@ -6,6 +6,7 @@ from cocoa.inter import PyBaseApp, BaseAppView class DupeGuruView(BaseAppView): def askYesNoWithPrompt_(self, prompt: str) -> bool: pass + def createResultsWindow(self): pass def showResultsWindow(self): pass def showProblemDialog(self): pass def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass @@ -123,6 +124,9 @@ class PyDupeGuruBase(PyBaseApp): def showIgnoreList(self): self.model.ignore_list_dialog.show() + def clearPictureCache(self): + self.model.clear_picture_cache() + #---Information def getScanOptions(self) -> list: return [o.label for o in self.model.SCANNER_CLASS.get_scan_options()] @@ -130,7 +134,50 @@ class PyDupeGuruBase(PyBaseApp): def resultsAreModified(self) -> bool: 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 + 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): self.model.options['mix_file_kind'] = mix_file_kind @@ -151,6 +198,10 @@ class PyDupeGuruBase(PyBaseApp): def ask_yes_no(self, prompt): return self.callback.askYesNoWithPrompt_(prompt) + @dontwrap + def create_results_window(self): + self.callback.createResultsWindow() + @dontwrap def show_results_window(self): self.callback.showResultsWindow() diff --git a/cocoa/inter/app_me.py b/cocoa/inter/app_me.py deleted file mode 100644 index 70e20b3f..00000000 --- a/cocoa/inter/app_me.py +++ /dev/null @@ -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) diff --git a/cocoa/inter/app_pe.py b/cocoa/inter/app_pe.py deleted file mode 100644 index 06988277..00000000 --- a/cocoa/inter/app_pe.py +++ /dev/null @@ -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 diff --git a/cocoa/inter/app_se.py b/cocoa/inter/app_se.py index 4dcbf2a5..c4bd2385 100644 --- a/cocoa/inter/app_se.py +++ b/cocoa/inter/app_se.py @@ -12,10 +12,12 @@ import os.path as op from hscommon.path import Path, pathify from cocoa import proxy -from core.scanner import ScanType from core.directories import Directories as DirectoriesBase, DirectoryState -from core_se.app import DupeGuru as DupeGuruBase -from core_se import fs +import core.pe.photo +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 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)) +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): ROOT_PATH_TO_EXCLUDE = list(map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev'])) HOME_PATH_TO_EXCLUDE = [Path('Library')] - def __init__(self): - DirectoriesBase.__init__(self) - self.folderclass = fs.Folder - + def _default_state_for_path(self, path): result = DirectoriesBase._default_state_for_path(self, path) if result is not None: @@ -58,8 +84,7 @@ class Directories(DirectoriesBase): yield from_folder return else: - for folder in DirectoriesBase._get_folders(self, from_folder, j): - yield folder + yield from DirectoriesBase._get_folders(self, from_folder, j) @staticmethod def get_subfolders(path): @@ -69,37 +94,31 @@ class Directories(DirectoriesBase): class DupeGuru(DupeGuruBase): def __init__(self, view): - # appdata = op.join(appdata, 'dupeGuru') - # print(repr(appdata)) DupeGuruBase.__init__(self, view) - self.fileclasses = [Bundle, fs.File] 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): def __init__(self): + core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = Photo 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 + diff --git a/cocoa/base/main.m b/cocoa/main.m similarity index 100% rename from cocoa/base/main.m rename to cocoa/main.m diff --git a/cocoa/me/AppDelegate.h b/cocoa/me/AppDelegate.h deleted file mode 100644 index 79cf3e41..00000000 --- a/cocoa/me/AppDelegate.h +++ /dev/null @@ -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 -#import "AppDelegateBase.h" -#import "ResultWindow.h" -#import "PyDupeGuru.h" - -@interface AppDelegate : AppDelegateBase {} -@end diff --git a/cocoa/me/AppDelegate.m b/cocoa/me/AppDelegate.m deleted file mode 100644 index 95f274da..00000000 --- a/cocoa/me/AppDelegate.m +++ /dev/null @@ -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 diff --git a/cocoa/me/DetailsPanel.h b/cocoa/me/DetailsPanel.h deleted file mode 100644 index 0bcdaf4b..00000000 --- a/cocoa/me/DetailsPanel.h +++ /dev/null @@ -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 -#import "DetailsPanelBase.h" - -@interface DetailsPanel : DetailsPanelBase -@end \ No newline at end of file diff --git a/cocoa/me/DetailsPanel.m b/cocoa/me/DetailsPanel.m deleted file mode 100644 index e6c3ad24..00000000 --- a/cocoa/me/DetailsPanel.m +++ /dev/null @@ -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 diff --git a/cocoa/me/InfoTemplate.plist b/cocoa/me/InfoTemplate.plist deleted file mode 100644 index e874b401..00000000 --- a/cocoa/me/InfoTemplate.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - dupeGuru - CFBundleHelpBookFolder - dupeguru_me_help - CFBundleHelpBookName - dupeGuru ME Help - CFBundleIconFile - dupeguru - CFBundleIdentifier - com.hardcoded-software.dupeguru-me - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - dupeGuru ME - CFBundlePackageType - APPL - CFBundleSignature - hsft - CFBundleShortVersionString - {version} - CFBundleVersion - {version} - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - NSHumanReadableCopyright - © Hardcoded Software, 2016 - SUFeedURL - https://www.hardcoded.net/updates/dupeguru_me.appcast - SUPublicDSAKeyFile - dsa_pub.pem - - diff --git a/cocoa/me/dg_cocoa.py b/cocoa/me/dg_cocoa.py deleted file mode 100644 index 3dc3f83a..00000000 --- a/cocoa/me/dg_cocoa.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/cocoa/me/dupeguru.icns b/cocoa/me/dupeguru.icns deleted file mode 100755 index 42f8641e50bfec321f2ce0facc5cdf11c2922bc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58956 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�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 -#import "AppDelegateBase.h" - -@interface AppDelegate : AppDelegateBase {} -- (void)clearPictureCache; -@end diff --git a/cocoa/pe/AppDelegate.m b/cocoa/pe/AppDelegate.m deleted file mode 100644 index 8c20946d..00000000 --- a/cocoa/pe/AppDelegate.m +++ /dev/null @@ -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 diff --git a/cocoa/pe/Consts.h b/cocoa/pe/Consts.h deleted file mode 100644 index 002a88ef..00000000 --- a/cocoa/pe/Consts.h +++ /dev/null @@ -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" diff --git a/cocoa/pe/InfoTemplate.plist b/cocoa/pe/InfoTemplate.plist deleted file mode 100644 index 2dee7023..00000000 --- a/cocoa/pe/InfoTemplate.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - dupeGuru - CFBundleHelpBookFolder - dupeguru_pe_help - CFBundleHelpBookName - dupeGuru PE Help - CFBundleIconFile - dupeguru - CFBundleIdentifier - com.hardcoded-software.dupeguru-pe - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - dupeGuru PE - CFBundlePackageType - APPL - CFBundleSignature - hsft - CFBundleShortVersionString - {version} - CFBundleVersion - {version} - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - NSHumanReadableCopyright - © Hardcoded Software, 2016 - SUFeedURL - https://www.hardcoded.net/updates/dupeguru_pe.appcast - SUPublicDSAKeyFile - dsa_pub.pem - - diff --git a/cocoa/pe/dg_cocoa.py b/cocoa/pe/dg_cocoa.py deleted file mode 100644 index bcf75f39..00000000 --- a/cocoa/pe/dg_cocoa.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/cocoa/pe/dupeguru.icns b/cocoa/pe/dupeguru.icns deleted file mode 100755 index c143ed8670c29efd911d9db402978756a26b01b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59921 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 -#import "AppDelegateBase.h" -#import "PyDupeGuru.h" - -@interface AppDelegate : AppDelegateBase {} -@end diff --git a/cocoa/se/AppDelegate.m b/cocoa/se/AppDelegate.m deleted file mode 100644 index 7f666fe0..00000000 --- a/cocoa/se/AppDelegate.m +++ /dev/null @@ -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 diff --git a/cocoa/se/DetailsPanel.h b/cocoa/se/DetailsPanel.h deleted file mode 100644 index 0bcdaf4b..00000000 --- a/cocoa/se/DetailsPanel.h +++ /dev/null @@ -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 -#import "DetailsPanelBase.h" - -@interface DetailsPanel : DetailsPanelBase -@end \ No newline at end of file diff --git a/cocoa/se/DetailsPanel.m b/cocoa/se/DetailsPanel.m deleted file mode 100644 index e6c3ad24..00000000 --- a/cocoa/se/DetailsPanel.m +++ /dev/null @@ -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 diff --git a/cocoa/base/ui/deletion_options.py b/cocoa/ui/deletion_options.py similarity index 100% rename from cocoa/base/ui/deletion_options.py rename to cocoa/ui/deletion_options.py diff --git a/cocoa/base/ui/details_panel.py b/cocoa/ui/details_panel.py similarity index 100% rename from cocoa/base/ui/details_panel.py rename to cocoa/ui/details_panel.py diff --git a/cocoa/pe/ui/details_panel.py b/cocoa/ui/details_panel_picture.py similarity index 96% rename from cocoa/pe/ui/details_panel.py rename to cocoa/ui/details_panel_picture.py index 1a1efbd8..5d0d110b 100644 --- a/cocoa/pe/ui/details_panel.py +++ b/cocoa/ui/details_panel_picture.py @@ -1,5 +1,5 @@ -ownerclass = 'DetailsPanel' -ownerimport = 'DetailsPanel.h' +ownerclass = 'DetailsPanelPicture' +ownerimport = 'DetailsPanelPicture.h' result = Panel(593, 398, "Details of Selected File") table = TableView(result) diff --git a/cocoa/base/ui/directory_panel.py b/cocoa/ui/directory_panel.py similarity index 77% rename from cocoa/base/ui/directory_panel.py rename to cocoa/ui/directory_panel.py index 01587102..9f362173 100644 --- a/cocoa/base/ui/directory_panel.py +++ b/cocoa/ui/directory_panel.py @@ -5,6 +5,8 @@ result = Window(425, 300, "dupeGuru") promptLabel = Label(result, "Select folders to scan and press \"Scan\".") directoryOutline = OutlineView(result) directoryOutline.OBJC_CLASS = 'HSOutlineView' +appModeSelector = SegmentedControl(result) +appModeLabel = Label(result, "Application Mode:") scanTypePopup = Popup(result) scanTypeLabel = Label(result, "Scan Type:") addButton = Button(result, "") @@ -15,6 +17,7 @@ addPopup = Popup(None) loadRecentPopup = Popup(None) owner.outlineView = directoryOutline +owner.appModeSelector = appModeSelector owner.scanTypePopup = scanTypePopup owner.removeButton = removeButton owner.loadResultsButton = loadResultsButton @@ -23,7 +26,9 @@ owner.loadRecentButtonPopUp = loadRecentPopup result.autosaveName = 'DirectoryPanel' 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.image = 'NSAddTemplate' removeButton.image = 'NSRemoveTemplate' @@ -31,6 +36,7 @@ for button in (addButton, removeButton): button.style = const.NSTexturedRoundedBezelStyle button.imagePosition = const.NSImageOnly scanButton.keyEquivalent = '\\r' +appModeSelector.action = Action(owner, 'changeAppMode:') addButton.action = Action(owner, 'popupAddDirectoryMenu:') removeButton.action = Action(owner, 'removeSelectedDirectory') loadResultsButton.action = Action(owner, 'popupLoadRecentMenu:') @@ -49,8 +55,10 @@ directoryOutline.allowsColumnReordering = False directoryOutline.allowsColumnSelection = False directoryOutline.allowsMultipleSelection = True -scanTypeLabel.width = 90 -scanTypeLayout = HLayout([scanTypeLabel, scanTypePopup], filler=scanTypePopup) +appModeLabel.width = scanTypeLabel.width = 110 +scanTypePopup.width = 248 +appModeLayout = HLayout([appModeLabel, appModeSelector]) +scanTypeLayout = HLayout([scanTypeLabel, scanTypePopup]) for button in (addButton, removeButton): button.width = 28 @@ -58,15 +66,11 @@ for button in (loadResultsButton, scanButton): button.width = 118 buttonLayout = HLayout([addButton, removeButton, None, loadResultsButton, scanButton]) -bottomLayout = VLayout([None, scanTypeLayout, buttonLayout]) -promptLabel.packToCorner(Pack.UpperLeft) -promptLabel.fill(Pack.Right) +mainLayout = VLayout([appModeLayout, scanTypeLayout, promptLabel, directoryOutline, buttonLayout], filler=directoryOutline) +mainLayout.packToCorner(Pack.UpperLeft) +mainLayout.fill(Pack.LowerRight) 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) directoryOutline.setAnchor(Pack.UpperLeft, growX=True, growY=True) -scanTypeLayout.setAnchor(Pack.Below) buttonLayout.setAnchor(Pack.Below) diff --git a/cocoa/base/ui/ignore_list_dialog.py b/cocoa/ui/ignore_list_dialog.py similarity index 100% rename from cocoa/base/ui/ignore_list_dialog.py rename to cocoa/ui/ignore_list_dialog.py diff --git a/cocoa/base/ui/main_menu.py b/cocoa/ui/main_menu.py similarity index 95% rename from cocoa/base/ui/main_menu.py rename to cocoa/ui/main_menu.py index 3f9e8255..4c6a8ce3 100644 --- a/cocoa/base/ui/main_menu.py +++ b/cocoa/ui/main_menu.py @@ -1,6 +1,5 @@ -ownerclass = 'AppDelegateBase' -ownerimport = 'AppDelegateBase.h' -edition = args.get('edition', 'se') +ownerclass = 'AppDelegate' +ownerimport = 'AppDelegate.h' result = Menu("") 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("Export Results to XHTML", Action(owner.model, 'exportToXHTML'), 'cmd+shift+e') 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 None", Action(None, 'markNone'), 'cmd+shift+a') diff --git a/cocoa/base/ui/preferences_panel.py b/cocoa/ui/preferences_panel.py similarity index 87% rename from cocoa/base/ui/preferences_panel.py rename to cocoa/ui/preferences_panel.py index 84a170ed..227bc681 100644 --- a/cocoa/base/ui/preferences_panel.py +++ b/cocoa/ui/preferences_panel.py @@ -1,16 +1,11 @@ -edition = args.get('edition', 'se') -dialogTitles = { - 'se': "dupeGuru Preferences", - 'me': "dupeGuru ME Preferences", - 'pe': "dupeGuru PE Preferences", -} +appmode = args.get('appmode', 'standard') dialogHeights = { - 'se': 325, - 'me': 345, - 'pe': 255, + 'standard': 325, + 'music': 345, + 'picture': 255, } -result = Window(410, dialogHeights[edition], dialogTitles[edition]) +result = Window(410, dialogHeights[appmode], "dupeGuru Preferences") tabView = TabView(result) basicTab = tabView.addTab("Basic") advancedTab = tabView.addTab("Advanced") @@ -21,19 +16,19 @@ fewerResultsLabel = Label(basicTab.view, "Fewer results") thresholdValueLabel = Label(basicTab.view, "") fontSizeCombo = Combobox(basicTab.view, ["11", "12", "13", "14", "18", "24"]) fontSizeLabel = Label(basicTab.view, "Font Size:") -if edition in ('se', 'me'): +if appmode in ('standard', 'music'): wordWeightingBox = Checkbox(basicTab.view, "Word weighting") matchSimilarWordsBox = Checkbox(basicTab.view, "Match similar words") -elif edition == 'pe': +elif appmode == 'picture': matchDifferentDimensionsBox = Checkbox(basicTab.view, "Match pictures of different dimensions") mixKindBox = Checkbox(basicTab.view, "Can mix file kind") removeEmptyFoldersBox = Checkbox(basicTab.view, "Remove empty folders on delete or move") checkForUpdatesBox = Checkbox(basicTab.view, "Automatically check for updates") -if edition == 'se': +if appmode == 'standard': ignoreSmallFilesBox = Checkbox(basicTab.view, "Ignore files smaller than:") smallFilesThresholdText = TextField(basicTab.view, "") smallFilesThresholdSuffixLabel = Label(basicTab.view, "KB") -elif edition == 'me': +elif appmode == 'music': tagsToScanLabel = Label(basicTab.view, "Tags to scan:") trackBox = Checkbox(basicTab.view, "Track") artistBox = Checkbox(basicTab.view, "Artist") @@ -63,27 +58,29 @@ ignoreHardlinksBox.bind('value', defaults, 'values.ignoreHardlinkMatches') debugModeCheckbox.bind('value', defaults, 'values.DebugMode') customCommandText.bind('value', defaults, 'values.CustomCommand') copyMovePopup.bind('selectedIndex', defaults, 'values.recreatePathType') -if edition in ('se', 'me'): +if appmode in ('standard', 'music'): wordWeightingBox.bind('value', defaults, 'values.wordWeighting') matchSimilarWordsBox.bind('value', defaults, 'values.matchSimilarWords') disableWhenContentScan = [thresholdSlider, wordWeightingBox, matchSimilarWordsBox] for control in disableWhenContentScan: - control.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsNotContent') - if edition == 'se': + vtname = 'vtScanTypeMusicIsNotContent' if appmode == 'music' else 'vtScanTypeIsNotContent' + 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') smallFilesThresholdText.bind('value', defaults, 'values.smallFileThreshold') - elif edition == 'me': + elif appmode == 'music': 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') artistBox.bind('value', defaults, 'values.scanTagArtist') albumBox.bind('value', defaults, 'values.scanTagAlbum') titleBox.bind('value', defaults, 'values.scanTagTitle') genreBox.bind('value', defaults, 'values.scanTagGenre') yearBox.bind('value', defaults, 'values.scanTagYear') -elif edition == 'pe': +elif appmode == 'picture': 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.canMinimize = False @@ -93,13 +90,13 @@ allLabels = [thresholdValueLabel, moreResultsLabel, fewerResultsLabel, thresholdLabel, fontSizeLabel, customCommandLabel, copyMoveLabel] allCheckboxes = [mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox, regexpCheckbox, ignoreHardlinksBox, debugModeCheckbox] -if edition == 'se': +if appmode == 'standard': allLabels += [smallFilesThresholdSuffixLabel] allCheckboxes += [ignoreSmallFilesBox, wordWeightingBox, matchSimilarWordsBox] -elif edition == 'me': +elif appmode == 'music': allLabels += [tagsToScanLabel] allCheckboxes += tagBoxes + [wordWeightingBox, matchSimilarWordsBox] -elif edition == 'pe': +elif appmode == 'picture': allCheckboxes += [matchDifferentDimensionsBox] for label in allLabels: label.controlSize = ControlSize.Small @@ -112,10 +109,10 @@ thresholdLabel.width = fontSizeLabel.width = 94 fontSizeCombo.width = 66 thresholdValueLabel.width = 25 resetToDefaultsButton.width = 136 -if edition == 'se': +if appmode == 'standard': smallFilesThresholdText.width = 60 smallFilesThresholdSuffixLabel.width = 40 -elif edition == 'me': +elif appmode == 'music': for box in tagBoxes: box.width = 70 @@ -135,7 +132,7 @@ fewerResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Right, fontSizeCombo.packRelativeTo(moreResultsLabel, Pack.Below) fontSizeLabel.packRelativeTo(fontSizeCombo, Pack.Left) -if edition == 'me': +if appmode == 'music': tagsToScanLabel.packRelativeTo(fontSizeCombo, Pack.Below) tagsToScanLabel.fill(Pack.Left) tagsToScanLabel.fill(Pack.Right) @@ -150,13 +147,13 @@ if edition == 'me': else: viewToPackCheckboxesUnder = fontSizeCombo -if edition == 'se': +if appmode == 'standard': checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox, ignoreSmallFilesBox] -elif edition == 'me': +elif appmode == 'music': checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox] -elif edition == 'pe': +elif appmode == 'picture': checkboxesToLayout = [matchDifferentDimensionsBox, mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox] checkboxLayout = VLayout(checkboxesToLayout) @@ -164,7 +161,7 @@ checkboxLayout.packRelativeTo(viewToPackCheckboxesUnder, Pack.Below) checkboxLayout.fill(Pack.Left) checkboxLayout.fill(Pack.Right) -if edition == 'se': +if appmode == 'standard': smallFilesThresholdText.packRelativeTo(ignoreSmallFilesBox, Pack.Below, margin=4) checkForUpdatesBox.packRelativeTo(smallFilesThresholdText, Pack.Below, margin=4) checkForUpdatesBox.fill(Pack.Right) diff --git a/cocoa/base/ui/prioritize_dialog.py b/cocoa/ui/prioritize_dialog.py similarity index 100% rename from cocoa/base/ui/prioritize_dialog.py rename to cocoa/ui/prioritize_dialog.py diff --git a/cocoa/base/ui/problem_dialog.py b/cocoa/ui/problem_dialog.py similarity index 100% rename from cocoa/base/ui/problem_dialog.py rename to cocoa/ui/problem_dialog.py diff --git a/cocoa/base/ui/result_window.py b/cocoa/ui/result_window.py similarity index 100% rename from cocoa/base/ui/result_window.py rename to cocoa/ui/result_window.py diff --git a/cocoa/wscript b/cocoa/wscript index 9ce28eec..89acb3dc 100644 --- a/cocoa/wscript +++ b/cocoa/wscript @@ -9,13 +9,8 @@ out = 'build' def options(opt): opt.load('compiler_c python') - opt.add_option('--edition', default='se', help="dupeGuru edition to build (se, me pe)") 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 conf.env.CC = 'clang' # 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) # The rest is standard WAF code that you can find the the python and macapp demos. conf.load('compiler_c python') - conf.check_python_version((3,3,0)) + conf.check_python_version((3,4,0)) conf.check_python_headers() conf.env.FRAMEWORK_COCOA = 'Cocoa' conf.env.ARCH_COCOA = ['x86_64'] @@ -53,8 +48,8 @@ def build(ctx): 'controllers/HSOutline', 'controllers/HSPopUpList', 'controllers/HSSelectableList', 'controllers/HSTextField', 'controllers/HSProgressWindow'] 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_src = sum([ctx.srcnode.ant_glob('%s/*.m' % folder) for folder in project_folders], []) + project_folders = [ctx.srcnode, ctx.srcnode.find_dir('autogen')] + project_src = ctx.srcnode.ant_glob('autogen/*.m') + ctx.srcnode.ant_glob('*.m') # Compile ctx.program( diff --git a/core/app.py b/core/app.py index a5fcedde..1da07a52 100644 --- a/core/app.py +++ b/core/app.py @@ -323,6 +323,14 @@ class DupeGuru(Broadcaster): self.notify('dupes_selected') #--- 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): if self.app_mode == AppMode.Picture: return pe.prioritize.all_categories() @@ -743,7 +751,7 @@ class DupeGuru(Broadcaster): def do(j): j.set_progress(0, tr("Collecting files to scan")) 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: files = list(self.directories.get_files(fileclasses=self.fileclasses, j=j)) if self.options['ignore_hardlink_matches']: @@ -794,12 +802,7 @@ class DupeGuru(Broadcaster): @property def 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] + return self._get_fileclasses() @property def SCANNER_CLASS(self):