Merge pull request #213 from atom/linux-open-dialog

Implement file dialog API on Linux
This commit is contained in:
Cheng Zhao 2014-03-13 08:34:19 +00:00
commit af301e682e
3 changed files with 194 additions and 24 deletions

View file

@ -5,15 +5,153 @@
#include "browser/ui/file_dialog.h" #include "browser/ui/file_dialog.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/file_util.h"
#include "browser/native_window.h"
#include "browser/ui/gtk/gtk_util.h"
#include "ui/base/gtk/gtk_signal.h"
namespace file_dialog { namespace file_dialog {
namespace {
class FileChooserDialog {
public:
FileChooserDialog(GtkFileChooserAction action,
atom::NativeWindow* parent_window,
const std::string& title,
const base::FilePath& default_path) {
const char* confirm_text = GTK_STOCK_OK;
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
confirm_text = GTK_STOCK_SAVE;
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
confirm_text = GTK_STOCK_OPEN;
GtkWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL;
dialog_ = gtk_file_chooser_dialog_new(
title.c_str(),
window,
action,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
confirm_text, GTK_RESPONSE_ACCEPT,
NULL);
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog_),
TRUE);
if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE);
// Set window-to-parent modality by adding the dialog to the same window
// group as the parent.
gtk_window_group_add_window(gtk_window_get_group(window),
GTK_WINDOW(dialog_));
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
if (!default_path.empty()) {
if (base::DirectoryExists(default_path))
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_),
default_path.value().c_str());
else
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog_),
default_path.value().c_str());
}
}
virtual ~FileChooserDialog() {
gtk_widget_destroy(dialog_);
}
void RunAsynchronous() {
g_signal_connect(dialog_, "delete-event",
G_CALLBACK(gtk_widget_hide_on_delete), NULL);
g_signal_connect(dialog_, "response",
G_CALLBACK(OnFileDialogResponseThunk), this);
gtk_widget_show_all(dialog_);
}
void RunSaveAsynchronous(const SaveDialogCallback& callback) {
save_callback_ = callback;
RunAsynchronous();
}
void RunOpenAsynchronous(const OpenDialogCallback& callback) {
open_callback_ = callback;
RunAsynchronous();
}
base::FilePath GetFileName() const {
gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_));
base::FilePath path(filename);
g_free(filename);
return path;
}
std::vector<base::FilePath> GetFileNames() const {
std::vector<base::FilePath> paths;
GSList* filenames = gtk_file_chooser_get_filenames(
GTK_FILE_CHOOSER(dialog_));
for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
base::FilePath path(static_cast<char*>(iter->data));
g_free(iter->data);
paths.push_back(path);
}
g_slist_free(filenames);
return paths;
}
CHROMEGTK_CALLBACK_1(FileChooserDialog, void, OnFileDialogResponse, int);
GtkWidget* dialog() const { return dialog_; }
private:
GtkWidget* dialog_;
SaveDialogCallback save_callback_;
OpenDialogCallback open_callback_;
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
};
void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
gtk_widget_hide_all(dialog_);
if (!save_callback_.is_null()) {
if (response == GTK_RESPONSE_ACCEPT)
save_callback_.Run(true, GetFileName());
else
save_callback_.Run(false, base::FilePath());
} else if (!open_callback_.is_null()) {
if (response == GTK_RESPONSE_ACCEPT)
open_callback_.Run(true, GetFileNames());
else
open_callback_.Run(false, std::vector<base::FilePath>());
}
delete this;
}
} // namespace
bool ShowOpenDialog(atom::NativeWindow* parent_window, bool ShowOpenDialog(atom::NativeWindow* parent_window,
const std::string& title, const std::string& title,
const base::FilePath& default_path, const base::FilePath& default_path,
int properties, int properties,
std::vector<base::FilePath>* paths) { std::vector<base::FilePath>* paths) {
return false; 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);
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(open_dialog.dialog()),
TRUE);
gtk_widget_show_all(open_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog()));
if (response == GTK_RESPONSE_ACCEPT) {
*paths = open_dialog.GetFileNames();
return true;
} else {
return false;
}
} }
void ShowOpenDialog(atom::NativeWindow* parent_window, void ShowOpenDialog(atom::NativeWindow* parent_window,
@ -21,21 +159,41 @@ void ShowOpenDialog(atom::NativeWindow* parent_window,
const base::FilePath& default_path, const base::FilePath& default_path,
int properties, int properties,
const OpenDialogCallback& callback) { const OpenDialogCallback& callback) {
callback.Run(false, std::vector<base::FilePath>()); 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);
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
gtk_file_chooser_set_select_multiple(
GTK_FILE_CHOOSER(open_dialog->dialog()), TRUE);
open_dialog->RunOpenAsynchronous(callback);
} }
bool ShowSaveDialog(atom::NativeWindow* parent_window, bool ShowSaveDialog(atom::NativeWindow* parent_window,
const std::string& title, const std::string& title,
const base::FilePath& default_path, const base::FilePath& default_path,
base::FilePath* path) { base::FilePath* path) {
return false; FileChooserDialog save_dialog(
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path);
gtk_widget_show_all(save_dialog.dialog());
int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
if (response == GTK_RESPONSE_ACCEPT) {
*path = save_dialog.GetFileName();
return true;
} else {
return false;
}
} }
void ShowSaveDialog(atom::NativeWindow* parent_window, void ShowSaveDialog(atom::NativeWindow* parent_window,
const std::string& title, const std::string& title,
const base::FilePath& default_path, const base::FilePath& default_path,
const SaveDialogCallback& callback) { const SaveDialogCallback& callback) {
callback.Run(false, base::FilePath()); FileChooserDialog* save_dialog = new FileChooserDialog(
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path);
save_dialog->RunSaveAsynchronous(callback);
} }
} // namespace file_dialog } // namespace file_dialog

View file

@ -1,6 +1,6 @@
# dialog # dialog
The `dialog` module provides functions to show system dialogs, so web The `dialog` module provides APIs to show native system dialogs, so web
applications can get the same user experience with native applications. applications can get the same user experience with native applications.
An example of showing a dialog to select multiple files and directories: An example of showing a dialog to select multiple files and directories:
@ -11,33 +11,43 @@ var dialog = require('dialog');
console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]}));
``` ```
## dialog.showOpenDialog(options) ## dialog.showOpenDialog([browserWindow], options, [callback])
* `options` Object
* `title` String
* `defaultPath` String
* `properties` Array - Contains which features the dialog should use, can
contain `openFile`, `openDirectory`, `multiSelections` and
`createDirectory`
On success, returns an array of file paths chosen by the user, otherwise
returns `undefined`.
**Note:** The `dialog.showOpenDialog` API is synchronous and blocks all windows.
## dialog.showSaveDialog(browserWindow, options)
* `browserWindow` BrowserWindow * `browserWindow` BrowserWindow
* `options` Object * `options` Object
* `title` String * `title` String
* `defaultPath` String * `defaultPath` String
* `properties` Array - Contains which features the dialog should use, can
contain `openFile`, `openDirectory`, `multiSelections` and
`createDirectory`
* `callback` Function
On success, returns an array of file paths chosen by the user, otherwise
returns `undefined`.
If a `callback` is passed, the API call would be asynchronous and the result
would be passed via `callback(filenames)`
**Note:** On Windows and Linux, an open dialog could not be both file selector
and directory selector at the same time, so if you set `properties` to
`['openFile', 'openDirectory']` on these platforms, a directory selector would
be showed.
## dialog.showSaveDialog([browserWindow], options, [callback])
* `browserWindow` BrowserWindow
* `options` Object
* `title` String
* `defaultPath` String
* `callback` Function
On success, returns the path of file chosen by the user, otherwise returns On success, returns the path of file chosen by the user, otherwise returns
`undefined`. `undefined`.
**Note:** The `dialog.showSaveDialog` API is synchronous and blocks all windows. If a `callback` is passed, the API call would be asynchronous and the result
would be passed via `callback(filename)`
## dialog.showMessageBox([browserWindow], options) ## dialog.showMessageBox([browserWindow], options, [callback])
* `browserWindow` BrowserWindow * `browserWindow` BrowserWindow
* `options` Object * `options` Object
@ -46,8 +56,10 @@ On success, returns the path of file chosen by the user, otherwise returns
* `title` String - Title of the message box, some platforms will not show it * `title` String - Title of the message box, some platforms will not show it
* `message` String - Content of the message box * `message` String - Content of the message box
* `detail` String - Extra information of the message * `detail` String - Extra information of the message
* `callback` Function
Shows a message box, it will block until the message box is closed. It returns Shows a message box, it will block until the message box is closed. It returns
the index of the clicked button. the index of the clicked button.
**Note:** The `dialog.showMessageBox` API is synchronous and blocks all windows. If a `callback` is passed, the API call would be asynchronous and the result
would be passed via `callback(response)`

View file

@ -42,7 +42,7 @@ You can also only build the `Debug` target:
$ ./script/build.py -c Debug $ ./script/build.py -c Debug
``` ```
After building is done, you can find `Atom.app` under `out/Debug`. After building is done, you can find `atom` under `out/Debug`.
## Troubleshooting ## Troubleshooting