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 42f8641e..00000000 Binary files a/cocoa/me/dupeguru.icns and /dev/null differ diff --git a/cocoa/pe/AppDelegate.h b/cocoa/pe/AppDelegate.h deleted file mode 100644 index af7cbaa8..00000000 --- a/cocoa/pe/AppDelegate.h +++ /dev/null @@ -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 -#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 c143ed86..00000000 Binary files a/cocoa/pe/dupeguru.icns and /dev/null differ diff --git a/cocoa/se/AppDelegate.h b/cocoa/se/AppDelegate.h deleted file mode 100644 index e04ed83f..00000000 --- a/cocoa/se/AppDelegate.h +++ /dev/null @@ -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 -#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):