diff --git a/atom/browser/api/lib/dialog.coffee b/atom/browser/api/lib/dialog.coffee index 95ecf99d3e03..d95fa7426ac5 100644 --- a/atom/browser/api/lib/dialog.coffee +++ b/atom/browser/api/lib/dialog.coffee @@ -9,7 +9,7 @@ fileDialogProperties = multiSelections: 1 << 2 createDirectory: 1 << 3 -messageBoxTypes = ['none', 'info', 'warning'] +messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] parseArgs = (window, options, callback) -> unless window is null or window?.constructor is BrowserWindow diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 40c14028d4fa..6eab9ef89f7b 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -4,49 +4,18 @@ #include "atom/browser/ui/file_dialog.h" -#include -#include -#include - -// This conflicts with mate::Converter, -#undef True -#undef False -// and V8. -#undef None - #include "atom/browser/native_window.h" #include "base/callback.h" #include "base/files/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" +#include "chrome/browser/ui/libgtk2ui/gtk2_util.h" #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" namespace file_dialog { namespace { -const char kAuraTransientParent[] = "aura-transient-parent"; - -void SetGtkTransientForAura(GtkWidget* dialog, aura::Window* parent) { - if (!parent || !parent->GetHost()) - return; - - gtk_widget_realize(dialog); - GdkWindow* gdk_window = gtk_widget_get_window(dialog); - - // TODO(erg): Check to make sure we're using X11 if wayland or some other - // display server ever happens. Otherwise, this will crash. - XSetTransientForHint(GDK_WINDOW_XDISPLAY(gdk_window), - GDK_WINDOW_XID(gdk_window), - parent->GetHost()->GetAcceleratedWidget()); - - // We also set the |parent| as a property of |dialog|, so that we can unlink - // the two later. - 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) { @@ -65,7 +34,7 @@ class FileChooserDialog { const std::string& title, const base::FilePath& default_path, const Filters& filters) - : dialog_scope_(new atom::NativeWindow::DialogScope(parent_window)) { + : dialog_scope_(parent_window) { const char* confirm_text = GTK_STOCK_OK; if (action == GTK_FILE_CHOOSER_ACTION_SAVE) confirm_text = GTK_STOCK_SAVE; @@ -81,7 +50,7 @@ class FileChooserDialog { NULL); if (parent_window) { gfx::NativeWindow window = parent_window->GetNativeWindow(); - SetGtkTransientForAura(dialog_, window); + libgtk2ui::SetGtkTransientForAura(dialog_, window); } if (action == GTK_FILE_CHOOSER_ACTION_SAVE) @@ -162,13 +131,13 @@ class FileChooserDialog { private: void AddFilters(const Filters& filters); + atom::NativeWindow::DialogScope dialog_scope_; + GtkWidget* dialog_; SaveDialogCallback save_callback_; OpenDialogCallback open_callback_; - scoped_ptr dialog_scope_; - DISALLOW_COPY_AND_ASSIGN(FileChooserDialog); }; diff --git a/atom/browser/ui/message_box.h b/atom/browser/ui/message_box.h index 04792085b7ae..acd1e3926b34 100644 --- a/atom/browser/ui/message_box.h +++ b/atom/browser/ui/message_box.h @@ -22,7 +22,9 @@ class NativeWindow; enum MessageBoxType { MESSAGE_BOX_TYPE_NONE = 0, MESSAGE_BOX_TYPE_INFORMATION, - MESSAGE_BOX_TYPE_WARNING + MESSAGE_BOX_TYPE_WARNING, + MESSAGE_BOX_TYPE_ERROR, + MESSAGE_BOX_TYPE_QUESTION, }; typedef base::Callback MessageBoxCallback; diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc new file mode 100644 index 000000000000..626f16704dde --- /dev/null +++ b/atom/browser/ui/message_box_gtk.cc @@ -0,0 +1,203 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/message_box.h" + +#include "atom/browser/browser.h" +#include "atom/browser/native_window.h" +#include "base/callback.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h" +#include "chrome/browser/ui/libgtk2ui/gtk2_util.h" +#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" +#include "ui/views/widget/desktop_aura/x11_desktop_handler.h" + +#define ANSI_FOREGROUND_RED "\x1b[31m" +#define ANSI_FOREGROUND_BLACK "\x1b[30m" +#define ANSI_TEXT_BOLD "\x1b[1m" +#define ANSI_BACKGROUND_GRAY "\x1b[47m" +#define ANSI_RESET "\x1b[0m" + +namespace atom { + +namespace { + +class GtkMessageBox { + public: + GtkMessageBox(NativeWindow* parent_window, + MessageBoxType type, + const std::vector& buttons, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon) + : dialog_scope_(parent_window), + cancel_id_(0) { + // Create dialog. + dialog_ = gtk_message_dialog_new( + nullptr, // parent + static_cast(0), // no flags + GetMessageType(type), // type + GTK_BUTTONS_NONE, // no buttons + "%s", message.c_str()); + if (!detail.empty()) + gtk_message_dialog_format_secondary_text( + GTK_MESSAGE_DIALOG(dialog_), "%s", detail.c_str()); + if (!title.empty()) + gtk_window_set_title(GTK_WINDOW(dialog_), title.c_str()); + + // Set dialog's icon. + if (!icon.isNull()) { + GdkPixbuf* pixbuf = libgtk2ui::GdkPixbufFromSkBitmap(*icon.bitmap()); + GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); + gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), image); + gtk_widget_show(image); + g_object_unref(pixbuf); + } + + // Add buttons. + for (size_t i = 0; i < buttons.size(); ++i) { + gtk_dialog_add_button(GTK_DIALOG(dialog_), + TranslateToStock(i, buttons[i]), + i); + } + + // Parent window. + if (parent_window) { + gfx::NativeWindow window = parent_window->GetNativeWindow(); + libgtk2ui::SetGtkTransientForAura(dialog_, window); + } + } + + ~GtkMessageBox() { + gtk_widget_destroy(dialog_); + } + + GtkMessageType GetMessageType(MessageBoxType type) { + switch (type) { + case MESSAGE_BOX_TYPE_INFORMATION: + return GTK_MESSAGE_INFO; + case MESSAGE_BOX_TYPE_WARNING: + return GTK_MESSAGE_WARNING; + case MESSAGE_BOX_TYPE_QUESTION: + return GTK_MESSAGE_QUESTION; + case MESSAGE_BOX_TYPE_ERROR: + return GTK_MESSAGE_ERROR; + default: + return GTK_MESSAGE_OTHER; + } + } + + const char* TranslateToStock(int id, const std::string& text) { + std::string lower = base::StringToLowerASCII(text); + if (lower == "cancel") { + cancel_id_ = id; + return GTK_STOCK_CANCEL; + } else if (lower == "no") { + cancel_id_ = id; + return GTK_STOCK_NO; + } else if (lower == "ok") { + return GTK_STOCK_OK; + } else if (lower == "yes") { + return GTK_STOCK_YES; + } else { + return text.c_str(); + } + } + + void Show() { + gtk_widget_show_all(dialog_); + // We need to call gtk_window_present after making the widgets visible to + // make sure window gets correctly raised and gets focus. + int time = views::X11DesktopHandler::get()->wm_user_time_ms(); + gtk_window_present_with_time(GTK_WINDOW(dialog_), time); + } + + int RunSynchronous() { + gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE); + Show(); + int response = gtk_dialog_run(GTK_DIALOG(dialog_)); + if (response < 0) + return cancel_id_; + else + return response; + } + + void RunAsynchronous(const MessageBoxCallback& callback) { + callback_ = callback; + g_signal_connect(dialog_, "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), nullptr); + g_signal_connect(dialog_, "response", + G_CALLBACK(OnResponseDialogThunk), this); + Show(); + } + + CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int); + + private: + atom::NativeWindow::DialogScope dialog_scope_; + + // The id to return when the dialog is closed without pressing buttons. + int cancel_id_; + + GtkWidget* dialog_; + MessageBoxCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(GtkMessageBox); +}; + +void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { + gtk_widget_hide_all(dialog_); + + if (response < 0) + callback_.Run(cancel_id_); + else + callback_.Run(response); + delete this; +} + +} // namespace + +int ShowMessageBox(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon) { + return GtkMessageBox(parent, type, buttons, title, message, detail, + icon).RunSynchronous(); +} + +void ShowMessageBox(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon, + const MessageBoxCallback& callback) { + (new GtkMessageBox(parent, type, buttons, title, message, detail, + icon))->RunAsynchronous(callback); +} + +void ShowErrorBox(const base::string16& title, const base::string16& content) { + if (Browser::Get()->is_ready()) { + GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, "Error", + base::UTF16ToUTF8(title).c_str(), + base::UTF16ToUTF8(content).c_str(), + gfx::ImageSkia()).RunSynchronous(); + } else { + fprintf(stderr, + ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY + ANSI_FOREGROUND_RED "%s\n" + ANSI_FOREGROUND_BLACK "%s" + ANSI_RESET "\n", + base::UTF16ToUTF8(title).c_str(), + base::UTF16ToUTF8(content).c_str()); + } +} + +} // namespace atom diff --git a/atom/browser/ui/message_box_views.cc b/atom/browser/ui/message_box_win.cc similarity index 90% rename from atom/browser/ui/message_box_views.cc rename to atom/browser/ui/message_box_win.cc index 6e1cc9daa2ba..508d6eb233d9 100644 --- a/atom/browser/ui/message_box_views.cc +++ b/atom/browser/ui/message_box_win.cc @@ -4,10 +4,6 @@ #include "atom/browser/ui/message_box.h" -#if defined(USE_X11) -#include -#endif - #include "atom/browser/native_window.h" #include "base/callback.h" #include "base/message_loop/message_loop.h" @@ -26,21 +22,10 @@ #include "ui/views/widget/widget_delegate.h" #include "ui/wm/core/shadow_types.h" -#if defined(USE_X11) -#include "atom/browser/browser.h" -#include "ui/views/window/native_frame_view.h" -#endif - #if defined(OS_WIN) #include "ui/base/win/message_box_win.h" #endif -#define ANSI_FOREGROUND_RED "\x1b[31m" -#define ANSI_FOREGROUND_BLACK "\x1b[30m" -#define ANSI_TEXT_BOLD "\x1b[1m" -#define ANSI_BACKGROUND_GRAY "\x1b[47m" -#define ANSI_RESET "\x1b[0m" - namespace atom { namespace { @@ -276,13 +261,8 @@ ui::ModalType MessageDialog::GetModalType() const { views::NonClientFrameView* MessageDialog::CreateNonClientFrameView( views::Widget* widget) { - if (!parent_) { -#if defined(USE_X11) - return new views::NativeFrameView(widget); -#else + if (!parent_) return NULL; -#endif - } // Create a bubble style frame like Chrome. views::BubbleFrameView* frame = new views::BubbleFrameView(gfx::Insets()); @@ -390,28 +370,7 @@ void ShowMessageBox(NativeWindow* parent_window, } void ShowErrorBox(const base::string16& title, const base::string16& content) { -#if defined(OS_WIN) ui::MessageBox(NULL, content, title, MB_OK | MB_ICONERROR | MB_TASKMODAL); -#elif defined(USE_X11) - if (Browser::Get()->is_ready()) { - GtkWidget* dialog = gtk_message_dialog_new( - NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "%s", base::UTF16ToUTF8(title).c_str()); - gtk_message_dialog_format_secondary_text( - GTK_MESSAGE_DIALOG(dialog), - "%s", base::UTF16ToUTF8(content).c_str()); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - } else { - fprintf(stderr, - ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY - ANSI_FOREGROUND_RED "%s\n" - ANSI_FOREGROUND_BLACK "%s" - ANSI_RESET "\n", - base::UTF16ToUTF8(title).c_str(), - base::UTF16ToUTF8(content).c_str()); - } -#endif } } // namespace atom diff --git a/docs/api/dialog.md b/docs/api/dialog.md index f8058649ae57..30689d958630 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -70,7 +70,7 @@ will be passed via `callback(filename)` * `browserWindow` BrowserWindow * `options` Object - * `type` String - Can be `"none"`, `"info"` or `"warning"` + * `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"` * `buttons` Array - Array of texts for buttons * `title` String - Title of the message box, some platforms will not show it * `message` String - Content of the message box diff --git a/filenames.gypi b/filenames.gypi index 3f299c066a65..837dd3301cc8 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -175,8 +175,9 @@ 'atom/browser/ui/file_dialog_mac.mm', 'atom/browser/ui/file_dialog_win.cc', 'atom/browser/ui/message_box.h', + 'atom/browser/ui/message_box_gtk.cc', 'atom/browser/ui/message_box_mac.mm', - 'atom/browser/ui/message_box_views.cc', + 'atom/browser/ui/message_box_win.cc', 'atom/browser/ui/tray_icon.cc', 'atom/browser/ui/tray_icon.h', 'atom/browser/ui/tray_icon_gtk.cc',