NSSavePanel Changing File Name Extensions With AccessoryView - objective-c

I have NSSavePanel with accessoryView to let the user select a graphic format so that they can save an image (NSImage) as a file. So far, I have the following. (I'm skipping some lines to make it short.)
- (void)exportFile {
NSString *filename;
if (formatIndex1 == 0) { // Default selection by user in Preferences
filename = #"Untitled.bmp";
}
else if (formatIndex1 == 1) {
filename = #"Untitled.gif";
}
...
[panel setAllowedFileTypes:[[NSArray alloc] initWithObjects:#"bmp",#"gif",#"jpg",#"jp2",#"png",nil]];
[panel setAllowsOtherFileTypes:NO];
[panel setExtensionHidden:NO];
[panel setCanCreateDirectories:YES];
[panel setNameFieldStringValue:filename];
[panel setAccessoryView:accessoryView1];
[formatMenu1 setAction:#selector(dropMenuChange:)]; // formatMenu1 is NSPopUpButton
[formatMenu1 setTarget:self];
[panel beginSheetModalForWindow:window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
// getting panel url
}
}];
}
-(void)dropMenuChange:(NSPopUpButton *)sender {
NSSavePanel *savePanel = (NSSavePanel *)[sender window];
[savePanel setNameFieldStringValue:#"..."];
}
I'm not 100% sure that I'm doing it right. What I want to achieve is that I want to append the right extension to the current file name whenever the user selects a file format on accessoryView's NSPopUpButton. Is there a magical way of doing that? Or do I have to set the current file name with the right extension to setNameFieldStringValue programmatically for myself?
Thank you for your help.

What I want to achieve is that I want to append the right extension to the current file name whenever the user selects a file format on accessoryView's NSPopUpButton. Is there a magical way of doing that?
Yes, there is. You need not do it yourself with setNameFieldStringValue: , let the savePanel do it. Let us assume fileName is a full path like /Users/hg/Pictures/2013/08/Airplanes/pic123.png and it exists an accessoryView for the savePanel with a matrix of radio buttons. Each button has a title like #"jpg" or #"png" or... The action of the matrix is -selectFileType:
- (IBAction) selectFileType:(id)sender
{
[savePanel setAllowedFileTypes:#[ [[sender selectedCell] title] ] ];
// this will set the right extension
}
For using the savePanel I tried the following code:
- (void) saveImage:(NSImage *) theImg
{
savePanel = [NSSavePanel savePanel];
NSString *imageName = [fileName lastPathComponent];
NSString *suffix = [imageName pathExtension];
NSString *baseName = [imageName stringByDeletingPathExtension];
// prepare the savePanel
[savePanel setAccessoryView:accessoryView];
[savePanel setAllowedFileTypes:#[ suffix ] ];
[savePanel setDirectoryURL:[NSURL fileURLWithPath:fileName]]; // convert to URL
[savePanel setNameFieldStringValue:baseName ]; // without extension !
// savePanel does append the suffix
// and now start the savePanel and choose the wanted fileType
int rtn = [savePanel runModal]; // preferred method since 10.6
if( rtn==NSFileHandlingPanelCancelButton) return; // do nothing
// finally create and save the file
if( [[[savePanel allowedFileTypes] objectAtIndex:0] isEqualToString:#"jpg" ){
// save as jpg-file
}
// check for other fileTypes
. . .
}

You have to set current file name with the right extension using setNameFieldStringValue
-(void)dropMenuChange:(NSPopUpButton *)sender {
NSSavePanel *savePanel = (NSSavePanel *)[sender window];
NSString *nameFieldString = [savePanel nameFieldStringValue];
NSString *nameFieldStringWithExt = [NSString stringWithFormat:#"%#.%#",[savePanel nameFieldStringValue], popupvalue];
[savePanel setNameFieldStringValue:nameFieldStringWithExt];
}

Related

Objective C - Asking to save before you quit

I have an Objective-C/Cocoa text-editor I'm working on(It's a mac app, not iOS).
The current challenge I'm facing is having a dialog when someone try to quit without saving.
I already have a shared bool called issavedsomewhere to tell if the user has saved or not. I even have the textview data available as a shared variable, so I can access it from any class.
I'm thinking that I'd put the save dialog in the (void)applicationWillTerminate method.
My current saving code is simple:
NSSavePanel *panel = [NSSavePanel savePanel];
// NSInteger result;
[panel setAllowedFileTypes:#[#"txt"]];
[panel beginWithCompletionHandler:^(NSInteger result){
//OK button pushed
if (result == NSFileHandlingPanelOKButton) {
// Close panel before handling errors
[panel orderOut:self];
// Do what you need to do with the selected path
NSString *selpath = [[panel URL] path];
NSError *error;
BOOL didOK = [[theDATA.textvieww string]writeToFile:selpath atomically:NO encoding:NSUTF8StringEncoding error:&error];
if(!didOK){
//error while saving
NSLog(#"Couldn't Save!!! -> %#", [error localizedFailureReason]);
}else{
//success!
theDATA.issavedsomewhere=YES;
theDATA.filepath=selpath;
theDATA.filename=[[[panel URL] path] lastPathComponent];
}
}/*Button other than the OK button was pushed*/
else{
}
}];
All it is, is an NSSavePanel that pops up and asks where you want to save.
The problem is that when I add it to (void)applicationWillTerminate, it doesn't wait for the user to answer.
Your help and ideas are appreciated:)
There are better ways to do this within the Cocoa framework, such as by using NSDocument and its ilk. However, it is possible to do what you want to do.
You first want to return NSTerminateLater in applicationShouldTerminate::
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
if (theDATA.issavedsomewhere) {
return NSTerminateLater;
}
return NSTerminateNow;
}
Then, you handler should ultimately call [NSApp replyToApplicationShouldTerminate:YES] when it is done:
NSSavePanel *panel = [NSSavePanel savePanel];
// NSInteger result;
[panel setAllowedFileTypes:#[#"txt"]];
[panel beginWithCompletionHandler:^(NSInteger result){
//OK button pushed
if (result == NSFileHandlingPanelOKButton) {
// Close panel before handling errors
[panel orderOut:self];
// Do what you need to do with the selected path
NSString *selpath = [[panel URL] path];
NSError *error;
BOOL didOK = [[theDATA.textvieww string]writeToFile:selpath atomically:NO encoding:NSUTF8StringEncoding error:&error];
if(!didOK){
//error while saving
NSLog(#"Couldn't Save!!! -> %#", [error localizedFailureReason]);
}else{
//success!
theDATA.issavedsomewhere=YES;
theDATA.filepath=selpath;
theDATA.filename=[[[panel URL] path] lastPathComponent];
}
}/*Button other than the OK button was pushed*/
else{
}
[NSApp replyToApplicationShouldTerminate:YES];
}];
One possibility is to just save in a temp file and on launch check to see if the tempfile exists and perhaps ask the user if he want to use it or not.
Since changes to my data can happen in multiple places I simply post a "data modified" notification whenever this happens:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"DataModifiedNotification"
object:self];
My app delegate has a dataSaved property and adds itself as an observer of this notification and sets its value to NO whenever the data is mutated:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.dataSaved = YES; // set to NO when data mutated
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(receiveDataModifiedNotification:)
name:#"DataModifiedNotification"
object:nil];
}
-(void)receiveDataModifiedNotification:(NSNotification *) notification {
self.dataSaved = NO;
}
The the app delegate asks the user if they really want to quit to give
them the opportunity to save the data (done elsewhere):
-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
if (!self.dataSaved) {
NSAlert *alert = [[NSAlert alloc] init];
alert.alertStyle = NSAlertStyleWarning;
alert.messageText = #"Data unsaved!";
alert.informativeText = #"Do you really want to Quit the application?";
[alert addButtonWithTitle:#"Quit"];
[alert addButtonWithTitle:#"Cancel"];
[alert beginSheetModalForWindow:self.window
completionHandler:^(NSModalResponse returnCode) {
const BOOL shouldQuit = returnCode == NSAlertFirstButtonReturn;
[NSApp replyToApplicationShouldTerminate: shouldQuit];
}];
return NSTerminateLater;
}
return NSTerminateNow;
}
Note: Set app property NSSupportsSuddenTermination to NO
which is labeled "Application can be killed immediately when user is shutting down or logging out" in Info.plist.

Displaying text in text view from NSArray

This method found in the AppDelagate, loads a text file of my choosing and splits the context of the text file into an array.
Im having trouble displaying the contents of the array in my NSScrollview * called self.textView.
I am not sure how to update the text view with each member of the array.
- (IBAction)loadButton:(id)sender {
NSOpenPanel *panel = [NSOpenPanel openPanel];
if ([panel runModal] == NSFileHandlingPanelOKButton) {
NSURL *bookUrl = [panel URL];
NSString *contents = [NSString stringWithContentsOfURL: bookUrl encoding: NSASCIIStringEncoding error: NULL];
NSArray *loadedBook = [contents componentsSeparatedByString:#"#NP#"];
self.textView.value = loadedBook[0];
}
}
The right method is setString:, which is declared in NSText.
your code should be:
- (IBAction)loadButton:(id)sender {
NSOpenPanel *panel = [NSOpenPanel openPanel];
if ([panel runModal] == NSFileHandlingPanelOKButton) {
NSURL *bookUrl = [panel URL];
NSString *contents = [NSString stringWithContentsOfURL: bookUrl encoding: NSASCIIStringEncoding error: NULL];
NSArray *loadedBooks = [contents componentsSeparatedByString:#"#NP#"];
[self.textView setString:bookStr];
}
}
UPDATE
Take a look at this question, to see how to add text to a NSScrollView

Get file path from dialog box and print to NSLog (Cocoa Objective-C)

I have a project that opens a file browser and when I select and open a file, I would like to print the file path for testing.
Here is my .main file:
#implementation SaveMenuCntroller
- (IBAction)askUserForImage:(id)sender {
NSOpenPanel* panel = [NSOpenPanel openPanel];
// Let the user select any images supported by
// the NSImage class.
NSArray* imageTypes = [NSImage imageTypes];
[panel setAllowedFileTypes:imageTypes];
[panel beginWithCompletionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
}
}];
}
#end
Anyone have experience with this that could help?
You can do this:
NSLog(#"selected file: %#", panel.URLs);
The URLs property is an array to support multiple selection. You could also do:
NSLog(#"selected file: %#", panel.URLs.firstObject);
And, finally, if you really want a path string instead of a URL, do:
NSLog(#"selected file: %#", [panel.URLs.firstObject path]);

How to change a deprecated beginSheetForDirectory method

I have an app that was using beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo:.
I checked Apple documentation which said it's deprecated and to use another method instead:
Presents a Save panel as a sheet with a specified path and,
optionally, a specified file in that path. (Deprecated in Mac OS X
v10.6. Use beginSheetModalForWindow:completionHandler: instead.)
My question is how to change this code to the new one?
// [savePanel setRequiredFileType:#"png"];
[savePanel beginSheetForDirectory:nil
file:nil
modalForWindow:[self window]
modalDelegate:self
didEndSelector:#selector(didEndSaveSheet:returnCode:conextInfo:)
contextInfo:NULL];
You're looking for the beginSheetModalForWindow:completionHandler: method.
Example:
NSSavePanel *savePanel = [NSSavePanel savePanel];
[savePanel beginSheetModalForWindow:_window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSURL *savePath = [[savePanel URLs] objectAtIndex:0];
} else {
[savePanel close];
}
}];

Calling methods after comparing string objects from NSArray Cocoa

I am selecting files through this code:
- (IBAction)selectFile:(id)sender {
// Create the File Open Dialog class.
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
[openDlg setPrompt:#"Select"];
fileTypes = [NSArray arrayWithObjects:#"wmv", #"3gp", #"mp4", #"avi", #"mp3", #"mma", #"wav", #"jpeg", #"png", #"jpg", #"tiff", nil];
// NSArray *JpegfileTypes = [NSArray arrayWithObjects:#"jpeg", #"png", #"jpg", #"tiff", #"mp3" nil];
// Enable the selection of files in the dialog.
[openDlg setCanChooseFiles:YES];
//Enable multiple selection of files
[openDlg setAllowsMultipleSelection:YES];
// Enable the selection of directories in the dialog.
[openDlg setCanChooseDirectories:YES];
// Display the dialog. If the OK button was pressed,
// process the files.
if ( [openDlg runModalForDirectory:nil file:nil types:fileTypes] == NSOKButton )
{
// Get an array containing the full filenames of all
// files and directories selected.
files = [[openDlg filenames] retain];
int i; // Loop counter.
// Loop through all the files and process them.
for( i = 0; i < [files count]; i++ )
{
NSString *tempFilePath = [files objectAtIndex:i];
NSLog(#"tempFilePath::: %#",tempFilePath);
inputFilePath = [[files objectAtIndex:i] retain];
NSLog(#"filename::: %#", inputFilePath);
// Do something with the filename.
[selectedFile setStringValue:inputFilePath];
NSLog(#"selectedFile:::: %#", selectedFile);
}
}
}
Then after selection I have used this code to process the selected file.
- (IBAction)setMessage:(id)sender {
[fileGenProgress startAnimation:self];
NSString *message = [[NSString alloc] initWithFormat:#"Started"];
[lblMessage setStringValue:message];
[message release];
[self startProcessingVideoFile];
[self startProcessingAudioFile];
[self startProcessingJpg];
}
The issue I am facing is that, I am not getting that how would I compare the different strings like if the selected file was 3gp/mp4 or jpg or mp3. As if user has selected some video file then the method [self startProcessingVideoFile]; will run and if he has selected some JPG or PNG etc file then [self startProcessingAudioFile]; method will run.
As selected will be having a path not only the extension of the file. So in this scenario how can I force the - (IBAction)setMessage:(id)sender method to run the appropriate method.
You can get string's extension (format) like this:
NSString *extension = [stringPath pathExtension];
And for comparing now it's very easy when You know what extension Your file is. For example:
NSLog(extension);
if ([extension isEqualToString:#"3gp"] || [ext isEqualToString:#"mp4"]) {
[self startProcessingVideoFile];
}
and etc.
Update:
Your IBAction:SetMessage should look like this:
- (IBAction)setMessage:(id)sender {
[fileGenProgress startAnimation:self];
NSString *message = [[NSString alloc] initWithFormat:#"Started"];
[lblMessage setStringValue:message];
[message release];
NSString *extension = [inputFilePath pathExtension];
NSLog(extension);
if ([extension isEqualToString:#"3gp"] || [ext isEqualToString:#"mp4"]) {
[self startProcessingVideoFile];
}
// And etc for others formats.
//[self startProcessingAudioFile];
//[self startProcessingJpg];
}