Browse Source

Add support for checkbox with dialog.showMessageBox

This adds the `checkboxLabel` and `checkboxChecked` options to display a
checkbox in the message box. Fixes #6048.
Birunthan Mohanathas 8 years ago
parent
commit
c8c11e68c6

+ 4 - 2
atom/browser/api/atom_api_dialog.cc

@@ -47,6 +47,8 @@ void ShowMessageBox(int type,
                     const std::string& title,
                     const std::string& message,
                     const std::string& detail,
+                    const std::string& checkbox_label,
+                    bool checkbox_checked,
                     const gfx::ImageSkia& icon,
                     atom::NativeWindow* window,
                     mate::Arguments* args) {
@@ -56,8 +58,8 @@ void ShowMessageBox(int type,
                                                         peek,
                                                         &callback)) {
     atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons,
-                         default_id, cancel_id, options, title,
-                         message, detail, icon, callback);
+                         default_id, cancel_id, options, title, message, detail,
+                         checkbox_label, checkbox_checked, icon, callback);
   } else {
     int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type,
                                       buttons, default_id, cancel_id,

+ 6 - 9
atom/browser/atom_javascript_dialog_manager.cc

@@ -38,14 +38,9 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog(
   }
 
   atom::ShowMessageBox(NativeWindow::FromWebContents(web_contents),
-                       atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE,
-                       buttons,
-                       -1,
-                       0,
-                       atom::MessageBoxOptions::MESSAGE_BOX_NONE,
-                       "",
-                       base::UTF16ToUTF8(message_text),
-                       "",
+                       atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1,
+                       0, atom::MessageBoxOptions::MESSAGE_BOX_NONE, "",
+                       base::UTF16ToUTF8(message_text), "", "", false,
                        gfx::ImageSkia(),
                        base::Bind(&OnMessageBoxCallback, callback));
 }
@@ -66,7 +61,9 @@ void AtomJavaScriptDialogManager::CancelDialogs(
 
 // static
 void AtomJavaScriptDialogManager::OnMessageBoxCallback(
-    const DialogClosedCallback& callback, int code) {
+    const DialogClosedCallback& callback,
+    int code,
+    bool checkbox_checked) {
   callback.Run(code == 0, base::string16());
 }
 

+ 2 - 1
atom/browser/atom_javascript_dialog_manager.h

@@ -32,7 +32,8 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager {
 
  private:
   static void OnMessageBoxCallback(const DialogClosedCallback& callback,
-                                   int code);
+                                   int code,
+                                   bool checkbox_checked);
 };
 
 }  // namespace atom

+ 4 - 1
atom/browser/ui/message_box.h

@@ -32,7 +32,8 @@ enum MessageBoxOptions {
   MESSAGE_BOX_NO_LINK = 1 << 0,
 };
 
-typedef base::Callback<void(int code)> MessageBoxCallback;
+typedef base::Callback<void(int code, bool checkbox_checked)>
+    MessageBoxCallback;
 
 int ShowMessageBox(NativeWindow* parent_window,
                    MessageBoxType type,
@@ -54,6 +55,8 @@ void ShowMessageBox(NativeWindow* parent_window,
                     const std::string& title,
                     const std::string& message,
                     const std::string& detail,
+                    const std::string& checkbox_label,
+                    bool checkbox_checked,
                     const gfx::ImageSkia& icon,
                     const MessageBoxCallback& callback);
 

+ 36 - 9
atom/browser/ui/message_box_gtk.cc

@@ -36,8 +36,11 @@ class GtkMessageBox : public NativeWindowObserver {
                 const std::string& title,
                 const std::string& message,
                 const std::string& detail,
+                const std::string& checkbox_label,
+                bool checkbox_checked,
                 const gfx::ImageSkia& icon)
       : cancel_id_(cancel_id),
+        checkbox_checked_(false),
         parent_(static_cast<NativeWindowViews*>(parent_window)) {
     // Create dialog.
     dialog_ = gtk_message_dialog_new(
@@ -68,6 +71,18 @@ class GtkMessageBox : public NativeWindowObserver {
       g_object_unref(pixbuf);
     }
 
+    if (!checkbox_label.empty()) {
+      GtkWidget* message_area =
+          gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_));
+      GtkWidget* check_button =
+          gtk_check_button_new_with_label(checkbox_label.c_str());
+      g_signal_connect(check_button, "toggled",
+                       G_CALLBACK(OnCheckboxToggledThunk), this);
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button),
+                                   checkbox_checked);
+      gtk_container_add(GTK_CONTAINER(message_area), check_button);
+    }
+
     // Add buttons.
     for (size_t i = 0; i < buttons.size(); ++i) {
       GtkWidget* button = gtk_dialog_add_button(
@@ -154,6 +169,7 @@ class GtkMessageBox : public NativeWindowObserver {
   }
 
   CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int);
+  CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnCheckboxToggled, gpointer);
 
  private:
   atom::UnresponsiveSuppressor unresponsive_suppressor_;
@@ -161,6 +177,8 @@ class GtkMessageBox : public NativeWindowObserver {
   // The id to return when the dialog is closed without pressing buttons.
   int cancel_id_;
 
+  bool checkbox_checked_;
+
   NativeWindowViews* parent_;
   GtkWidget* dialog_;
   MessageBoxCallback callback_;
@@ -172,12 +190,16 @@ void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) {
   gtk_widget_hide(dialog_);
 
   if (response < 0)
-    callback_.Run(cancel_id_);
+    callback_.Run(cancel_id_, checkbox_checked_);
   else
-    callback_.Run(response);
+    callback_.Run(response, checkbox_checked_);
   delete this;
 }
 
+void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget, gpointer data) {
+  checkbox_checked_ = GTK_TOGGLE_BUTTON(widget)->active;
+}
+
 }  // namespace
 
 int ShowMessageBox(NativeWindow* parent,
@@ -190,8 +212,9 @@ int ShowMessageBox(NativeWindow* parent,
                    const std::string& message,
                    const std::string& detail,
                    const gfx::ImageSkia& icon) {
-  return GtkMessageBox(parent, type, buttons, default_id, cancel_id,
-                       title, message, detail, icon).RunSynchronous();
+  return GtkMessageBox(parent, type, buttons, default_id, cancel_id, title,
+                       message, detail, "", false, icon)
+      .RunSynchronous();
 }
 
 void ShowMessageBox(NativeWindow* parent,
@@ -203,18 +226,22 @@ void ShowMessageBox(NativeWindow* parent,
                     const std::string& title,
                     const std::string& message,
                     const std::string& detail,
+                    const std::string& checkbox_label,
+                    bool checkbox_checked,
                     const gfx::ImageSkia& icon,
                     const MessageBoxCallback& callback) {
-  (new GtkMessageBox(parent, type, buttons, default_id, cancel_id,
-                     title, message, detail, icon))->RunAsynchronous(callback);
+  (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, title,
+                     message, detail, checkbox_label, checkbox_checked, 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" }, -1, 0, "Error",
+    GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, {"OK"}, -1, 0, "Error",
                   base::UTF16ToUTF8(title).c_str(),
-                  base::UTF16ToUTF8(content).c_str(),
-                  gfx::ImageSkia()).RunSynchronous();
+                  base::UTF16ToUTF8(content).c_str(), "", false,
+                  gfx::ImageSkia())
+        .RunSynchronous();
   } else {
     fprintf(stderr,
             ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY

+ 17 - 8
atom/browser/ui/message_box_mac.mm

@@ -39,7 +39,7 @@
 - (void)alertDidEnd:(NSAlert*)alert
          returnCode:(NSInteger)returnCode
         contextInfo:(void*)contextInfo {
-  callback_.Run(returnCode);
+  callback_.Run(returnCode, alert.suppressionButton.state == NSOnState);
   [alert_ release];
   [self release];
 
@@ -60,6 +60,8 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
                        const std::string& title,
                        const std::string& message,
                        const std::string& detail,
+                       const std::string& checkbox_label,
+                       bool checkbox_checked,
                        const gfx::ImageSkia& icon) {
   // Ignore the title; it's the window title on other platforms and ignorable.
   NSAlert* alert = [[NSAlert alloc] init];
@@ -95,6 +97,12 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
     [[ns_buttons objectAtIndex:default_id] setKeyEquivalent:@"\r"];
   }
 
+  if (!checkbox_label.empty()) {
+    alert.showsSuppressionButton = YES;
+    alert.suppressionButton.title = base::SysUTF8ToNSString(checkbox_label);
+    alert.suppressionButton.state = checkbox_checked ? NSOnState : NSOffState;
+  }
+
   if (!icon.isNull()) {
     NSImage* image = skia::SkBitmapToNSImageWithColorSpace(
         *icon.bitmap(), base::mac::GetGenericRGBColorSpace());
@@ -104,7 +112,7 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
   return alert;
 }
 
-void SetReturnCode(int* ret_code, int result) {
+void SetReturnCode(int* ret_code, int result, bool checkbox_checked) {
   *ret_code = result;
 }
 
@@ -120,9 +128,8 @@ int ShowMessageBox(NativeWindow* parent_window,
                    const std::string& message,
                    const std::string& detail,
                    const gfx::ImageSkia& icon) {
-  NSAlert* alert = CreateNSAlert(
-      parent_window, type, buttons, default_id, title, message,
-      detail, icon);
+  NSAlert* alert = CreateNSAlert(parent_window, type, buttons, default_id,
+                                 title, message, detail, "", false, icon);
 
   // Use runModal for synchronous alert without parent, since we don't have a
   // window to wait for.
@@ -154,11 +161,13 @@ void ShowMessageBox(NativeWindow* parent_window,
                     const std::string& title,
                     const std::string& message,
                     const std::string& detail,
+                    const std::string& checkbox_label,
+                    bool checkbox_checked,
                     const gfx::ImageSkia& icon,
                     const MessageBoxCallback& callback) {
-  NSAlert* alert = CreateNSAlert(
-      parent_window, type, buttons, default_id, title, message,
-      detail, icon);
+  NSAlert* alert =
+      CreateNSAlert(parent_window, type, buttons, default_id, title, message,
+                    detail, checkbox_label, checkbox_checked, icon);
   ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback
                                                            andAlert:alert
                                                        callEndModal:false];

+ 61 - 28
atom/browser/ui/message_box_win.cc

@@ -72,7 +72,7 @@ void MapToCommonID(const std::vector<base::string16>& buttons,
   }
 }
 
-int ShowMessageBoxUTF16(HWND parent,
+int ShowTaskDialogUTF16(NativeWindow* parent,
                         MessageBoxType type,
                         const std::vector<base::string16>& buttons,
                         int default_id,
@@ -81,6 +81,8 @@ int ShowMessageBoxUTF16(HWND parent,
                         const base::string16& title,
                         const base::string16& message,
                         const base::string16& detail,
+                        const base::string16& checkbox_label,
+                        bool* checkbox_checked,
                         const gfx::ImageSkia& icon) {
   TASKDIALOG_FLAGS flags =
       TDF_SIZE_TO_CONTENT |  // Show all content.
@@ -88,10 +90,14 @@ int ShowMessageBoxUTF16(HWND parent,
 
   TASKDIALOGCONFIG config = { 0 };
   config.cbSize     = sizeof(config);
-  config.hwndParent = parent;
   config.hInstance  = GetModuleHandle(NULL);
   config.dwFlags    = flags;
 
+  if (parent) {
+    config.hwndParent =
+        static_cast<atom::NativeWindowViews*>(parent)->GetAcceleratedWidget();
+  }
+
   if (default_id > 0)
     config.nDefaultButton = kIDStart + default_id;
 
@@ -132,6 +138,14 @@ int ShowMessageBoxUTF16(HWND parent,
     config.pszContent = detail.c_str();
   }
 
+  if (!checkbox_label.empty()) {
+    config.pszVerificationText = checkbox_label.c_str();
+
+    if (checkbox_checked && *checkbox_checked) {
+      config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED;
+    }
+  }
+
   // Iterate through the buttons, put common buttons in dwCommonButtons
   // and custom buttons in pButtons.
   std::map<int, int> id_map;
@@ -151,7 +165,12 @@ int ShowMessageBoxUTF16(HWND parent,
   }
 
   int id = 0;
-  TaskDialogIndirect(&config, &id, NULL, NULL);
+  BOOL verificationFlagChecked = FALSE;
+  TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
+  if (checkbox_checked) {
+    *checkbox_checked = verificationFlagChecked;
+  }
+
   if (id_map.find(id) != id_map.end())  // common button.
     return id_map[id];
   else if (id >= kIDStart)  // custom button.
@@ -160,6 +179,29 @@ int ShowMessageBoxUTF16(HWND parent,
     return cancel_id;
 }
 
+int ShowTaskDialogUTF8(NativeWindow* parent,
+                       MessageBoxType type,
+                       const std::vector<std::string>& buttons,
+                       int default_id,
+                       int cancel_id,
+                       int options,
+                       const std::string& title,
+                       const std::string& message,
+                       const std::string& detail,
+                       const std::string& checkbox_label,
+                       bool* checkbox_checked,
+                       const gfx::ImageSkia& icon) {
+  std::vector<base::string16> utf16_buttons;
+  for (const auto& button : buttons)
+    utf16_buttons.push_back(base::UTF8ToUTF16(button));
+
+  return ShowTaskDialogUTF16(
+      parent, type, utf16_buttons, default_id, cancel_id, options,
+      base::UTF8ToUTF16(title), base::UTF8ToUTF16(message),
+      base::UTF8ToUTF16(detail), base::UTF8ToUTF16(checkbox_label),
+      checkbox_checked, icon);
+}
+
 void RunMessageBoxInNewThread(base::Thread* thread,
                               NativeWindow* parent,
                               MessageBoxType type,
@@ -170,12 +212,16 @@ void RunMessageBoxInNewThread(base::Thread* thread,
                               const std::string& title,
                               const std::string& message,
                               const std::string& detail,
+                              const std::string& checkbox_label,
+                              bool checkbox_checked,
                               const gfx::ImageSkia& icon,
                               const MessageBoxCallback& callback) {
-  int result = ShowMessageBox(parent, type, buttons, default_id,
-                              cancel_id, options, title, message, detail, icon);
+  int result = ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id,
+                                  options, title, message, detail,
+                                  checkbox_label, &checkbox_checked, icon);
   content::BrowserThread::PostTask(
-      content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result));
+      content::BrowserThread::UI, FROM_HERE,
+      base::Bind(callback, result, checkbox_checked));
   content::BrowserThread::DeleteSoon(
       content::BrowserThread::UI, FROM_HERE, thread);
 }
@@ -192,25 +238,9 @@ int ShowMessageBox(NativeWindow* parent,
                    const std::string& message,
                    const std::string& detail,
                    const gfx::ImageSkia& icon) {
-  std::vector<base::string16> utf16_buttons;
-  for (const auto& button : buttons)
-    utf16_buttons.push_back(base::UTF8ToUTF16(button));
-
-  HWND hwnd_parent = parent ?
-      static_cast<atom::NativeWindowViews*>(parent)->GetAcceleratedWidget() :
-      NULL;
-
   atom::UnresponsiveSuppressor suppressor;
-  return ShowMessageBoxUTF16(hwnd_parent,
-                             type,
-                             utf16_buttons,
-                             default_id,
-                             cancel_id,
-                             options,
-                             base::UTF8ToUTF16(title),
-                             base::UTF8ToUTF16(message),
-                             base::UTF8ToUTF16(detail),
-                             icon);
+  return ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id,
+                            options, title, message, detail, "", nullptr, icon);
 }
 
 void ShowMessageBox(NativeWindow* parent,
@@ -222,13 +252,15 @@ void ShowMessageBox(NativeWindow* parent,
                     const std::string& title,
                     const std::string& message,
                     const std::string& detail,
+                    const std::string& checkbox_label,
+                    bool checkbox_checked,
                     const gfx::ImageSkia& icon,
                     const MessageBoxCallback& callback) {
   std::unique_ptr<base::Thread> thread(
       new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread"));
   thread->init_com_with_mta(false);
   if (!thread->Start()) {
-    callback.Run(cancel_id);
+    callback.Run(cancel_id, checkbox_checked);
     return;
   }
 
@@ -237,13 +269,14 @@ void ShowMessageBox(NativeWindow* parent,
       FROM_HERE,
       base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained),
                  parent, type, buttons, default_id, cancel_id, options, title,
-                 message, detail, icon, callback));
+                 message, detail, checkbox_label, checkbox_checked, icon,
+                 callback));
 }
 
 void ShowErrorBox(const base::string16& title, const base::string16& content) {
   atom::UnresponsiveSuppressor suppressor;
-  ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error",
-                      title, content, gfx::ImageSkia());
+  ShowTaskDialogUTF16(nullptr, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error",
+                      title, content, L"", nullptr, gfx::ImageSkia());
 }
 
 }  // namespace atom

+ 7 - 0
docs/api/dialog.md

@@ -122,6 +122,11 @@ will be passed via `callback(filename)`
   * `title` String (optional) - Title of the message box, some platforms will not show it.
   * `message` String - Content of the message box.
   * `detail` String (optional) - Extra information of the message.
+  * `checkboxLabel` String (optional) - If provided, the message box will
+    include a checkbox with the given label. The checkbox state can be
+    inspected only when using `callback`.
+  * `checkboxChecked` Boolean (optional) - Initial checked state of the
+    checkbox. `false` by default.
   * `icon` [NativeImage](native-image.md) (optional)
   * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog
     instead of clicking the buttons of the dialog. By default it is the index
@@ -135,6 +140,8 @@ will be passed via `callback(filename)`
     set `noLink` to `true`.
 * `callback` Function (optional)
   * `response` Number - The index of the button that was clicked
+  * `checkboxChecked` Boolean - The checked state of the checkbox if
+    `checkboxLabel` was set. Otherwise `false`.
 
 Returns `Integer`, the index of the clicked button, if a callback is provided
 it returns undefined.

+ 14 - 3
lib/browser/api/dialog.js

@@ -178,7 +178,10 @@ module.exports = {
       }
     }
 
-    let {buttons, cancelId, defaultId, detail, icon, message, title, type} = options
+    let {
+      buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail,
+      icon, message, title, type
+    } = options
 
     if (type == null) {
       type = 'none'
@@ -217,6 +220,14 @@ module.exports = {
       throw new TypeError('Detail must be a string')
     }
 
+    checkboxChecked = !!checkboxChecked
+
+    if (checkboxLabel == null) {
+      checkboxLabel = ''
+    } else if (typeof checkboxLabel !== 'string') {
+      throw new TypeError('checkboxLabel must be a string')
+    }
+
     if (icon == null) {
       icon = null
     }
@@ -239,8 +250,8 @@ module.exports = {
 
     const flags = options.noLink ? messageBoxOptions.noLink : 0
     return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId,
-                                  flags, title, message, detail, icon, window,
-                                  callback)
+                                  flags, title, message, detail, checkboxLabel,
+                                  checkboxChecked, icon, window, callback)
   },
 
   showErrorBox: function (...args) {

+ 4 - 0
spec/api-dialog-spec.js

@@ -59,6 +59,10 @@ describe('dialog module', () => {
       assert.throws(() => {
         dialog.showMessageBox({detail: 3.14})
       }, /Detail must be a string/)
+
+      assert.throws(() => {
+        dialog.showMessageBox({checkboxLabel: false})
+      }, /checkboxLabel must be a string/)
     })
   })