From 177690ee8ca63a9953b254b3e15a0694b126133b Mon Sep 17 00:00:00 2001 From: Elias Lundell Date: Wed, 20 Aug 2025 01:59:55 +0200 Subject: [PATCH 1/2] Add Copy to Action --- Base.lproj/MainMenu.xib | 12 ++++++ CreeveyController.h | 2 + CreeveyController.m | 75 +++++++++++++++++++++++++++++++++++- Localizable.xcstrings | 16 ++++++++ mul.lproj/MainMenu.xcstrings | 24 ++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index 4d1d100..4550a78 100644 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -122,6 +122,18 @@ + + + + + + + + + + + + CA diff --git a/CreeveyController.h b/CreeveyController.h index 6216776..b874ced 100644 --- a/CreeveyController.h +++ b/CreeveyController.h @@ -45,6 +45,8 @@ NSDirectoryEnumerator *CreeveyEnumerator(NSString *path, BOOL recurseSubfolders) - (IBAction)setDesktopPicture:(id)sender; - (IBAction)moveSelectedFiles:(id)sender; - (IBAction)moveSelectedFilesAgain:(id)sender; +- (IBAction)copySelectedFiles:(id)sender; +- (IBAction)copySelectedFilesAgain:(id)sender; - (IBAction)moveToTrash:(id)sender; - (IBAction)transformJpeg:(id)sender; - (IBAction)sortThumbnails:(id)sender; diff --git a/CreeveyController.m b/CreeveyController.m index a7a2608..6d341b7 100644 --- a/CreeveyController.m +++ b/CreeveyController.m @@ -239,6 +239,7 @@ - (void)applicationWillFinishLaunching:(NSNotification *)notification { [filetypes removeObject:type]; } [self updateMoveToMenuItem]; + [self updateCopyToMenuItem]; [self updateAlternateSlideshowMenuItem]; [self updateAppearance]; @@ -484,6 +485,15 @@ - (void)updateMoveToMenuItem { item.title = [NSString stringWithFormat:NSLocalizedString(@"Move to “%@” Again", @"File menu"), name]; } +- (void)updateCopyToMenuItem { + NSString *path = [NSUserDefaults.standardUserDefaults stringForKey:@"lastUsedCopyToFolder"]; + if (path == nil) return; + NSMenu *m = [NSApp.mainMenu itemWithTag:FILE_MENU].submenu; + NSMenuItem *item = [m itemWithTag:COPY_TO_AGAIN]; + NSString *name = [NSFileManager.defaultManager displayNameAtPath:path]; + item.title = [NSString stringWithFormat:NSLocalizedString(@"Copy to “%@” Again", @"File menu"), name]; +} + - (void)updateAlternateSlideshowMenuItem { NSMenu *m = [NSApp.mainMenu itemWithTag:SLIDESHOW_MENU].submenu; NSMenuItem *item = [m itemWithTag:BEGIN_SLIDESHOW_ALTERNATE]; @@ -524,6 +534,36 @@ - (void)moveSelectedFilesTo:(NSURL *)dest { [creeveyWindows makeObjectsPerformSelector:@selector(filesWereUndeleted:) withObject:[moved valueForKey:@"path"]]; } +- (void)copySelectedFilesTo:(NSURL *)dest { + NSString *curr = slidesWindow.isMainWindow ? slidesWindow.basePath : frontWindow.path; + if ([dest isEqual:[NSURL fileURLWithPath:curr]]) return; + + NSArray *files = slidesWindow.isMainWindow ? @[slidesWindow.currentFile] : frontWindow.currentSelection; + NSMutableArray *paths = [NSMutableArray array]; + NSMutableArray *copied = [NSMutableArray arrayWithCapacity:files.count]; + NSMutableArray *notCopied = [NSMutableArray array]; + + NSError * __autoreleasing err; + for (NSString *f in files) { + NSURL *destUrl = [dest URLByAppendingPathComponent:f.lastPathComponent]; + if ([NSFileManager.defaultManager copyItemAtPath:f toPath:destUrl.path error:&err]) { + [paths addObject:f]; + [copied addObject:destUrl]; + } else { + [notCopied addObject:f]; + } + } + if (notCopied.count) { + NSAlert *alert = [[NSAlert alloc] init]; + if (notCopied.count == 1) { + alert.informativeText = [NSString stringWithFormat:NSLocalizedString(@"The file “%@” could not be copied because of an error: %@", @""), notCopied[0].lastPathComponent, err.localizedDescription]; + } else { + alert.informativeText = [NSString stringWithFormat:NSLocalizedString(@"%lu files could not be copied because of an error.",@""), notCopied.count]; + } + [alert runModal]; + } +} + - (IBAction)moveSelectedFiles:(id)sender { NSOpenPanel *op = [NSOpenPanel openPanel]; op.canChooseFiles = NO; @@ -541,6 +581,24 @@ - (IBAction)moveSelectedFilesAgain:(id)sender { [self moveSelectedFilesTo:dest]; } +- (IBAction)copySelectedFiles:(id)sender { + NSOpenPanel *op = [NSOpenPanel openPanel]; + op.canChooseFiles = NO; + op.canChooseDirectories = YES; + if ([op runModal] != NSModalResponseOK) return; + NSURL *dest = op.URL; + [self copySelectedFilesTo:dest]; + [NSUserDefaults.standardUserDefaults setObject:dest.path forKey:@"lastUsedCopyToFolder"]; + [self updateCopyToMenuItem]; +} + +- (IBAction)copySelectedFilesAgain:(id)sender { + NSString *folder = [NSUserDefaults.standardUserDefaults stringForKey:@"lastUsedCopyToFolder"]; + NSURL *dest = [NSURL fileURLWithPath:folder isDirectory:YES]; + [self copySelectedFilesTo:dest]; +} + + // returns 1 if successful // unsuccessful: 0 user wants to continue; 2 cancel/abort - (char)trashFile:(NSString *)fullpath numLeft:(NSUInteger)numFiles resultingURL:(NSURL **)newURL { @@ -838,6 +896,8 @@ -(void)applicationDidChangeScreenParameters:(NSNotification *)notification { BEGIN_SLIDESHOW_ALTERNATE, MOVE_TO, MOVE_TO_AGAIN, + COPY_TO, + COPY_TO_AGAIN, JPEG_OP = 100, ROTATE_L = 107, ROTATE_R = 105, @@ -875,6 +935,7 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { NSUInteger numSelected = frontWindow ? frontWindow.selectedIndexes.count : 0; BOOL writable, isjpeg; NSString *moveTo; + NSString *copyTo; switch (test_t) { case NEW_TAB: @@ -883,6 +944,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { moveTo = [NSUserDefaults.standardUserDefaults stringForKey:@"lastUsedMoveToFolder"]; // fall through case MOVE_TO: + case COPY_TO_AGAIN: + copyTo = [NSUserDefaults.standardUserDefaults stringForKey:@"lastUsedCopyToFolder"]; + case COPY_TO: case MOVE_TO_TRASH: case JPEG_OP: // only when slides isn't loading cache! @@ -893,8 +957,15 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { [NSFileManager.defaultManager isDeletableFileAtPath: slidesWindow.currentFile] : numSelected > 0 && frontWindow && frontWindow.currentFilesDeletable; - if (t != JPEG_OP) return writable && (t == MOVE_TO_AGAIN ? moveTo != nil : YES); - + if (t != JPEG_OP) { + if (t == MOVE_TO_AGAIN) { + return writable && moveTo != nil; + } else if (t == COPY_TO_AGAIN) { + return writable && copyTo != nil; + } else { + return writable && YES; + } + } isjpeg = slidesWindow.isMainWindow ? slidesWindow.currentFile && FileIsJPEG(slidesWindow.currentFile) : numSelected > 0 && frontWindow && FilesContainJPEG(frontWindow.currentSelection); diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 414fa60..97530a7 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -348,6 +348,9 @@ } } } + }, + "%lu files could not be copied because of an error." : { + }, "%lu files could not be moved because of an error." : { "localizations" : { @@ -908,6 +911,9 @@ } } }, + "Copy to “%@” Again" : { + "comment" : "File menu" + }, "Could not check for update - an error occurred while connecting to the server." : { "localizations" : { "de" : { @@ -2056,6 +2062,16 @@ } } }, + "The file “%@” could not be copied because of an error: %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "The file “%1$@” could not be copied because of an error: %2$@" + } + } + } + }, "The file “%@” could not be moved because of an error: %@" : { "localizations" : { "de" : { diff --git a/mul.lproj/MainMenu.xcstrings b/mul.lproj/MainMenu.xcstrings index 3982c59..4a84f8f 100644 --- a/mul.lproj/MainMenu.xcstrings +++ b/mul.lproj/MainMenu.xcstrings @@ -3847,6 +3847,18 @@ } } }, + "b2k-58-4mj.title" : { + "comment" : "Class = \"NSMenuItem\"; title = \"Copy To Folder Again\"; ObjectID = \"b2k-58-4mj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy To Folder Again" + } + } + } + }, "c0g-qO-OVm.title" : { "comment" : "Class = \"NSMenuItem\"; title = \"Move To Folder Again\"; ObjectID = \"c0g-qO-OVm\";", "extractionState" : "extracted_with_value", @@ -3889,6 +3901,18 @@ } } }, + "e4f-92-7kq.title" : { + "comment" : "Class = \"NSMenuItem\"; title = \"Copy To...\"; ObjectID = \"e4f-92-7kq\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Copy To..." + } + } + } + }, "FZ5-3D-1VQ.title" : { "comment" : "Class = \"NSMenuItem\"; title = \"Begin Slideshow In Window\"; ObjectID = \"FZ5-3D-1VQ\";", "extractionState" : "extracted_with_value", From ad4d5f8c479c5a292ef0bcfa485353a11994633c Mon Sep 17 00:00:00 2001 From: Elias Lundell Date: Wed, 20 Aug 2025 02:01:31 +0200 Subject: [PATCH 2/2] Fix whitespace --- CreeveyController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CreeveyController.m b/CreeveyController.m index 6d341b7..bc83ab8 100644 --- a/CreeveyController.m +++ b/CreeveyController.m @@ -598,7 +598,6 @@ - (IBAction)copySelectedFilesAgain:(id)sender { [self copySelectedFilesTo:dest]; } - // returns 1 if successful // unsuccessful: 0 user wants to continue; 2 cancel/abort - (char)trashFile:(NSString *)fullpath numLeft:(NSUInteger)numFiles resultingURL:(NSURL **)newURL { @@ -966,6 +965,7 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { return writable && YES; } } + isjpeg = slidesWindow.isMainWindow ? slidesWindow.currentFile && FileIsJPEG(slidesWindow.currentFile) : numSelected > 0 && frontWindow && FilesContainJPEG(frontWindow.currentSelection);