Merge pull request #552 from atom/dialog-filters
Add support for extension filters for file dialogs
This commit is contained in:
commit
196be5291d
8 changed files with 223 additions and 171 deletions
|
@ -3,6 +3,7 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/api/atom_api_window.h"
|
||||
|
@ -16,6 +17,26 @@
|
|||
|
||||
#include "atom/common/node_includes.h"
|
||||
|
||||
namespace mate {
|
||||
|
||||
template<>
|
||||
struct Converter<file_dialog::Filter> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Handle<v8::Value> val,
|
||||
file_dialog::Filter* out) {
|
||||
mate::Dictionary dict(isolate);
|
||||
if (!ConvertFromV8(isolate, val, &dict))
|
||||
return false;
|
||||
if (!dict.Get("name", &(out->first)))
|
||||
return false;
|
||||
if (!dict.Get("extensions", &(out->second)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mate
|
||||
|
||||
namespace {
|
||||
|
||||
void ShowMessageBox(int type,
|
||||
|
@ -41,6 +62,7 @@ void ShowMessageBox(int type,
|
|||
|
||||
void ShowOpenDialog(const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const file_dialog::Filters& filters,
|
||||
int properties,
|
||||
atom::NativeWindow* window,
|
||||
mate::Arguments* args) {
|
||||
|
@ -49,18 +71,19 @@ void ShowOpenDialog(const std::string& title,
|
|||
if (mate::Converter<file_dialog::OpenDialogCallback>::FromV8(args->isolate(),
|
||||
peek,
|
||||
&callback)) {
|
||||
file_dialog::ShowOpenDialog(window, title, default_path, properties,
|
||||
callback);
|
||||
file_dialog::ShowOpenDialog(window, title, default_path, filters,
|
||||
properties, callback);
|
||||
} else {
|
||||
std::vector<base::FilePath> paths;
|
||||
if (file_dialog::ShowOpenDialog(window, title, default_path, properties,
|
||||
&paths))
|
||||
if (file_dialog::ShowOpenDialog(window, title, default_path, filters,
|
||||
properties, &paths))
|
||||
args->Return(paths);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowSaveDialog(const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const file_dialog::Filters& filters,
|
||||
atom::NativeWindow* window,
|
||||
mate::Arguments* args) {
|
||||
v8::Handle<v8::Value> peek = args->PeekNext();
|
||||
|
@ -68,10 +91,11 @@ void ShowSaveDialog(const std::string& title,
|
|||
if (mate::Converter<file_dialog::SaveDialogCallback>::FromV8(args->isolate(),
|
||||
peek,
|
||||
&callback)) {
|
||||
file_dialog::ShowSaveDialog(window, title, default_path, callback);
|
||||
file_dialog::ShowSaveDialog(window, title, default_path, filters, callback);
|
||||
} else {
|
||||
base::FilePath path;
|
||||
if (file_dialog::ShowSaveDialog(window, title, default_path, &path))
|
||||
if (file_dialog::ShowSaveDialog(window, title, default_path, filters,
|
||||
&path))
|
||||
args->Return(path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ v8Util = process.atomBinding 'v8_util'
|
|||
BrowserWindow = require 'browser-window'
|
||||
|
||||
fileDialogProperties =
|
||||
openFile: 1, openDirectory: 2, multiSelections: 4, createDirectory: 8
|
||||
openFile: 1 << 0
|
||||
openDirectory: 1 << 1
|
||||
multiSelections: 1 << 2
|
||||
createDirectory: 1 << 3
|
||||
|
||||
messageBoxTypes = ['none', 'info', 'warning']
|
||||
|
||||
|
@ -25,6 +28,7 @@ module.exports =
|
|||
|
||||
options.title ?= ''
|
||||
options.defaultPath ?= ''
|
||||
options.filters ?= []
|
||||
|
||||
wrappedCallback =
|
||||
if typeof callback is 'function'
|
||||
|
@ -34,6 +38,7 @@ module.exports =
|
|||
|
||||
binding.showOpenDialog String(options.title),
|
||||
String(options.defaultPath),
|
||||
options.filters
|
||||
properties,
|
||||
window,
|
||||
wrappedCallback
|
||||
|
@ -48,6 +53,7 @@ module.exports =
|
|||
options ?= title: 'Save'
|
||||
options.title ?= ''
|
||||
options.defaultPath ?= ''
|
||||
options.filters ?= []
|
||||
|
||||
wrappedCallback =
|
||||
if typeof callback is 'function'
|
||||
|
@ -57,6 +63,7 @@ module.exports =
|
|||
|
||||
binding.showSaveDialog String(options.title),
|
||||
String(options.defaultPath),
|
||||
options.filters
|
||||
window,
|
||||
wrappedCallback
|
||||
|
||||
|
|
|
@ -528,8 +528,9 @@ void NativeWindow::DevToolsSaveToFile(const std::string& url,
|
|||
if (it != saved_files_.end() && !save_as) {
|
||||
path = it->second;
|
||||
} else {
|
||||
file_dialog::Filters filters;
|
||||
base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url));
|
||||
if (!file_dialog::ShowSaveDialog(this, url, default_path, &path)) {
|
||||
if (!file_dialog::ShowSaveDialog(this, url, default_path, filters, &path)) {
|
||||
base::StringValue url_value(url);
|
||||
CallDevToolsFunction("InspectorFrontendAPI.canceledSaveURL", &url_value);
|
||||
return;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#define ATOM_BROWSER_UI_FILE_DIALOG_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
|
@ -17,11 +18,15 @@ class NativeWindow;
|
|||
|
||||
namespace file_dialog {
|
||||
|
||||
// <description, extensions>
|
||||
typedef std::pair<std::string, std::vector<std::string> > Filter;
|
||||
typedef std::vector<Filter> Filters;
|
||||
|
||||
enum FileDialogProperty {
|
||||
FILE_DIALOG_OPEN_FILE = 1,
|
||||
FILE_DIALOG_OPEN_DIRECTORY = 2,
|
||||
FILE_DIALOG_MULTI_SELECTIONS = 4,
|
||||
FILE_DIALOG_CREATE_DIRECTORY = 8,
|
||||
FILE_DIALOG_OPEN_FILE = 1 << 0,
|
||||
FILE_DIALOG_OPEN_DIRECTORY = 1 << 1,
|
||||
FILE_DIALOG_MULTI_SELECTIONS = 1 << 2,
|
||||
FILE_DIALOG_CREATE_DIRECTORY = 1 << 3,
|
||||
};
|
||||
|
||||
typedef base::Callback<void(
|
||||
|
@ -33,23 +38,27 @@ typedef base::Callback<void(
|
|||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths);
|
||||
|
||||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
const OpenDialogCallback& callback);
|
||||
|
||||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
base::FilePath* path);
|
||||
|
||||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
const SaveDialogCallback& callback);
|
||||
|
||||
} // namespace file_dialog
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "atom/browser/native_window.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/aura/window_tree_host.h"
|
||||
|
@ -45,12 +46,24 @@ void SetGtkTransientForAura(GtkWidget* dialog, aura::Window* parent) {
|
|||
g_object_set_data(G_OBJECT(dialog), kAuraTransientParent, parent);
|
||||
}
|
||||
|
||||
// Makes sure that .jpg also shows .JPG.
|
||||
gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
|
||||
std::string* file_extension) {
|
||||
return EndsWith(file_info->filename, *file_extension, false);
|
||||
}
|
||||
|
||||
// Deletes |data| when gtk_file_filter_add_custom() is done with it.
|
||||
void OnFileFilterDataDestroyed(std::string* file_extension) {
|
||||
delete file_extension;
|
||||
}
|
||||
|
||||
class FileChooserDialog {
|
||||
public:
|
||||
FileChooserDialog(GtkFileChooserAction action,
|
||||
atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path)
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters)
|
||||
: dialog_scope_(new atom::NativeWindow::DialogScope(parent_window)) {
|
||||
const char* confirm_text = GTK_STOCK_OK;
|
||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
|
@ -86,6 +99,9 @@ class FileChooserDialog {
|
|||
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog_),
|
||||
default_path.value().c_str());
|
||||
}
|
||||
|
||||
if (!filters.empty())
|
||||
AddFilters(filters);
|
||||
}
|
||||
|
||||
virtual ~FileChooserDialog() {
|
||||
|
@ -135,6 +151,8 @@ class FileChooserDialog {
|
|||
GtkWidget* dialog() const { return dialog_; }
|
||||
|
||||
private:
|
||||
void AddFilters(const Filters& filters);
|
||||
|
||||
GtkWidget* dialog_;
|
||||
|
||||
SaveDialogCallback save_callback_;
|
||||
|
@ -162,17 +180,40 @@ void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
|
|||
delete this;
|
||||
}
|
||||
|
||||
void FileChooserDialog::AddFilters(const Filters& filters) {
|
||||
for (size_t i = 0; i < filters.size(); ++i) {
|
||||
const Filter& filter = filters[i];
|
||||
GtkFileFilter* gtk_filter = gtk_file_filter_new();
|
||||
|
||||
for (size_t j = 0; j < filter.second.size(); ++j) {
|
||||
scoped_ptr<std::string> file_extension(
|
||||
new std::string("." + filter.second[j]));
|
||||
gtk_file_filter_add_custom(
|
||||
gtk_filter,
|
||||
GTK_FILE_FILTER_FILENAME,
|
||||
reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
|
||||
file_extension.release(),
|
||||
reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
|
||||
}
|
||||
|
||||
gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_), gtk_filter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
FileChooserDialog open_dialog(action, parent_window, title, default_path);
|
||||
FileChooserDialog open_dialog(action, parent_window, title, default_path,
|
||||
filters);
|
||||
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(open_dialog.dialog()),
|
||||
TRUE);
|
||||
|
@ -190,13 +231,14 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
const OpenDialogCallback& callback) {
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
FileChooserDialog* open_dialog = new FileChooserDialog(
|
||||
action, parent_window, title, default_path);
|
||||
action, parent_window, title, default_path, filters);
|
||||
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
gtk_file_chooser_set_select_multiple(
|
||||
GTK_FILE_CHOOSER(open_dialog->dialog()), TRUE);
|
||||
|
@ -207,9 +249,10 @@ void ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
base::FilePath* path) {
|
||||
FileChooserDialog save_dialog(
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path);
|
||||
FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, parent_window,
|
||||
title, default_path, filters);
|
||||
gtk_widget_show_all(save_dialog.dialog());
|
||||
int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
|
||||
if (response == GTK_RESPONSE_ACCEPT) {
|
||||
|
@ -223,9 +266,11 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
|||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
const SaveDialogCallback& callback) {
|
||||
FileChooserDialog* save_dialog = new FileChooserDialog(
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path);
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path,
|
||||
filters);
|
||||
save_dialog->RunSaveAsynchronous(callback);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,15 +9,45 @@
|
|||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "base/file_util.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/mac/scoped_cftyperef.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
namespace {
|
||||
|
||||
CFStringRef CreateUTIFromExtension(const std::string& ext) {
|
||||
base::ScopedCFTypeRef<CFStringRef> ext_cf(base::SysUTF8ToCFStringRef(ext));
|
||||
return UTTypeCreatePreferredIdentifierForTag(
|
||||
kUTTagClassFilenameExtension, ext_cf.get(), NULL);
|
||||
}
|
||||
|
||||
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) {
|
||||
base::ScopedCFTypeRef<CFStringRef> uti(
|
||||
CreateUTIFromExtension(filter.second[j]));
|
||||
[file_type_set addObject:base::mac::CFToNSCast(uti.get())];
|
||||
|
||||
// Always allow the extension itself, in case the UTI doesn't map
|
||||
// back to the original extension correctly. This occurs with dynamic
|
||||
// UTIs on 10.7 and 10.8.
|
||||
// See http://crbug.com/148840, http://openradar.me/12316273
|
||||
base::ScopedCFTypeRef<CFStringRef> ext_cf(
|
||||
base::SysUTF8ToCFStringRef(filter.second[j]));
|
||||
[file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())];
|
||||
}
|
||||
}
|
||||
[dialog setAllowedFileTypes:[file_type_set allObjects]];
|
||||
}
|
||||
|
||||
void SetupDialog(NSSavePanel* dialog,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path) {
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters) {
|
||||
if (!title.empty())
|
||||
[dialog setTitle:base::SysUTF8ToNSString(title)];
|
||||
|
||||
|
@ -39,7 +69,10 @@ void SetupDialog(NSSavePanel* dialog,
|
|||
[dialog setNameFieldStringValue:default_filename];
|
||||
|
||||
[dialog setCanSelectHiddenExtension:YES];
|
||||
[dialog setAllowsOtherFileTypes:YES];
|
||||
if (filters.empty())
|
||||
[dialog setAllowsOtherFileTypes:YES];
|
||||
else
|
||||
SetAllowedFileTypes(dialog, filters);
|
||||
}
|
||||
|
||||
void SetupDialogForProperties(NSOpenPanel* dialog, int properties) {
|
||||
|
@ -83,12 +116,13 @@ void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
|
|||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
DCHECK(paths);
|
||||
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
SetupDialog(dialog, title, default_path, filters);
|
||||
SetupDialogForProperties(dialog, properties);
|
||||
|
||||
int chosen = RunModalDialog(dialog, parent_window);
|
||||
|
@ -102,11 +136,12 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
const OpenDialogCallback& c) {
|
||||
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
SetupDialog(dialog, title, default_path, filters);
|
||||
SetupDialogForProperties(dialog, properties);
|
||||
|
||||
// Duplicate the callback object here since c is a reference and gcd would
|
||||
|
@ -129,11 +164,12 @@ void ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
base::FilePath* path) {
|
||||
DCHECK(path);
|
||||
NSSavePanel* dialog = [NSSavePanel savePanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
SetupDialog(dialog, title, default_path, filters);
|
||||
|
||||
int chosen = RunModalDialog(dialog, parent_window);
|
||||
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
|
||||
|
@ -146,10 +182,11 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
|||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
const SaveDialogCallback& c) {
|
||||
NSSavePanel* dialog = [NSSavePanel savePanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
SetupDialog(dialog, title, default_path, filters);
|
||||
|
||||
__block SaveDialogCallback callback = c;
|
||||
|
||||
|
|
|
@ -30,103 +30,30 @@ bool IsDirectory(const base::FilePath& path) {
|
|||
file_info.is_directory : path.EndsWithSeparator();
|
||||
}
|
||||
|
||||
// Get the file type description from the registry. This will be "Text Document"
|
||||
// for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't
|
||||
// have an entry for the file type, we return false, true if the description was
|
||||
// found. 'file_ext' must be in form ".txt".
|
||||
static bool GetRegistryDescriptionFromExtension(const std::wstring& file_ext,
|
||||
std::wstring* reg_description) {
|
||||
DCHECK(reg_description);
|
||||
base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, file_ext.c_str(), KEY_READ);
|
||||
std::wstring reg_app;
|
||||
if (reg_ext.ReadValue(NULL, ®_app) == ERROR_SUCCESS && !reg_app.empty()) {
|
||||
base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ);
|
||||
if (reg_link.ReadValue(NULL, reg_description) == ERROR_SUCCESS)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up a filter for a Save/Open dialog, which will consist of |file_ext| file
|
||||
// extensions (internally separated by semicolons), |ext_desc| as the text
|
||||
// descriptions of the |file_ext| types (optional), and (optionally) the default
|
||||
// 'All Files' view. The purpose of the filter is to show only files of a
|
||||
// particular type in a Windows Save/Open dialog box. The resulting filter is
|
||||
// returned. The filters created here are:
|
||||
// 1. only files that have 'file_ext' as their extension
|
||||
// 2. all files (only added if 'include_all_files' is true)
|
||||
// Example:
|
||||
// file_ext: { "*.txt", "*.htm;*.html" }
|
||||
// ext_desc: { "Text Document" }
|
||||
// returned: "Text Document\0*.txt\0HTML Document\0*.htm;*.html\0"
|
||||
// "All Files\0*.*\0\0" (in one big string)
|
||||
// If a description is not provided for a file extension, it will be retrieved
|
||||
// from the registry. If the file extension does not exist in the registry, it
|
||||
// will be omitted from the filter, as it is likely a bogus extension.
|
||||
void FormatFilterForExtensions(
|
||||
std::vector<std::wstring>* file_ext,
|
||||
std::vector<std::wstring>* ext_desc,
|
||||
bool include_all_files,
|
||||
std::vector<COMDLG_FILTERSPEC>* file_types) {
|
||||
DCHECK(file_ext->size() >= ext_desc->size());
|
||||
|
||||
if (file_ext->empty())
|
||||
include_all_files = true;
|
||||
|
||||
for (size_t i = 0; i < file_ext->size(); ++i) {
|
||||
std::wstring ext = (*file_ext)[i];
|
||||
std::wstring desc;
|
||||
if (i < ext_desc->size())
|
||||
desc = (*ext_desc)[i];
|
||||
|
||||
if (ext.empty()) {
|
||||
// Force something reasonable to appear in the dialog box if there is no
|
||||
// extension provided.
|
||||
include_all_files = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc.empty()) {
|
||||
DCHECK(ext.find(L'.') != std::wstring::npos);
|
||||
std::wstring first_extension = ext.substr(ext.find(L'.'));
|
||||
size_t first_separator_index = first_extension.find(L';');
|
||||
if (first_separator_index != std::wstring::npos)
|
||||
first_extension = first_extension.substr(0, first_separator_index);
|
||||
|
||||
// Find the extension name without the preceeding '.' character.
|
||||
std::wstring ext_name = first_extension;
|
||||
size_t ext_index = ext_name.find_first_not_of(L'.');
|
||||
if (ext_index != std::wstring::npos)
|
||||
ext_name = ext_name.substr(ext_index);
|
||||
|
||||
if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) {
|
||||
// The extension doesn't exist in the registry. Create a description
|
||||
// based on the unknown extension type (i.e. if the extension is .qqq,
|
||||
// the we create a description "QQQ File (.qqq)").
|
||||
include_all_files = true;
|
||||
// TODO(zcbenz): should be localized.
|
||||
desc = base::i18n::ToUpper(base::WideToUTF16(ext_name)) + L" File";
|
||||
}
|
||||
desc += L" (*." + ext_name + L")";
|
||||
|
||||
// Store the description.
|
||||
ext_desc->push_back(desc);
|
||||
}
|
||||
|
||||
COMDLG_FILTERSPEC spec = { (*ext_desc)[i].c_str(), (*file_ext)[i].c_str() };
|
||||
file_types->push_back(spec);
|
||||
void ConvertFilters(const Filters& filters,
|
||||
std::vector<std::wstring>* buffer,
|
||||
std::vector<COMDLG_FILTERSPEC>* filterspec) {
|
||||
if (filters.empty()) {
|
||||
COMDLG_FILTERSPEC spec = { L"All Files (*.*)", L"*.*" };
|
||||
filterspec->push_back(spec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (include_all_files) {
|
||||
// TODO(zcbenz): Should be localized.
|
||||
ext_desc->push_back(L"All Files (*.*)");
|
||||
file_ext->push_back(L"*.*");
|
||||
buffer->reserve(filters.size() * 2);
|
||||
for (size_t i = 0; i < filters.size(); ++i) {
|
||||
const Filter& filter = filters[i];
|
||||
|
||||
COMDLG_FILTERSPEC spec = {
|
||||
(*ext_desc)[ext_desc->size() - 1].c_str(),
|
||||
(*file_ext)[file_ext->size() - 1].c_str(),
|
||||
};
|
||||
file_types->push_back(spec);
|
||||
COMDLG_FILTERSPEC spec;
|
||||
buffer->push_back(base::UTF8ToWide(filter.first));
|
||||
spec.pszName = buffer->back().c_str();
|
||||
|
||||
std::vector<std::string> extensions(filter.second);
|
||||
for (size_t j = 0; j < extensions.size(); ++j)
|
||||
extensions[j].insert(0, "*.");
|
||||
buffer->push_back(base::UTF8ToWide(JoinString(extensions, ";")));
|
||||
spec.pszSpec = buffer->back().c_str();
|
||||
|
||||
filterspec->push_back(spec);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,26 +62,18 @@ void FormatFilterForExtensions(
|
|||
template <typename T>
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog(const base::FilePath& default_path,
|
||||
const std::string title,
|
||||
int options,
|
||||
const std::vector<std::wstring>& file_ext,
|
||||
const std::vector<std::wstring>& desc_ext)
|
||||
: file_ext_(file_ext),
|
||||
desc_ext_(desc_ext) {
|
||||
std::vector<COMDLG_FILTERSPEC> filters;
|
||||
FormatFilterForExtensions(&file_ext_, &desc_ext_, true, &filters);
|
||||
|
||||
FileDialog(const base::FilePath& default_path, const std::string& title,
|
||||
const Filters& filters, int options) {
|
||||
std::wstring file_part;
|
||||
if (!IsDirectory(default_path))
|
||||
file_part = default_path.BaseName().value();
|
||||
|
||||
dialog_.reset(new T(
|
||||
file_part.c_str(),
|
||||
options,
|
||||
NULL,
|
||||
filters.data(),
|
||||
filters.size()));
|
||||
std::vector<std::wstring> buffer;
|
||||
std::vector<COMDLG_FILTERSPEC> filterspec;
|
||||
ConvertFilters(filters, &buffer, &filterspec);
|
||||
|
||||
dialog_.reset(new T(file_part.c_str(), options, NULL,
|
||||
filterspec.data(), filterspec.size()));
|
||||
|
||||
if (!title.empty())
|
||||
GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str());
|
||||
|
@ -174,8 +93,6 @@ class FileDialog {
|
|||
|
||||
IFileDialog* GetPtr() const { return dialog_->GetPtr(); }
|
||||
|
||||
const std::vector<std::wstring> file_ext() const { return file_ext_; }
|
||||
|
||||
private:
|
||||
// Set up the initial directory for the dialog.
|
||||
void SetDefaultFolder(const base::FilePath file_path) {
|
||||
|
@ -193,10 +110,6 @@ class FileDialog {
|
|||
|
||||
scoped_ptr<T> dialog_;
|
||||
|
||||
std::vector<std::wstring> file_ext_;
|
||||
std::vector<std::wstring> desc_ext_;
|
||||
std::vector<COMDLG_FILTERSPEC> filters_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FileDialog);
|
||||
};
|
||||
|
||||
|
@ -205,6 +118,7 @@ class FileDialog {
|
|||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST;
|
||||
|
@ -214,11 +128,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
options |= FOS_ALLOWMULTISELECT;
|
||||
|
||||
FileDialog<CShellFileOpenDialog> open_dialog(
|
||||
default_path,
|
||||
title,
|
||||
options,
|
||||
std::vector<std::wstring>(),
|
||||
std::vector<std::wstring>());
|
||||
default_path, title, filters, options);
|
||||
if (!open_dialog.Show(parent_window))
|
||||
return false;
|
||||
|
||||
|
@ -255,12 +165,14 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
int properties,
|
||||
const OpenDialogCallback& callback) {
|
||||
std::vector<base::FilePath> paths;
|
||||
bool result = ShowOpenDialog(parent_window,
|
||||
title,
|
||||
default_path,
|
||||
filters,
|
||||
properties,
|
||||
&paths);
|
||||
callback.Run(result, paths);
|
||||
|
@ -269,52 +181,51 @@ void ShowOpenDialog(atom::NativeWindow* parent_window,
|
|||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
base::FilePath* path) {
|
||||
// TODO(zcbenz): Accept custom filters from caller.
|
||||
std::vector<std::wstring> file_ext;
|
||||
std::wstring extension = default_path.Extension();
|
||||
if (!extension.empty())
|
||||
file_ext.push_back(extension.insert(0, L"*"));
|
||||
|
||||
FileDialog<CShellFileSaveDialog> save_dialog(
|
||||
default_path,
|
||||
title,
|
||||
FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT,
|
||||
file_ext,
|
||||
std::vector<std::wstring>());
|
||||
default_path, title, filters,
|
||||
FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT);
|
||||
if (!save_dialog.Show(parent_window))
|
||||
return false;
|
||||
|
||||
wchar_t file_name[MAX_PATH];
|
||||
HRESULT hr = save_dialog.GetDialog()->GetFilePath(file_name, MAX_PATH);
|
||||
wchar_t buffer[MAX_PATH];
|
||||
HRESULT hr = save_dialog.GetDialog()->GetFilePath(buffer, MAX_PATH);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
std::string file_name = base::WideToUTF8(std::wstring(buffer));
|
||||
|
||||
// Append extension according to selected filter.
|
||||
UINT filter_index = 1;
|
||||
save_dialog.GetPtr()->GetFileTypeIndex(&filter_index);
|
||||
std::wstring selected_filter = save_dialog.file_ext()[filter_index - 1];
|
||||
if (selected_filter != L"*.*") {
|
||||
std::wstring result = file_name;
|
||||
if (!EndsWith(result, selected_filter.substr(1), false)) {
|
||||
if (result[result.length() - 1] != L'.')
|
||||
result.push_back(L'.');
|
||||
result.append(selected_filter.substr(2));
|
||||
*path = base::FilePath(result);
|
||||
return true;
|
||||
if (!filters.empty()) {
|
||||
UINT filter_index = 1;
|
||||
save_dialog.GetPtr()->GetFileTypeIndex(&filter_index);
|
||||
const Filter& filter = filters[filter_index - 1];
|
||||
|
||||
bool matched = false;
|
||||
for (size_t i = 0; i < filter.second.size(); ++i) {
|
||||
if (EndsWith(file_name, filter.second[i], false)) {
|
||||
matched = true;
|
||||
break;;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched && !filter.second.empty())
|
||||
file_name += ("." + filter.second[0]);
|
||||
}
|
||||
|
||||
*path = base::FilePath(file_name);
|
||||
*path = base::FilePath(base::UTF8ToUTF16(file_name));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const Filters& filters,
|
||||
const SaveDialogCallback& callback) {
|
||||
base::FilePath path;
|
||||
bool result = ShowSaveDialog(parent_window, title, default_path, &path);
|
||||
bool result = ShowSaveDialog(parent_window, title, default_path, filters,
|
||||
&path);
|
||||
callback.Run(result, path);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', '
|
|||
* `options` Object
|
||||
* `title` String
|
||||
* `defaultPath` String
|
||||
* `filters` Array
|
||||
* `properties` Array - Contains which features the dialog should use, can
|
||||
contain `openFile`, `openDirectory`, `multiSelections` and
|
||||
`createDirectory`
|
||||
|
@ -27,6 +28,19 @@ console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', '
|
|||
On success, returns an array of file paths chosen by the user, otherwise
|
||||
returns `undefined`.
|
||||
|
||||
The `filters` specifies an array of file types that can be displayed or
|
||||
selected, an example is:
|
||||
|
||||
```javascript
|
||||
{
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
|
||||
{ name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] },
|
||||
{ name: 'Custom File Type', extensions: ['as'] },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
If a `callback` is passed, the API call would be asynchronous and the result
|
||||
would be passed via `callback(filenames)`
|
||||
|
||||
|
@ -41,11 +55,15 @@ be showed.
|
|||
* `options` Object
|
||||
* `title` String
|
||||
* `defaultPath` String
|
||||
* `filters` Array
|
||||
* `callback` Function
|
||||
|
||||
On success, returns the path of file chosen by the user, otherwise returns
|
||||
`undefined`.
|
||||
|
||||
The `filters` specifies an array of file types that can be displayed, see
|
||||
`dialog.showOpenDialog` for an example.
|
||||
|
||||
If a `callback` is passed, the API call would be asynchronous and the result
|
||||
would be passed via `callback(filename)`
|
||||
|
||||
|
|
Loading…
Reference in a new issue