2014-10-31 18:17:05 +00:00
|
|
|
// Copyright (c) 2013 GitHub, Inc.
|
2014-04-25 09:49:37 +00:00
|
|
|
// Use of this source code is governed by the MIT license that can be
|
2013-07-05 01:59:53 +00:00
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2014-03-16 00:30:26 +00:00
|
|
|
#include "atom/browser/ui/message_box.h"
|
2013-07-05 01:59:53 +00:00
|
|
|
|
2016-08-26 22:43:40 +00:00
|
|
|
#include <windows.h> // windows.h must be included first
|
|
|
|
|
2015-07-08 06:12:11 +00:00
|
|
|
#include <commctrl.h>
|
|
|
|
|
2015-07-08 08:23:43 +00:00
|
|
|
#include <map>
|
|
|
|
#include <vector>
|
|
|
|
|
2015-08-04 13:56:17 +00:00
|
|
|
#include "atom/browser/browser.h"
|
2015-07-08 06:12:11 +00:00
|
|
|
#include "atom/browser/native_window_views.h"
|
2016-07-11 06:29:03 +00:00
|
|
|
#include "atom/browser/unresponsive_suppressor.h"
|
2013-09-22 09:11:09 +00:00
|
|
|
#include "base/callback.h"
|
2015-07-08 07:55:53 +00:00
|
|
|
#include "base/strings/string_util.h"
|
2013-12-17 06:01:40 +00:00
|
|
|
#include "base/strings/utf_string_conversions.h"
|
2015-07-08 06:12:11 +00:00
|
|
|
#include "base/threading/thread.h"
|
2015-07-08 07:04:34 +00:00
|
|
|
#include "base/win/scoped_gdi_object.h"
|
2015-07-08 06:12:11 +00:00
|
|
|
#include "content/public/browser/browser_thread.h"
|
2015-07-08 07:04:34 +00:00
|
|
|
#include "ui/gfx/icon_util.h"
|
2018-04-10 06:23:16 +00:00
|
|
|
#include "ui/gfx/image/image_skia.h"
|
2014-11-05 08:05:11 +00:00
|
|
|
|
2013-07-05 01:59:53 +00:00
|
|
|
namespace atom {
|
|
|
|
|
2013-07-24 10:34:50 +00:00
|
|
|
namespace {
|
|
|
|
|
2015-07-08 06:35:28 +00:00
|
|
|
// Small command ID values are already taken by Windows, we have to start from
|
|
|
|
// a large number to avoid conflicts with Windows.
|
|
|
|
const int kIDStart = 100;
|
|
|
|
|
2015-07-08 07:55:53 +00:00
|
|
|
// Get the common ID from button's name.
|
|
|
|
struct CommonButtonID {
|
|
|
|
int button;
|
|
|
|
int id;
|
|
|
|
};
|
|
|
|
CommonButtonID GetCommonID(const base::string16& button) {
|
2015-12-07 11:56:23 +00:00
|
|
|
base::string16 lower = base::ToLowerASCII(button);
|
2015-07-08 07:55:53 +00:00
|
|
|
if (lower == L"ok")
|
|
|
|
return { TDCBF_OK_BUTTON, IDOK };
|
|
|
|
else if (lower == L"yes")
|
|
|
|
return { TDCBF_YES_BUTTON, IDYES };
|
|
|
|
else if (lower == L"no")
|
|
|
|
return { TDCBF_NO_BUTTON, IDNO };
|
|
|
|
else if (lower == L"cancel")
|
|
|
|
return { TDCBF_CANCEL_BUTTON, IDCANCEL };
|
|
|
|
else if (lower == L"retry")
|
|
|
|
return { TDCBF_RETRY_BUTTON, IDRETRY };
|
|
|
|
else if (lower == L"close")
|
|
|
|
return { TDCBF_CLOSE_BUTTON, IDCLOSE };
|
|
|
|
return { -1, -1 };
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine whether the buttons are common buttons, if so map common ID
|
|
|
|
// to button ID.
|
|
|
|
void MapToCommonID(const std::vector<base::string16>& buttons,
|
|
|
|
std::map<int, int>* id_map,
|
|
|
|
TASKDIALOG_COMMON_BUTTON_FLAGS* button_flags,
|
|
|
|
std::vector<TASKDIALOG_BUTTON>* dialog_buttons) {
|
|
|
|
for (size_t i = 0; i < buttons.size(); ++i) {
|
|
|
|
auto common = GetCommonID(buttons[i]);
|
|
|
|
if (common.button != -1) {
|
|
|
|
// It is a common button.
|
|
|
|
(*id_map)[common.id] = i;
|
|
|
|
(*button_flags) |= common.button;
|
|
|
|
} else {
|
|
|
|
// It is a custom button.
|
2016-05-10 04:09:41 +00:00
|
|
|
dialog_buttons->push_back(
|
|
|
|
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
|
2015-07-08 07:55:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-06 15:35:36 +00:00
|
|
|
int ShowTaskDialogUTF16(NativeWindow* parent,
|
2015-07-08 06:46:06 +00:00
|
|
|
MessageBoxType type,
|
2015-07-08 06:12:11 +00:00
|
|
|
const std::vector<base::string16>& buttons,
|
2016-01-10 23:33:27 +00:00
|
|
|
int default_id,
|
2015-07-08 06:12:11 +00:00
|
|
|
int cancel_id,
|
2015-07-23 09:20:43 +00:00
|
|
|
int options,
|
2015-07-08 06:12:11 +00:00
|
|
|
const base::string16& title,
|
|
|
|
const base::string16& message,
|
2015-07-08 07:04:34 +00:00
|
|
|
const base::string16& detail,
|
2017-02-06 15:35:36 +00:00
|
|
|
const base::string16& checkbox_label,
|
|
|
|
bool* checkbox_checked,
|
2015-07-08 07:04:34 +00:00
|
|
|
const gfx::ImageSkia& icon) {
|
2015-08-22 01:20:52 +00:00
|
|
|
TASKDIALOG_FLAGS flags =
|
2015-08-22 01:26:09 +00:00
|
|
|
TDF_SIZE_TO_CONTENT | // Show all content.
|
|
|
|
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
|
2015-07-08 06:12:11 +00:00
|
|
|
|
|
|
|
TASKDIALOGCONFIG config = { 0 };
|
2016-01-11 13:12:07 +00:00
|
|
|
config.cbSize = sizeof(config);
|
|
|
|
config.hInstance = GetModuleHandle(NULL);
|
|
|
|
config.dwFlags = flags;
|
|
|
|
|
2017-02-06 15:35:36 +00:00
|
|
|
if (parent) {
|
|
|
|
config.hwndParent =
|
|
|
|
static_cast<atom::NativeWindowViews*>(parent)->GetAcceleratedWidget();
|
|
|
|
}
|
|
|
|
|
2016-01-11 13:12:07 +00:00
|
|
|
if (default_id > 0)
|
|
|
|
config.nDefaultButton = kIDStart + default_id;
|
2015-07-08 08:23:43 +00:00
|
|
|
|
2015-08-04 13:56:17 +00:00
|
|
|
// TaskDialogIndirect doesn't allow empty name, if we set empty title it
|
|
|
|
// will show "electron.exe" in title.
|
|
|
|
base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName());
|
|
|
|
if (title.empty())
|
|
|
|
config.pszWindowTitle = app_name.c_str();
|
|
|
|
else
|
2015-07-08 08:23:43 +00:00
|
|
|
config.pszWindowTitle = title.c_str();
|
2015-07-08 06:41:19 +00:00
|
|
|
|
2015-07-08 07:04:34 +00:00
|
|
|
base::win::ScopedHICON hicon;
|
|
|
|
if (!icon.isNull()) {
|
2016-03-09 19:27:16 +00:00
|
|
|
hicon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap());
|
2015-07-08 07:04:34 +00:00
|
|
|
config.dwFlags |= TDF_USE_HICON_MAIN;
|
2016-03-09 19:27:16 +00:00
|
|
|
config.hMainIcon = hicon.get();
|
2015-07-08 07:04:34 +00:00
|
|
|
} else {
|
|
|
|
// Show icon according to dialog's type.
|
|
|
|
switch (type) {
|
|
|
|
case MESSAGE_BOX_TYPE_INFORMATION:
|
2015-07-14 04:49:29 +00:00
|
|
|
case MESSAGE_BOX_TYPE_QUESTION:
|
2015-07-08 07:04:34 +00:00
|
|
|
config.pszMainIcon = TD_INFORMATION_ICON;
|
|
|
|
break;
|
|
|
|
case MESSAGE_BOX_TYPE_WARNING:
|
|
|
|
config.pszMainIcon = TD_WARNING_ICON;
|
|
|
|
break;
|
|
|
|
case MESSAGE_BOX_TYPE_ERROR:
|
|
|
|
config.pszMainIcon = TD_ERROR_ICON;
|
|
|
|
break;
|
|
|
|
}
|
2015-07-08 06:46:06 +00:00
|
|
|
}
|
|
|
|
|
2015-07-08 06:41:19 +00:00
|
|
|
// If "detail" is empty then don't make message hilighted.
|
|
|
|
if (detail.empty()) {
|
|
|
|
config.pszContent = message.c_str();
|
|
|
|
} else {
|
|
|
|
config.pszMainInstruction = message.c_str();
|
|
|
|
config.pszContent = detail.c_str();
|
|
|
|
}
|
2015-07-08 06:12:11 +00:00
|
|
|
|
2017-02-06 15:35:36 +00:00
|
|
|
if (!checkbox_label.empty()) {
|
|
|
|
config.pszVerificationText = checkbox_label.c_str();
|
|
|
|
|
|
|
|
if (checkbox_checked && *checkbox_checked) {
|
|
|
|
config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-08 07:55:53 +00:00
|
|
|
// Iterate through the buttons, put common buttons in dwCommonButtons
|
|
|
|
// and custom buttons in pButtons.
|
|
|
|
std::map<int, int> id_map;
|
|
|
|
std::vector<TASKDIALOG_BUTTON> dialog_buttons;
|
2015-07-23 09:28:04 +00:00
|
|
|
if (options & MESSAGE_BOX_NO_LINK) {
|
|
|
|
for (size_t i = 0; i < buttons.size(); ++i)
|
2016-05-10 04:09:41 +00:00
|
|
|
dialog_buttons.push_back(
|
|
|
|
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
|
2015-07-23 09:28:04 +00:00
|
|
|
} else {
|
|
|
|
MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons);
|
|
|
|
}
|
2015-07-08 08:23:43 +00:00
|
|
|
if (dialog_buttons.size() > 0) {
|
|
|
|
config.pButtons = &dialog_buttons.front();
|
|
|
|
config.cButtons = dialog_buttons.size();
|
2015-07-23 09:20:43 +00:00
|
|
|
if (!(options & MESSAGE_BOX_NO_LINK))
|
|
|
|
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
|
2015-07-08 08:23:43 +00:00
|
|
|
}
|
2015-07-08 07:55:53 +00:00
|
|
|
|
2015-07-08 06:12:11 +00:00
|
|
|
int id = 0;
|
2017-02-06 15:35:36 +00:00
|
|
|
BOOL verificationFlagChecked = FALSE;
|
|
|
|
TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
|
|
|
|
if (checkbox_checked) {
|
|
|
|
*checkbox_checked = verificationFlagChecked;
|
|
|
|
}
|
|
|
|
|
2015-07-08 07:55:53 +00:00
|
|
|
if (id_map.find(id) != id_map.end()) // common button.
|
|
|
|
return id_map[id];
|
|
|
|
else if (id >= kIDStart) // custom button.
|
2015-07-08 06:35:28 +00:00
|
|
|
return id - kIDStart;
|
2015-07-08 07:55:53 +00:00
|
|
|
else
|
|
|
|
return cancel_id;
|
2015-07-08 06:12:11 +00:00
|
|
|
}
|
|
|
|
|
2017-02-06 15:35:36 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-07-08 06:12:11 +00:00
|
|
|
void RunMessageBoxInNewThread(base::Thread* thread,
|
|
|
|
NativeWindow* parent,
|
|
|
|
MessageBoxType type,
|
|
|
|
const std::vector<std::string>& buttons,
|
2016-01-10 23:33:27 +00:00
|
|
|
int default_id,
|
2015-07-08 06:12:11 +00:00
|
|
|
int cancel_id,
|
2015-07-23 09:28:04 +00:00
|
|
|
int options,
|
2015-07-08 06:12:11 +00:00
|
|
|
const std::string& title,
|
|
|
|
const std::string& message,
|
|
|
|
const std::string& detail,
|
2017-02-06 15:35:36 +00:00
|
|
|
const std::string& checkbox_label,
|
|
|
|
bool checkbox_checked,
|
2015-07-08 06:12:11 +00:00
|
|
|
const gfx::ImageSkia& icon,
|
|
|
|
const MessageBoxCallback& callback) {
|
2017-02-06 15:35:36 +00:00
|
|
|
int result = ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id,
|
|
|
|
options, title, message, detail,
|
|
|
|
checkbox_label, &checkbox_checked, icon);
|
2015-07-08 06:12:11 +00:00
|
|
|
content::BrowserThread::PostTask(
|
2017-02-06 15:35:36 +00:00
|
|
|
content::BrowserThread::UI, FROM_HERE,
|
|
|
|
base::Bind(callback, result, checkbox_checked));
|
2015-07-08 06:12:11 +00:00
|
|
|
content::BrowserThread::DeleteSoon(
|
|
|
|
content::BrowserThread::UI, FROM_HERE, thread);
|
2013-07-24 10:34:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2015-07-08 06:12:11 +00:00
|
|
|
int ShowMessageBox(NativeWindow* parent,
|
2013-07-05 01:59:53 +00:00
|
|
|
MessageBoxType type,
|
|
|
|
const std::vector<std::string>& buttons,
|
2016-01-10 23:33:27 +00:00
|
|
|
int default_id,
|
2015-07-07 10:26:50 +00:00
|
|
|
int cancel_id,
|
2015-07-23 06:16:43 +00:00
|
|
|
int options,
|
2013-07-05 01:59:53 +00:00
|
|
|
const std::string& title,
|
|
|
|
const std::string& message,
|
2015-01-05 23:08:42 +00:00
|
|
|
const std::string& detail,
|
|
|
|
const gfx::ImageSkia& icon) {
|
2016-07-11 06:29:03 +00:00
|
|
|
atom::UnresponsiveSuppressor suppressor;
|
2017-02-06 15:35:36 +00:00
|
|
|
return ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id,
|
|
|
|
options, title, message, detail, "", nullptr, icon);
|
2013-07-05 01:59:53 +00:00
|
|
|
}
|
|
|
|
|
2015-07-08 06:12:11 +00:00
|
|
|
void ShowMessageBox(NativeWindow* parent,
|
2013-09-22 09:11:09 +00:00
|
|
|
MessageBoxType type,
|
|
|
|
const std::vector<std::string>& buttons,
|
2016-01-10 23:33:27 +00:00
|
|
|
int default_id,
|
2015-07-07 10:26:50 +00:00
|
|
|
int cancel_id,
|
2015-07-23 06:16:43 +00:00
|
|
|
int options,
|
2013-09-22 09:11:09 +00:00
|
|
|
const std::string& title,
|
|
|
|
const std::string& message,
|
|
|
|
const std::string& detail,
|
2017-02-06 15:35:36 +00:00
|
|
|
const std::string& checkbox_label,
|
|
|
|
bool checkbox_checked,
|
2015-01-05 23:08:42 +00:00
|
|
|
const gfx::ImageSkia& icon,
|
2013-09-22 09:11:09 +00:00
|
|
|
const MessageBoxCallback& callback) {
|
2016-05-23 01:59:39 +00:00
|
|
|
std::unique_ptr<base::Thread> thread(
|
2015-07-08 06:12:11 +00:00
|
|
|
new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread"));
|
|
|
|
thread->init_com_with_mta(false);
|
|
|
|
if (!thread->Start()) {
|
2017-02-06 15:35:36 +00:00
|
|
|
callback.Run(cancel_id, checkbox_checked);
|
2015-07-08 06:12:11 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
base::Thread* unretained = thread.release();
|
2017-01-26 07:10:28 +00:00
|
|
|
unretained->task_runner()->PostTask(
|
2015-07-08 06:12:11 +00:00
|
|
|
FROM_HERE,
|
|
|
|
base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained),
|
2016-01-11 13:12:07 +00:00
|
|
|
parent, type, buttons, default_id, cancel_id, options, title,
|
2017-02-06 15:35:36 +00:00
|
|
|
message, detail, checkbox_label, checkbox_checked, icon,
|
|
|
|
callback));
|
2013-09-22 09:11:09 +00:00
|
|
|
}
|
|
|
|
|
2014-11-05 08:05:11 +00:00
|
|
|
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
2016-07-11 06:29:03 +00:00
|
|
|
atom::UnresponsiveSuppressor suppressor;
|
2017-02-06 15:35:36 +00:00
|
|
|
ShowTaskDialogUTF16(nullptr, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error",
|
|
|
|
title, content, L"", nullptr, gfx::ImageSkia());
|
2014-11-05 08:05:11 +00:00
|
|
|
}
|
|
|
|
|
2013-07-05 01:59:53 +00:00
|
|
|
} // namespace atom
|