mirror of
				https://github.com/arsenetar/dupeguru-cocoa.git
				synced 2025-09-11 17:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			386 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| /* 
 | |
| 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 "HSPyUtil.h"
 | |
| #import "Consts.h"
 | |
| #import "Dialogs.h"
 | |
| #import "Utils.h"
 | |
| #import "ValueTransformers.h"
 | |
| #import "DetailsPanelPicture.h"
 | |
| #import "PreferencesPanelStandard_UI.h"
 | |
| #import "PreferencesPanelMusic_UI.h"
 | |
| #import "PreferencesPanelPicture_UI.h"
 | |
| 
 | |
| @implementation AppDelegate
 | |
| 
 | |
| @synthesize recentResultsMenu;
 | |
| @synthesize columnsMenu;
 | |
| 
 | |
| + (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"];
 | |
|     [d setObject:b2n(NO) forKey:@"useRegexpFilter"];
 | |
|     [d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"];
 | |
|     [d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
 | |
|     [d setObject:b2n(NO) forKey:@"DebugMode"];
 | |
|     [d setObject:@"" forKey:@"CustomCommand"];
 | |
|     [d setObject:[NSArray array] forKey:@"recentDirectories"];
 | |
|     [d setObject:[NSArray array] forKey:@"columnsOrder"];
 | |
|     [d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
 | |
|     return d;
 | |
| }
 | |
| 
 | |
| + (void)initialize
 | |
| {
 | |
|     HSVTAdd *vt = [[[HSVTAdd alloc] initWithValue:4] autorelease];
 | |
|     [NSValueTransformer setValueTransformer:vt forName:@"vtRowHeightOffset"];
 | |
|     NSDictionary *d = [self defaultPreferences];
 | |
|     [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
 | |
|     [[NSUserDefaults standardUserDefaults] registerDefaults:d];
 | |
| }
 | |
| 
 | |
| - (void)awakeFromNib
 | |
| {
 | |
|     model = [[PyDupeGuru alloc] init];
 | |
|     [model bindCallback:createCallback(@"DupeGuruView", self)];
 | |
|     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];
 | |
|     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"];
 | |
|     _recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu];
 | |
|     [_recentResults setDelegate:self];
 | |
|     _directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self];
 | |
|     _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]];
 | |
|      // Lazily loaded
 | |
|     _aboutBox = nil;
 | |
|     _preferencesPanel = nil;
 | |
|     _resultWindow = nil;
 | |
|     _detailsPanel = nil;
 | |
|     [[[self directoryPanel] window] makeKeyAndOrderFront:self];
 | |
| }
 | |
| 
 | |
| /* Virtual */
 | |
| 
 | |
| - (PyDupeGuru *)model
 | |
| {
 | |
|     return model;
 | |
| }
 | |
| 
 | |
| - (DetailsPanel *)createDetailsPanel
 | |
| {
 | |
|     NSInteger appMode = [self getAppMode];
 | |
|     if (appMode == AppModePicture) {
 | |
|         return [[DetailsPanelPicture alloc] initWithApp:model];
 | |
|     }
 | |
|     else {
 | |
|         return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)setScanOptions
 | |
| {
 | |
|     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"])];
 | |
|     NSInteger smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
 | |
|     NSInteger 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 */
 | |
| - (ResultWindow *)resultWindow
 | |
| {
 | |
|     return _resultWindow;
 | |
| }
 | |
| 
 | |
| - (DirectoryPanel *)directoryPanel
 | |
| {
 | |
|     return _directoryPanel;
 | |
| }
 | |
| 
 | |
| - (DetailsPanel *)detailsPanel
 | |
| {
 | |
|     return _detailsPanel;
 | |
| }
 | |
| 
 | |
| - (HSRecentFiles *)recentResults
 | |
| {
 | |
|     return _recentResults;
 | |
| }
 | |
| 
 | |
| - (NSInteger)getAppMode
 | |
| {
 | |
|     return [model getAppMode];
 | |
| }
 | |
| 
 | |
| - (void)setAppMode:(NSInteger)appMode
 | |
| {
 | |
|     [model setAppMode:appMode];
 | |
|     if (_preferencesPanel != nil) {
 | |
|         [_preferencesPanel release];
 | |
|         _preferencesPanel = nil;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Actions */
 | |
| - (IBAction)clearPictureCache:(id)sender
 | |
| {
 | |
|     NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @"");
 | |
|     if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
 | |
|         return;
 | |
|     [model clearPictureCache];
 | |
| }
 | |
| 
 | |
| - (IBAction)loadResults:(id)sender
 | |
| {
 | |
|     NSOpenPanel *op = [NSOpenPanel openPanel];
 | |
|     [op setCanChooseFiles:YES];
 | |
|     [op setCanChooseDirectories:NO];
 | |
|     [op setCanCreateDirectories:NO];
 | |
|     [op setAllowsMultipleSelection:NO];
 | |
|     [op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
 | |
|     [op setTitle:NSLocalizedString(@"Select a results file to load", @"")];
 | |
|     if ([op runModal] == NSOKButton) {
 | |
|         NSString *filename = [[[op URLs] objectAtIndex:0] path];
 | |
|         [model loadResultsFrom:filename];
 | |
|         [[self recentResults] addFile:filename];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (IBAction)openWebsite:(id)sender
 | |
| {
 | |
|     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.hardcoded.net/dupeguru/"]];
 | |
| }
 | |
| 
 | |
| - (IBAction)openHelp:(id)sender
 | |
| {
 | |
|     NSBundle *b = [NSBundle mainBundle];
 | |
|     NSString *p = [b pathForResource:@"index" ofType:@"html" inDirectory:@"help"];
 | |
|     NSURL *u = [NSURL fileURLWithPath:p];
 | |
|     [[NSWorkspace sharedWorkspace] openURL:u];
 | |
| }
 | |
| 
 | |
| - (IBAction)showAboutBox:(id)sender
 | |
| {
 | |
|     if (_aboutBox == nil) {
 | |
|         _aboutBox = [[HSAboutBox alloc] initWithApp:model];
 | |
|     }
 | |
|     [[_aboutBox window] makeKeyAndOrderFront:nil];
 | |
| }
 | |
| 
 | |
| - (IBAction)showDirectoryWindow:(id)sender
 | |
| {
 | |
|     [[[self directoryPanel] window] makeKeyAndOrderFront:nil];
 | |
| }
 | |
| 
 | |
| - (IBAction)showPreferencesPanel:(id)sender
 | |
| {
 | |
|     if (_preferencesPanel == 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];
 | |
| }
 | |
| 
 | |
| - (IBAction)showResultWindow:(id)sender
 | |
| {
 | |
|     [[[self resultWindow] window] makeKeyAndOrderFront:nil];
 | |
| }
 | |
| 
 | |
| - (IBAction)showIgnoreList:(id)sender
 | |
| {
 | |
|     [model showIgnoreList];
 | |
| }
 | |
| 
 | |
| - (IBAction)startScanning:(id)sender
 | |
| {
 | |
|     [[self directoryPanel] startDuplicateScan];
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Delegate */
 | |
| - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
 | |
| {
 | |
|     [model loadSession];
 | |
| }
 | |
| 
 | |
| - (void)applicationWillBecomeActive:(NSNotification *)aNotification
 | |
| {
 | |
|     if (![[[self directoryPanel] window] isVisible]) {
 | |
|         [[self directoryPanel] showWindow:NSApp];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
 | |
| {
 | |
|     if ([model resultsAreModified]) {
 | |
|         NSString *msg = NSLocalizedString(@"You have unsaved results, do you really want to quit?", @"");
 | |
|         if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) { // NO
 | |
|             return NSTerminateCancel;
 | |
|         }
 | |
|     }
 | |
|     return NSTerminateNow;
 | |
| }
 | |
| 
 | |
| - (void)applicationWillTerminate:(NSNotification *)aNotification
 | |
| {
 | |
|     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
 | |
|     NSInteger sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
 | |
|     if (sc >= 10) {
 | |
|         sc = -1;
 | |
|         [model purgeIgnoreList];
 | |
|     }
 | |
|     sc++;
 | |
|     [model saveSession];
 | |
|     [ud setInteger:sc forKey:@"sessionCountSinceLastIgnorePurge"];
 | |
|     // NSApplication does not release nib instances objects, we must do it manually
 | |
|     // Well, it isn't needed because the memory is freed anyway (we are quitting the application
 | |
|     // But I need to release HSRecentFiles so it saves the user defaults
 | |
|     [_directoryPanel release];
 | |
|     [_recentResults release];
 | |
| }
 | |
| 
 | |
| - (void)recentFileClicked:(NSString *)path
 | |
| {
 | |
|     [model loadResultsFrom:path];
 | |
| }
 | |
| 
 | |
| 
 | |
| /* model --> view */
 | |
| - (void)showMessage:(NSString *)msg
 | |
| {
 | |
|     [Dialogs showMessage:msg];
 | |
| }
 | |
| 
 | |
| - (BOOL)askYesNoWithPrompt:(NSString *)prompt
 | |
| {
 | |
|     return [Dialogs askYesNo:prompt] == NSAlertFirstButtonReturn;
 | |
| }
 | |
| 
 | |
| - (void)createResultsWindow
 | |
| {
 | |
|     if (_resultWindow != nil) {
 | |
|         [_resultWindow release];
 | |
|     }
 | |
|     if (_detailsPanel != nil) {
 | |
|         [_detailsPanel release];
 | |
|     }
 | |
|     // Warning: creation order is important
 | |
|     // If the details panel is not created first and that there are some results in the model
 | |
|     // (happens if we load results), a dupe selection event triggers a details refresh in the
 | |
|     // core before we have the chance to initialize it, and then we crash.
 | |
|     _detailsPanel = [self createDetailsPanel];
 | |
|     _resultWindow = [[ResultWindow alloc] initWithParentApp:self];
 | |
| }
 | |
| - (void)showResultsWindow
 | |
| {
 | |
|     [[[self resultWindow] window] makeKeyAndOrderFront:nil];
 | |
| }
 | |
| 
 | |
| - (void)showProblemDialog
 | |
| {
 | |
|     [_problemDialog showWindow:self];
 | |
| }
 | |
| 
 | |
| - (NSString *)selectDestFolderWithPrompt:(NSString *)prompt
 | |
| {
 | |
|     NSOpenPanel *op = [NSOpenPanel openPanel];
 | |
|     [op setCanChooseFiles:NO];
 | |
|     [op setCanChooseDirectories:YES];
 | |
|     [op setCanCreateDirectories:YES];
 | |
|     [op setAllowsMultipleSelection:NO];
 | |
|     [op setTitle:prompt];
 | |
|     if ([op runModal] == NSOKButton) {
 | |
|         return [[[op URLs] objectAtIndex:0] path];
 | |
|     }
 | |
|     else {
 | |
|         return nil;
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSString *)selectDestFileWithPrompt:(NSString *)prompt extension:(NSString *)extension
 | |
| {
 | |
|     NSSavePanel *sp = [NSSavePanel savePanel];
 | |
|     [sp setCanCreateDirectories:YES];
 | |
|     [sp setAllowedFileTypes:[NSArray arrayWithObject:extension]];
 | |
|     [sp setTitle:prompt];
 | |
|     if ([sp runModal] == NSOKButton) {
 | |
|         return [[sp URL] path];
 | |
|     }
 | |
|     else {
 | |
|         return nil;
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 |