1/*
2 * Copyright (C) 2018 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitScriptDialogImpl.h"
22
23#include "WebKitScriptDialogPrivate.h"
24#include <glib/gi18n-lib.h>
25#include <wtf/glib/WTFGType.h>
26#include <wtf/text/CString.h>
27
28struct _WebKitScriptDialogImplPrivate {
29 WebKitScriptDialog* dialog;
30 GtkWidget* vbox;
31 GtkWidget* swindow;
32 GtkWidget* title;
33 GtkWidget* label;
34 GtkWidget* entry;
35 GtkWidget* actionArea;
36 GtkWidget* defaultButton;
37};
38
39WEBKIT_DEFINE_TYPE(WebKitScriptDialogImpl, webkit_script_dialog_impl, WEBKIT_TYPE_WEB_VIEW_DIALOG)
40
41static void webkitScriptDialogImplClose(WebKitScriptDialogImpl* dialog)
42{
43 webkit_script_dialog_close(dialog->priv->dialog);
44 gtk_widget_destroy(GTK_WIDGET(dialog));
45}
46
47static gboolean webkitScriptDialogImplKeyPressEvent(GtkWidget* widget, GdkEventKey* keyEvent)
48{
49 if (keyEvent->keyval == GDK_KEY_Escape) {
50 webkitScriptDialogImplClose(WEBKIT_SCRIPT_DIALOG_IMPL(widget));
51 return GDK_EVENT_STOP;
52 }
53
54 return GDK_EVENT_PROPAGATE;
55}
56
57static void webkitScriptDialogImplMap(GtkWidget* widget)
58{
59 WebKitScriptDialogImplPrivate* priv = WEBKIT_SCRIPT_DIALOG_IMPL(widget)->priv;
60 gtk_widget_grab_default(priv->defaultButton);
61
62 switch (priv->dialog->type) {
63 case WEBKIT_SCRIPT_DIALOG_ALERT:
64 case WEBKIT_SCRIPT_DIALOG_CONFIRM:
65 case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM:
66 gtk_widget_grab_focus(priv->defaultButton);
67 break;
68 case WEBKIT_SCRIPT_DIALOG_PROMPT:
69 gtk_widget_grab_focus(priv->entry);
70 break;
71 }
72
73 GTK_WIDGET_CLASS(webkit_script_dialog_impl_parent_class)->map(widget);
74}
75
76static void webkitScriptDialogImplConstructed(GObject* object)
77{
78 G_OBJECT_CLASS(webkit_script_dialog_impl_parent_class)->constructed(object);
79
80 auto* dialog = WEBKIT_SCRIPT_DIALOG_IMPL(object);
81 WebKitScriptDialogImplPrivate* priv = dialog->priv;
82
83 priv->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20);
84 gtk_container_set_border_width(GTK_CONTAINER(priv->vbox), 0);
85 gtk_container_add(GTK_CONTAINER(dialog), priv->vbox);
86 gtk_widget_show(priv->vbox);
87
88 GtkWidget* box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
89 gtk_style_context_add_class(gtk_widget_get_style_context(box), GTK_STYLE_CLASS_TITLEBAR);
90 gtk_widget_set_size_request(box, -1, 16);
91 priv->title = gtk_label_new(nullptr);
92 gtk_label_set_ellipsize(GTK_LABEL(priv->title), PANGO_ELLIPSIZE_END);
93 gtk_widget_set_margin_top(priv->title, 6);
94 gtk_widget_set_margin_bottom(priv->title, 6);
95 gtk_style_context_add_class(gtk_widget_get_style_context(priv->title), GTK_STYLE_CLASS_TITLE);
96 gtk_box_set_center_widget(GTK_BOX(box), priv->title);
97 gtk_widget_show(priv->title);
98 gtk_box_pack_start(GTK_BOX(priv->vbox), box, TRUE, FALSE, 0);
99 gtk_widget_show(box);
100
101 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 30);
102 gtk_widget_set_margin_start(box, 30);
103 gtk_widget_set_margin_end(box, 30);
104 gtk_box_pack_start(GTK_BOX(priv->vbox), box, TRUE, FALSE, 0);
105 gtk_widget_show(box);
106
107 GtkWidget* messageArea = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
108 gtk_box_pack_start(GTK_BOX(box), messageArea, TRUE, TRUE, 0);
109 gtk_widget_show(messageArea);
110
111 priv->swindow = gtk_scrolled_window_new(nullptr, nullptr);
112 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(priv->swindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
113 gtk_box_pack_start(GTK_BOX(messageArea), priv->swindow, TRUE, TRUE, 0);
114 gtk_widget_show(priv->swindow);
115
116 priv->label = gtk_label_new(nullptr);
117 gtk_widget_set_halign(priv->label, GTK_ALIGN_CENTER);
118 gtk_widget_set_valign(priv->label, GTK_ALIGN_START);
119 gtk_label_set_line_wrap(GTK_LABEL(priv->label), TRUE);
120 gtk_label_set_max_width_chars(GTK_LABEL(priv->label), 60);
121 gtk_container_add(GTK_CONTAINER(priv->swindow), priv->label);
122 gtk_widget_show(priv->label);
123
124 GtkWidget* actionBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
125 gtk_style_context_add_class(gtk_widget_get_style_context(actionBox), "dialog-action-box");
126 gtk_box_pack_end(GTK_BOX(priv->vbox), actionBox, FALSE, TRUE, 0);
127 gtk_widget_show(actionBox);
128
129 priv->actionArea = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
130 gtk_button_box_set_layout(GTK_BUTTON_BOX(priv->actionArea), GTK_BUTTONBOX_EXPAND);
131 gtk_widget_set_hexpand(priv->actionArea, TRUE);
132 gtk_style_context_add_class(gtk_widget_get_style_context(priv->actionArea), "dialog-action-area");
133 gtk_box_pack_end(GTK_BOX(actionBox), priv->actionArea, FALSE, TRUE, 0);
134 gtk_widget_show(priv->actionArea);
135}
136
137static void webkitScriptDialogImplDispose(GObject* object)
138{
139 auto* dialog = WEBKIT_SCRIPT_DIALOG_IMPL(object);
140 if (dialog->priv->dialog) {
141 dialog->priv->dialog->nativeDialog = nullptr;
142 webkit_script_dialog_unref(dialog->priv->dialog);
143 dialog->priv->dialog = nullptr;
144 }
145
146 G_OBJECT_CLASS(webkit_script_dialog_impl_parent_class)->dispose(object);
147}
148
149static void webkit_script_dialog_impl_class_init(WebKitScriptDialogImplClass* klass)
150{
151 GObjectClass* objectClass = G_OBJECT_CLASS(klass);
152 objectClass->constructed = webkitScriptDialogImplConstructed;
153 objectClass->dispose = webkitScriptDialogImplDispose;
154
155 GtkWidgetClass* widgetClass = GTK_WIDGET_CLASS(klass);
156 widgetClass->key_press_event = webkitScriptDialogImplKeyPressEvent;
157 widgetClass->map = webkitScriptDialogImplMap;
158 gtk_widget_class_set_accessible_role(widgetClass, ATK_ROLE_ALERT);
159}
160
161static void webkitScriptDialogImplSetText(WebKitScriptDialogImpl* dialog, const char* text, GtkRequisition* maxSize)
162{
163 WebKitScriptDialogImplPrivate* priv = dialog->priv;
164 gtk_label_set_text(GTK_LABEL(priv->label), text);
165 GtkRequisition naturalRequisition;
166 gtk_widget_get_preferred_size(priv->label, nullptr, &naturalRequisition);
167 gtk_widget_set_size_request(priv->swindow, std::min(naturalRequisition.width, maxSize->width), std::min(maxSize->height, naturalRequisition.height));
168}
169
170static GtkWidget* webkitScriptDialogImplAddButton(WebKitScriptDialogImpl* dialog, const char* text)
171{
172 WebKitScriptDialogImplPrivate* priv = dialog->priv;
173 GtkWidget* button = gtk_button_new_with_label(text);
174 gtk_button_set_use_underline(GTK_BUTTON(button), TRUE);
175 gtk_style_context_add_class(gtk_widget_get_style_context(button), "text-button");
176 gtk_widget_set_can_default(button, TRUE);
177
178 gtk_widget_set_valign(button, GTK_ALIGN_BASELINE);
179 gtk_container_add(GTK_CONTAINER(priv->actionArea), button);
180 gtk_widget_show(button);
181
182 return button;
183}
184
185GtkWidget* webkitScriptDialogImplNew(WebKitScriptDialog* scriptDialog, const char* title, GtkRequisition* maxSize)
186{
187 auto* dialog = WEBKIT_SCRIPT_DIALOG_IMPL(g_object_new(WEBKIT_TYPE_SCRIPT_DIALOG_IMPL, nullptr));
188 dialog->priv->dialog = webkit_script_dialog_ref(scriptDialog);
189 dialog->priv->dialog->nativeDialog = GTK_WIDGET(dialog);
190
191 switch (scriptDialog->type) {
192 case WEBKIT_SCRIPT_DIALOG_ALERT: {
193 gtk_label_set_text(GTK_LABEL(dialog->priv->title), title);
194
195 GtkWidget* button = webkitScriptDialogImplAddButton(dialog, _("_Close"));
196 dialog->priv->defaultButton = button;
197 g_signal_connect_swapped(button, "clicked", G_CALLBACK(webkitScriptDialogImplCancel), dialog);
198 webkitScriptDialogImplSetText(dialog, scriptDialog->message.data(), maxSize);
199 break;
200 }
201 case WEBKIT_SCRIPT_DIALOG_PROMPT:
202 dialog->priv->entry = gtk_entry_new();
203 gtk_entry_set_text(GTK_ENTRY(dialog->priv->entry), scriptDialog->defaultText.data());
204 gtk_container_add(GTK_CONTAINER(dialog->priv->vbox), dialog->priv->entry);
205 gtk_entry_set_activates_default(GTK_ENTRY(dialog->priv->entry), TRUE);
206 gtk_widget_show(dialog->priv->entry);
207
208 FALLTHROUGH;
209 case WEBKIT_SCRIPT_DIALOG_CONFIRM: {
210 gtk_label_set_text(GTK_LABEL(dialog->priv->title), title);
211
212 GtkWidget* button = webkitScriptDialogImplAddButton(dialog, _("_Cancel"));
213 g_signal_connect_swapped(button, "clicked", G_CALLBACK(webkitScriptDialogImplCancel), dialog);
214 button = webkitScriptDialogImplAddButton(dialog, _("_OK"));
215 dialog->priv->defaultButton = button;
216 g_signal_connect_swapped(button, "clicked", G_CALLBACK(webkitScriptDialogImplConfirm), dialog);
217 webkitScriptDialogImplSetText(dialog, scriptDialog->message.data(), maxSize);
218 break;
219 }
220 case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM: {
221 gtk_label_set_text(GTK_LABEL(dialog->priv->title), _("Are you sure you want to leave this page?"));
222
223 GtkWidget* button = webkitScriptDialogImplAddButton(dialog, _("Stay on Page"));
224 g_signal_connect_swapped(button, "clicked", G_CALLBACK(webkitScriptDialogImplCancel), dialog);
225 button = webkitScriptDialogImplAddButton(dialog, _("Leave Page"));
226 dialog->priv->defaultButton = button;
227 g_signal_connect_swapped(button, "clicked", G_CALLBACK(webkitScriptDialogImplConfirm), dialog);
228 webkitScriptDialogImplSetText(dialog, scriptDialog->message.data(), maxSize);
229 break;
230 }
231 }
232
233 return GTK_WIDGET(dialog);
234}
235
236void webkitScriptDialogImplCancel(WebKitScriptDialogImpl* dialog)
237{
238 switch (dialog->priv->dialog->type) {
239 case WEBKIT_SCRIPT_DIALOG_ALERT:
240 case WEBKIT_SCRIPT_DIALOG_PROMPT:
241 break;
242 case WEBKIT_SCRIPT_DIALOG_CONFIRM:
243 case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM:
244 dialog->priv->dialog->confirmed = false;
245 break;
246 }
247 webkitScriptDialogImplClose(dialog);
248}
249
250void webkitScriptDialogImplConfirm(WebKitScriptDialogImpl* dialog)
251{
252 switch (dialog->priv->dialog->type) {
253 case WEBKIT_SCRIPT_DIALOG_ALERT:
254 break;
255 case WEBKIT_SCRIPT_DIALOG_PROMPT:
256 dialog->priv->dialog->text = gtk_entry_get_text(GTK_ENTRY(dialog->priv->entry));
257 break;
258 case WEBKIT_SCRIPT_DIALOG_CONFIRM:
259 case WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM:
260 dialog->priv->dialog->confirmed = true;
261 break;
262 }
263 webkitScriptDialogImplClose(dialog);
264}
265
266void webkitScriptDialogImplSetEntryText(WebKitScriptDialogImpl* dialog, const String& text)
267{
268 if (dialog->priv->dialog->type != WEBKIT_SCRIPT_DIALOG_PROMPT)
269 return;
270
271 gtk_entry_set_text(GTK_ENTRY(dialog->priv->entry), text.utf8().data());
272}
273