message_box_gtk.cc 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright (c) 2015 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/ui/gtk_util.h"
  5. #include "shell/browser/ui/message_box.h"
  6. #include "base/callback.h"
  7. #include "base/strings/string_util.h"
  8. #include "base/strings/utf_string_conversions.h"
  9. #include "shell/browser/browser.h"
  10. #include "shell/browser/native_window_observer.h"
  11. #include "shell/browser/native_window_views.h"
  12. #include "shell/browser/unresponsive_suppressor.h"
  13. #include "ui/base/glib/glib_signal.h"
  14. #include "ui/gfx/image/image_skia.h"
  15. #include "ui/gtk/gtk_util.h"
  16. #if defined(USE_X11)
  17. #include "ui/events/platform/x11/x11_event_source.h"
  18. #endif
  19. #if defined(USE_OZONE) || defined(USE_X11)
  20. #include "ui/base/ui_base_features.h"
  21. #endif
  22. #define ANSI_FOREGROUND_RED "\x1b[31m"
  23. #define ANSI_FOREGROUND_BLACK "\x1b[30m"
  24. #define ANSI_TEXT_BOLD "\x1b[1m"
  25. #define ANSI_BACKGROUND_GRAY "\x1b[47m"
  26. #define ANSI_RESET "\x1b[0m"
  27. namespace electron {
  28. MessageBoxSettings::MessageBoxSettings() = default;
  29. MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
  30. MessageBoxSettings::~MessageBoxSettings() = default;
  31. namespace {
  32. class GtkMessageBox : public NativeWindowObserver {
  33. public:
  34. explicit GtkMessageBox(const MessageBoxSettings& settings)
  35. : cancel_id_(settings.cancel_id),
  36. parent_(static_cast<NativeWindow*>(settings.parent_window)) {
  37. // Create dialog.
  38. dialog_ =
  39. gtk_message_dialog_new(nullptr, // parent
  40. static_cast<GtkDialogFlags>(0), // no flags
  41. GetMessageType(settings.type), // type
  42. GTK_BUTTONS_NONE, // no buttons
  43. "%s", settings.message.c_str());
  44. if (!settings.detail.empty())
  45. gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_),
  46. "%s", settings.detail.c_str());
  47. if (!settings.title.empty())
  48. gtk_window_set_title(GTK_WINDOW(dialog_), settings.title.c_str());
  49. if (!settings.icon.isNull()) {
  50. // No easy way to obtain this programmatically, but GTK+'s docs
  51. // define GTK_ICON_SIZE_DIALOG to be 48 pixels
  52. static constexpr int pixel_width = 48;
  53. static constexpr int pixel_height = 48;
  54. GdkPixbuf* pixbuf =
  55. gtk_util::GdkPixbufFromSkBitmap(*settings.icon.bitmap());
  56. GdkPixbuf* scaled_pixbuf = gdk_pixbuf_scale_simple(
  57. pixbuf, pixel_width, pixel_height, GDK_INTERP_BILINEAR);
  58. GtkWidget* w = gtk_image_new_from_pixbuf(scaled_pixbuf);
  59. gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), w);
  60. gtk_widget_show(w);
  61. g_clear_pointer(&scaled_pixbuf, g_object_unref);
  62. g_clear_pointer(&pixbuf, g_object_unref);
  63. }
  64. if (!settings.checkbox_label.empty()) {
  65. GtkWidget* message_area =
  66. gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_));
  67. GtkWidget* check_button =
  68. gtk_check_button_new_with_label(settings.checkbox_label.c_str());
  69. g_signal_connect(check_button, "toggled",
  70. G_CALLBACK(OnCheckboxToggledThunk), this);
  71. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button),
  72. settings.checkbox_checked);
  73. gtk_container_add(GTK_CONTAINER(message_area), check_button);
  74. gtk_widget_show(check_button);
  75. }
  76. // Add buttons.
  77. GtkDialog* dialog = GTK_DIALOG(dialog_);
  78. if (settings.buttons.size() == 0) {
  79. gtk_dialog_add_button(dialog, TranslateToStock(0, "OK"), 0);
  80. } else {
  81. for (size_t i = 0; i < settings.buttons.size(); ++i) {
  82. gtk_dialog_add_button(dialog, TranslateToStock(i, settings.buttons[i]),
  83. i);
  84. }
  85. }
  86. gtk_dialog_set_default_response(dialog, settings.default_id);
  87. // Parent window.
  88. if (parent_) {
  89. parent_->AddObserver(this);
  90. static_cast<NativeWindowViews*>(parent_)->SetEnabled(false);
  91. gtk::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
  92. gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
  93. }
  94. }
  95. ~GtkMessageBox() override {
  96. gtk_widget_destroy(dialog_);
  97. if (parent_) {
  98. parent_->RemoveObserver(this);
  99. static_cast<NativeWindowViews*>(parent_)->SetEnabled(true);
  100. }
  101. }
  102. GtkMessageType GetMessageType(MessageBoxType type) {
  103. switch (type) {
  104. case MessageBoxType::kInformation:
  105. return GTK_MESSAGE_INFO;
  106. case MessageBoxType::kWarning:
  107. return GTK_MESSAGE_WARNING;
  108. case MessageBoxType::kQuestion:
  109. return GTK_MESSAGE_QUESTION;
  110. case MessageBoxType::kError:
  111. return GTK_MESSAGE_ERROR;
  112. default:
  113. return GTK_MESSAGE_OTHER;
  114. }
  115. }
  116. const char* TranslateToStock(int id, const std::string& text) {
  117. const std::string lower = base::ToLowerASCII(text);
  118. if (lower == "cancel")
  119. return gtk_util::kCancelLabel;
  120. if (lower == "no")
  121. return gtk_util::kNoLabel;
  122. if (lower == "ok")
  123. return gtk_util::kOkLabel;
  124. if (lower == "yes")
  125. return gtk_util::kYesLabel;
  126. return text.c_str();
  127. }
  128. void Show() {
  129. gtk_widget_show(dialog_);
  130. #if defined(USE_X11)
  131. if (!features::IsUsingOzonePlatform()) {
  132. // We need to call gtk_window_present after making the widgets visible to
  133. // make sure window gets correctly raised and gets focus.
  134. x11::Time time = ui::X11EventSource::GetInstance()->GetTimestamp();
  135. gtk_window_present_with_time(GTK_WINDOW(dialog_),
  136. static_cast<uint32_t>(time));
  137. }
  138. #endif
  139. }
  140. int RunSynchronous() {
  141. Show();
  142. int response = gtk_dialog_run(GTK_DIALOG(dialog_));
  143. return (response < 0) ? cancel_id_ : response;
  144. }
  145. void RunAsynchronous(MessageBoxCallback callback) {
  146. callback_ = std::move(callback);
  147. g_signal_connect(dialog_, "delete-event",
  148. G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
  149. g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseDialogThunk),
  150. this);
  151. Show();
  152. }
  153. void OnWindowClosed() override {
  154. parent_->RemoveObserver(this);
  155. parent_ = nullptr;
  156. }
  157. CHROMEG_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, GtkWidget*, int);
  158. CHROMEG_CALLBACK_0(GtkMessageBox, void, OnCheckboxToggled, GtkWidget*);
  159. private:
  160. electron::UnresponsiveSuppressor unresponsive_suppressor_;
  161. // The id to return when the dialog is closed without pressing buttons.
  162. int cancel_id_ = 0;
  163. bool checkbox_checked_ = false;
  164. NativeWindow* parent_;
  165. GtkWidget* dialog_;
  166. MessageBoxCallback callback_;
  167. DISALLOW_COPY_AND_ASSIGN(GtkMessageBox);
  168. };
  169. void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) {
  170. gtk_widget_hide(dialog_);
  171. if (response < 0)
  172. std::move(callback_).Run(cancel_id_, checkbox_checked_);
  173. else
  174. std::move(callback_).Run(response, checkbox_checked_);
  175. delete this;
  176. }
  177. void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget) {
  178. checkbox_checked_ = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
  179. }
  180. } // namespace
  181. int ShowMessageBoxSync(const MessageBoxSettings& settings) {
  182. return GtkMessageBox(settings).RunSynchronous();
  183. }
  184. void ShowMessageBox(const MessageBoxSettings& settings,
  185. MessageBoxCallback callback) {
  186. (new GtkMessageBox(settings))->RunAsynchronous(std::move(callback));
  187. }
  188. void ShowErrorBox(const base::string16& title, const base::string16& content) {
  189. if (Browser::Get()->is_ready()) {
  190. electron::MessageBoxSettings settings;
  191. settings.type = electron::MessageBoxType::kError;
  192. settings.buttons = {};
  193. settings.title = "Error";
  194. settings.message = base::UTF16ToUTF8(title);
  195. settings.detail = base::UTF16ToUTF8(content);
  196. GtkMessageBox(settings).RunSynchronous();
  197. } else {
  198. fprintf(stderr,
  199. ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY ANSI_FOREGROUND_RED
  200. "%s\n" ANSI_FOREGROUND_BLACK "%s" ANSI_RESET "\n",
  201. base::UTF16ToUTF8(title).c_str(),
  202. base::UTF16ToUTF8(content).c_str());
  203. }
  204. }
  205. } // namespace electron