1 // Copyright (c) 2013 GitHub, Inc.
2 // Use of this source code is governed by the MIT license that can be
3 // found in the LICENSE file.
5 #include "atom/browser/ui/message_box.h"
7 #include <windows.h> // windows.h must be included first
14 #include "atom/browser/browser.h"
15 #include "atom/browser/native_window_views.h"
16 #include "atom/browser/unresponsive_suppressor.h"
17 #include "base/callback.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/threading/thread.h"
21 #include "base/win/scoped_gdi_object.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "ui/gfx/icon_util.h"
29 // Small command ID values are already taken by Windows, we have to start from
30 // a large number to avoid conflicts with Windows.
31 const int kIDStart = 100;
33 // Get the common ID from button's name.
34 struct CommonButtonID {
38 CommonButtonID GetCommonID(const base::string16& button) {
39 base::string16 lower = base::ToLowerASCII(button);
41 return { TDCBF_OK_BUTTON, IDOK };
42 else if (lower == L"yes")
43 return { TDCBF_YES_BUTTON, IDYES };
44 else if (lower == L"no")
45 return { TDCBF_NO_BUTTON, IDNO };
46 else if (lower == L"cancel")
47 return { TDCBF_CANCEL_BUTTON, IDCANCEL };
48 else if (lower == L"retry")
49 return { TDCBF_RETRY_BUTTON, IDRETRY };
50 else if (lower == L"close")
51 return { TDCBF_CLOSE_BUTTON, IDCLOSE };
55 // Determine whether the buttons are common buttons, if so map common ID
57 void MapToCommonID(const std::vector<base::string16>& buttons,
58 std::map<int, int>* id_map,
59 TASKDIALOG_COMMON_BUTTON_FLAGS* button_flags,
60 std::vector<TASKDIALOG_BUTTON>* dialog_buttons) {
61 for (size_t i = 0; i < buttons.size(); ++i) {
62 auto common = GetCommonID(buttons[i]);
63 if (common.button != -1) {
64 // It is a common button.
65 (*id_map)[common.id] = i;
66 (*button_flags) |= common.button;
68 // It is a custom button.
69 dialog_buttons->push_back(
70 {static_cast<int>(i + kIDStart), buttons[i].c_str()});
75 int ShowMessageBoxUTF16(HWND parent,
77 const std::vector<base::string16>& buttons,
81 const base::string16& title,
82 const base::string16& message,
83 const base::string16& detail,
84 const gfx::ImageSkia& icon) {
85 TASKDIALOG_FLAGS flags =
86 TDF_SIZE_TO_CONTENT | // Show all content.
87 TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
89 TASKDIALOGCONFIG config = { 0 };
90 config.cbSize = sizeof(config);
91 config.hwndParent = parent;
92 config.hInstance = GetModuleHandle(NULL);
93 config.dwFlags = flags;
96 config.nDefaultButton = kIDStart + default_id;
98 // TaskDialogIndirect doesn't allow empty name, if we set empty title it
99 // will show "electron.exe" in title.
100 base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName());
102 config.pszWindowTitle = app_name.c_str();
104 config.pszWindowTitle = title.c_str();
106 base::win::ScopedHICON hicon;
107 if (!icon.isNull()) {
108 hicon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap());
109 config.dwFlags |= TDF_USE_HICON_MAIN;
110 config.hMainIcon = hicon.get();
112 // Show icon according to dialog's type.
114 case MESSAGE_BOX_TYPE_INFORMATION:
115 case MESSAGE_BOX_TYPE_QUESTION:
116 config.pszMainIcon = TD_INFORMATION_ICON;
118 case MESSAGE_BOX_TYPE_WARNING:
119 config.pszMainIcon = TD_WARNING_ICON;
121 case MESSAGE_BOX_TYPE_ERROR:
122 config.pszMainIcon = TD_ERROR_ICON;
127 // If "detail" is empty then don't make message hilighted.
128 if (detail.empty()) {
129 config.pszContent = message.c_str();
131 config.pszMainInstruction = message.c_str();
132 config.pszContent = detail.c_str();
135 // Iterate through the buttons, put common buttons in dwCommonButtons
136 // and custom buttons in pButtons.
137 std::map<int, int> id_map;
138 std::vector<TASKDIALOG_BUTTON> dialog_buttons;
139 if (options & MESSAGE_BOX_NO_LINK) {
140 for (size_t i = 0; i < buttons.size(); ++i)
141 dialog_buttons.push_back(
142 {static_cast<int>(i + kIDStart), buttons[i].c_str()});
144 MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons);
146 if (dialog_buttons.size() > 0) {
147 config.pButtons = &dialog_buttons.front();
148 config.cButtons = dialog_buttons.size();
149 if (!(options & MESSAGE_BOX_NO_LINK))
150 config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
154 TaskDialogIndirect(&config, &id, NULL, NULL);
155 if (id_map.find(id) != id_map.end()) // common button.
157 else if (id >= kIDStart) // custom button.
158 return id - kIDStart;
163 void RunMessageBoxInNewThread(base::Thread* thread,
164 NativeWindow* parent,
166 const std::vector<std::string>& buttons,
170 const std::string& title,
171 const std::string& message,
172 const std::string& detail,
173 const gfx::ImageSkia& icon,
174 const MessageBoxCallback& callback) {
175 int result = ShowMessageBox(parent, type, buttons, default_id,
176 cancel_id, options, title, message, detail, icon);
177 content::BrowserThread::PostTask(
178 content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result));
179 content::BrowserThread::DeleteSoon(
180 content::BrowserThread::UI, FROM_HERE, thread);
185 int ShowMessageBox(NativeWindow* parent,
187 const std::vector<std::string>& buttons,
191 const std::string& title,
192 const std::string& message,
193 const std::string& detail,
194 const gfx::ImageSkia& icon) {
195 std::vector<base::string16> utf16_buttons;
196 for (const auto& button : buttons)
197 utf16_buttons.push_back(base::UTF8ToUTF16(button));
199 HWND hwnd_parent = parent ?
200 static_cast<atom::NativeWindowViews*>(parent)->GetAcceleratedWidget() :
203 atom::UnresponsiveSuppressor suppressor;
204 return ShowMessageBoxUTF16(hwnd_parent,
210 base::UTF8ToUTF16(title),
211 base::UTF8ToUTF16(message),
212 base::UTF8ToUTF16(detail),
216 void ShowMessageBox(NativeWindow* parent,
218 const std::vector<std::string>& buttons,
222 const std::string& title,
223 const std::string& message,
224 const std::string& detail,
225 const gfx::ImageSkia& icon,
226 const MessageBoxCallback& callback) {
227 std::unique_ptr<base::Thread> thread(
228 new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread"));
229 thread->init_com_with_mta(false);
230 if (!thread->Start()) {
231 callback.Run(cancel_id);
235 base::Thread* unretained = thread.release();
236 unretained->task_runner()->PostTask(
238 base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained),
239 parent, type, buttons, default_id, cancel_id, options, title,
240 message, detail, icon, callback));
243 void ShowErrorBox(const base::string16& title, const base::string16& content) {
244 atom::UnresponsiveSuppressor suppressor;
245 ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error",
246 title, content, gfx::ImageSkia());