2014-10-31 18:17:05 +00:00
|
|
|
// Copyright (c) 2013 GitHub, Inc.
|
2014-04-25 09:49:37 +00:00
|
|
|
// Use of this source code is governed by the MIT license that can be
|
2013-05-20 13:46:43 +00:00
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2014-03-16 00:30:26 +00:00
|
|
|
#include "atom/browser/ui/file_dialog.h"
|
2013-05-20 13:46:43 +00:00
|
|
|
|
|
|
|
#import <Cocoa/Cocoa.h>
|
2014-03-16 01:37:04 +00:00
|
|
|
#import <CoreServices/CoreServices.h>
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2014-03-16 01:37:04 +00:00
|
|
|
#include "atom/browser/native_window.h"
|
2015-01-10 01:45:50 +00:00
|
|
|
#include "base/files/file_util.h"
|
2015-03-11 00:00:55 +00:00
|
|
|
#include "base/mac/foundation_util.h"
|
2014-08-06 05:47:44 +00:00
|
|
|
#include "base/mac/mac_util.h"
|
|
|
|
#include "base/mac/scoped_cftyperef.h"
|
2013-05-20 13:46:43 +00:00
|
|
|
#include "base/strings/sys_string_conversions.h"
|
|
|
|
|
|
|
|
namespace file_dialog {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2014-08-06 05:47:44 +00:00
|
|
|
void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) {
|
|
|
|
NSMutableSet* file_type_set = [NSMutableSet set];
|
|
|
|
for (size_t i = 0; i < filters.size(); ++i) {
|
|
|
|
const Filter& filter = filters[i];
|
|
|
|
for (size_t j = 0; j < filter.second.size(); ++j) {
|
2015-08-21 04:15:20 +00:00
|
|
|
// If we meet a '*' file extension, we allow all the file types and no
|
|
|
|
// need to set the specified file types.
|
|
|
|
if (filter.second[j] == "*") {
|
|
|
|
[dialog setAllowsOtherFileTypes:YES];
|
|
|
|
return;
|
|
|
|
}
|
2014-08-06 05:47:44 +00:00
|
|
|
base::ScopedCFTypeRef<CFStringRef> ext_cf(
|
|
|
|
base::SysUTF8ToCFStringRef(filter.second[j]));
|
|
|
|
[file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())];
|
|
|
|
}
|
|
|
|
}
|
2015-12-30 20:38:02 +00:00
|
|
|
|
2015-12-31 10:58:16 +00:00
|
|
|
// Passing empty array to setAllowedFileTypes will cause exception.
|
2015-12-30 20:38:02 +00:00
|
|
|
NSArray* file_types = nil;
|
|
|
|
if ([file_type_set count])
|
|
|
|
file_types = [file_type_set allObjects];
|
|
|
|
|
2015-12-31 10:58:16 +00:00
|
|
|
[dialog setAllowedFileTypes:file_types];
|
2014-08-06 05:47:44 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
void SetupDialog(NSSavePanel* dialog,
|
2017-02-08 01:32:58 +00:00
|
|
|
const DialogSettings& settings) {
|
|
|
|
if (!settings.title.empty())
|
|
|
|
[dialog setTitle:base::SysUTF8ToNSString(settings.title)];
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
if (!settings.button_label.empty())
|
|
|
|
[dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)];
|
2016-05-06 18:10:31 +00:00
|
|
|
|
2017-02-09 19:25:05 +00:00
|
|
|
if (!settings.message.empty())
|
|
|
|
[dialog setMessage:base::SysUTF8ToNSString(settings.message)];
|
2017-02-01 15:34:21 +00:00
|
|
|
|
2017-02-09 19:25:05 +00:00
|
|
|
if (!settings.name_field_label.empty())
|
|
|
|
[dialog setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)];
|
2017-02-01 15:34:21 +00:00
|
|
|
|
2017-02-09 19:25:05 +00:00
|
|
|
[dialog setShowsTagField:settings.shows_tag_field];
|
2017-02-01 15:34:21 +00:00
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
NSString* default_dir = nil;
|
|
|
|
NSString* default_filename = nil;
|
2017-02-08 01:32:58 +00:00
|
|
|
if (!settings.default_path.empty()) {
|
|
|
|
if (base::DirectoryExists(settings.default_path)) {
|
|
|
|
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
|
2013-05-20 13:46:43 +00:00
|
|
|
} else {
|
2017-05-25 23:11:58 +00:00
|
|
|
if (settings.default_path.IsAbsolute()) {
|
|
|
|
default_dir =
|
|
|
|
base::SysUTF8ToNSString(settings.default_path.DirName().value());
|
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
default_filename =
|
2017-02-08 01:32:58 +00:00
|
|
|
base::SysUTF8ToNSString(settings.default_path.BaseName().value());
|
2013-05-20 13:46:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-15 17:27:34 +00:00
|
|
|
if (settings.filters.empty()) {
|
2017-05-28 19:20:22 +00:00
|
|
|
[dialog setAllowsOtherFileTypes:YES];
|
2017-06-15 17:27:34 +00:00
|
|
|
} else {
|
2017-06-03 15:48:18 +00:00
|
|
|
// Set setAllowedFileTypes before setNameFieldStringValue as it might
|
|
|
|
// override the extension set using setNameFieldStringValue
|
2017-05-28 19:20:22 +00:00
|
|
|
SetAllowedFileTypes(dialog, settings.filters);
|
2017-06-03 15:48:18 +00:00
|
|
|
}
|
2017-05-28 19:20:22 +00:00
|
|
|
|
2017-09-07 23:43:37 +00:00
|
|
|
// Make sure the extension is always visible. Without this, the extension in
|
|
|
|
// the default filename will not be used in the saved file.
|
|
|
|
[dialog setExtensionHidden:NO];
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
if (default_dir)
|
|
|
|
[dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
|
|
|
|
if (default_filename)
|
|
|
|
[dialog setNameFieldStringValue:default_filename];
|
|
|
|
}
|
|
|
|
|
2013-09-23 11:22:36 +00:00
|
|
|
void SetupDialogForProperties(NSOpenPanel* dialog, int properties) {
|
|
|
|
[dialog setCanChooseFiles:(properties & FILE_DIALOG_OPEN_FILE)];
|
|
|
|
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
|
|
|
[dialog setCanChooseDirectories:YES];
|
|
|
|
if (properties & FILE_DIALOG_CREATE_DIRECTORY)
|
|
|
|
[dialog setCanCreateDirectories:YES];
|
|
|
|
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
|
|
|
[dialog setAllowsMultipleSelection:YES];
|
2016-07-11 04:30:18 +00:00
|
|
|
if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
|
|
|
|
[dialog setShowsHiddenFiles:YES];
|
2017-02-12 04:23:27 +00:00
|
|
|
if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES)
|
2017-02-13 16:23:49 +00:00
|
|
|
[dialog setResolvesAliases:NO];
|
2017-07-18 01:40:57 +00:00
|
|
|
if (properties & FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
|
|
|
|
[dialog setTreatsFilePackagesAsDirectories:YES];
|
2013-09-23 11:22:36 +00:00
|
|
|
}
|
|
|
|
|
2013-09-23 11:42:07 +00:00
|
|
|
// Run modal dialog with parent window and return user's choice.
|
|
|
|
int RunModalDialog(NSSavePanel* dialog, atom::NativeWindow* parent_window) {
|
|
|
|
__block int chosen = NSFileHandlingPanelCancelButton;
|
2017-09-13 07:36:28 +00:00
|
|
|
if (!parent_window || !parent_window->GetNativeWindow() ||
|
|
|
|
parent_window->IsOffScreenDummy()) {
|
2013-09-23 11:42:07 +00:00
|
|
|
chosen = [dialog runModal];
|
|
|
|
} else {
|
|
|
|
NSWindow* window = parent_window->GetNativeWindow();
|
|
|
|
|
|
|
|
[dialog beginSheetModalForWindow:window
|
|
|
|
completionHandler:^(NSInteger c) {
|
|
|
|
chosen = c;
|
|
|
|
[NSApp stopModal];
|
|
|
|
}];
|
|
|
|
[NSApp runModalForWindow:window];
|
|
|
|
}
|
|
|
|
|
|
|
|
return chosen;
|
|
|
|
}
|
|
|
|
|
2013-09-23 11:22:36 +00:00
|
|
|
void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
|
|
|
|
NSArray* urls = [dialog URLs];
|
|
|
|
for (NSURL* url in urls)
|
|
|
|
if ([url isFileURL])
|
|
|
|
paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path])));
|
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
} // namespace
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
bool ShowOpenDialog(const DialogSettings& settings,
|
2013-05-20 13:46:43 +00:00
|
|
|
std::vector<base::FilePath>* paths) {
|
|
|
|
DCHECK(paths);
|
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
SetupDialog(dialog, settings);
|
|
|
|
SetupDialogForProperties(dialog, settings.properties);
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
int chosen = RunModalDialog(dialog, settings.parent_window);
|
2013-09-23 08:27:22 +00:00
|
|
|
if (chosen == NSFileHandlingPanelCancelButton)
|
2013-05-20 13:46:43 +00:00
|
|
|
return false;
|
|
|
|
|
2013-09-23 11:22:36 +00:00
|
|
|
ReadDialogPaths(dialog, paths);
|
2013-05-20 13:46:43 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
void ShowOpenDialog(const DialogSettings& settings,
|
2013-09-23 11:22:36 +00:00
|
|
|
const OpenDialogCallback& c) {
|
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
SetupDialog(dialog, settings);
|
|
|
|
SetupDialogForProperties(dialog, settings.properties);
|
2013-09-23 11:22:36 +00:00
|
|
|
|
|
|
|
// Duplicate the callback object here since c is a reference and gcd would
|
|
|
|
// only store the pointer, by duplication we can force gcd to store a copy.
|
|
|
|
__block OpenDialogCallback callback = c;
|
|
|
|
|
2017-09-13 07:36:28 +00:00
|
|
|
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
|
|
|
settings.parent_window->IsOffScreenDummy()) {
|
|
|
|
int chosen = [dialog runModal];
|
2013-09-23 11:22:36 +00:00
|
|
|
if (chosen == NSFileHandlingPanelCancelButton) {
|
|
|
|
callback.Run(false, std::vector<base::FilePath>());
|
|
|
|
} else {
|
|
|
|
std::vector<base::FilePath> paths;
|
|
|
|
ReadDialogPaths(dialog, &paths);
|
|
|
|
callback.Run(true, paths);
|
|
|
|
}
|
2017-09-13 07:36:28 +00:00
|
|
|
} else {
|
|
|
|
NSWindow* window = settings.parent_window->GetNativeWindow();
|
|
|
|
[dialog beginSheetModalForWindow:window
|
|
|
|
completionHandler:^(NSInteger chosen) {
|
|
|
|
if (chosen == NSFileHandlingPanelCancelButton) {
|
|
|
|
callback.Run(false, std::vector<base::FilePath>());
|
|
|
|
} else {
|
|
|
|
std::vector<base::FilePath> paths;
|
|
|
|
ReadDialogPaths(dialog, &paths);
|
|
|
|
callback.Run(true, paths);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
2013-09-23 11:22:36 +00:00
|
|
|
}
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
bool ShowSaveDialog(const DialogSettings& settings,
|
2013-05-20 13:46:43 +00:00
|
|
|
base::FilePath* path) {
|
|
|
|
DCHECK(path);
|
|
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
SetupDialog(dialog, settings);
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
int chosen = RunModalDialog(dialog, settings.parent_window);
|
2013-09-23 11:36:52 +00:00
|
|
|
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
|
|
|
|
return false;
|
2013-05-20 13:46:43 +00:00
|
|
|
|
2013-09-23 11:36:52 +00:00
|
|
|
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
|
|
|
|
return true;
|
2013-05-20 13:46:43 +00:00
|
|
|
}
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
void ShowSaveDialog(const DialogSettings& settings,
|
2013-09-23 12:08:32 +00:00
|
|
|
const SaveDialogCallback& c) {
|
|
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
|
|
|
2017-02-08 01:32:58 +00:00
|
|
|
SetupDialog(dialog, settings);
|
2016-08-16 23:08:01 +00:00
|
|
|
[dialog setCanSelectHiddenExtension:YES];
|
2013-09-23 12:08:32 +00:00
|
|
|
|
|
|
|
__block SaveDialogCallback callback = c;
|
|
|
|
|
2017-09-13 07:36:28 +00:00
|
|
|
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
|
|
|
settings.parent_window->IsOffScreenDummy()) {
|
|
|
|
int chosen = [dialog runModal];
|
2013-09-23 12:08:32 +00:00
|
|
|
if (chosen == NSFileHandlingPanelCancelButton) {
|
|
|
|
callback.Run(false, base::FilePath());
|
|
|
|
} else {
|
|
|
|
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
|
|
|
callback.Run(true, base::FilePath(path));
|
|
|
|
}
|
2017-09-13 07:36:28 +00:00
|
|
|
} else {
|
|
|
|
NSWindow* window = settings.parent_window->GetNativeWindow();
|
|
|
|
[dialog beginSheetModalForWindow:window
|
|
|
|
completionHandler:^(NSInteger chosen) {
|
|
|
|
if (chosen == NSFileHandlingPanelCancelButton) {
|
|
|
|
callback.Run(false, base::FilePath());
|
|
|
|
} else {
|
|
|
|
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
|
|
|
callback.Run(true, base::FilePath(path));
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
2013-09-23 12:08:32 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 13:46:43 +00:00
|
|
|
} // namespace file_dialog
|