Browse Source

Merge pull request #2145 from atom/gtk-message-box

Use GtkMessageBox for dialog.showMessageBox on Linux
Cheng Zhao 9 years ago
parent
commit
083d0b8b60

+ 1 - 1
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

+ 5 - 36
atom/browser/ui/file_dialog_gtk.cc

@@ -4,49 +4,18 @@
 
 #include "atom/browser/ui/file_dialog.h"
 
-#include <gdk/gdk.h>
-#include <gdk/gdkx.h>
-#include <gtk/gtk.h>
-
-// 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<atom::NativeWindow::DialogScope> dialog_scope_;
-
   DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
 };
 

+ 3 - 1
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<void(int code)> MessageBoxCallback;

+ 203 - 0
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<std::string>& 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<GtkDialogFlags>(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<std::string>& 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<std::string>& 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

+ 1 - 42
atom/browser/ui/message_box_views.cc → atom/browser/ui/message_box_win.cc

@@ -4,10 +4,6 @@
 
 #include "atom/browser/ui/message_box.h"
 
-#if defined(USE_X11)
-#include <gtk/gtk.h>
-#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

+ 1 - 1
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

+ 2 - 1
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',