1/*
2 * Copyright (C) 2006, 2007 Apple Inc.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2011 Igalia S.L.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
19 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 * THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H && defined(BUILDING_WITH_CMAKE)
29#include "cmakeconfig.h"
30#endif
31#include "BrowserWindow.h"
32
33#include "BrowserDownloadsBar.h"
34#include "BrowserSearchBar.h"
35#include "BrowserSettingsDialog.h"
36#include "BrowserTab.h"
37#include <gdk/gdkkeysyms.h>
38#include <string.h>
39
40struct _BrowserWindow {
41 GtkWindow parent;
42
43 WebKitWebContext *webContext;
44
45 GtkAccelGroup *accelGroup;
46 GtkWidget *mainBox;
47 GtkWidget *toolbar;
48 GtkWidget *uriEntry;
49 GtkWidget *backItem;
50 GtkWidget *forwardItem;
51 GtkWidget *zoomInItem;
52 GtkWidget *zoomOutItem;
53 GtkWidget *boldItem;
54 GtkWidget *italicItem;
55 GtkWidget *underlineItem;
56 GtkWidget *strikethroughItem;
57 GtkWidget *settingsDialog;
58 GtkWidget *notebook;
59 BrowserTab *activeTab;
60 GtkWidget *downloadsBar;
61 gboolean searchBarVisible;
62 gboolean fullScreenIsEnabled;
63 GdkPixbuf *favicon;
64 GtkWidget *reloadOrStopButton;
65 GtkWindow *parentWindow;
66 guint resetEntryProgressTimeoutId;
67 gchar *sessionFile;
68 GdkRGBA backgroundColor;
69};
70
71struct _BrowserWindowClass {
72 GtkWindowClass parent;
73};
74
75static const char *defaultWindowTitle = "WebKitGTK+ MiniBrowser";
76static const gdouble minimumZoomLevel = 0.5;
77static const gdouble maximumZoomLevel = 3;
78static const gdouble defaultZoomLevel = 1;
79static const gdouble zoomStep = 1.2;
80static GList *windowList;
81
82G_DEFINE_TYPE(BrowserWindow, browser_window, GTK_TYPE_WINDOW)
83
84static char *getExternalURI(const char *uri)
85{
86 /* From the user point of view we support about: prefix. */
87 if (uri && g_str_has_prefix(uri, BROWSER_ABOUT_SCHEME))
88 return g_strconcat("about", uri + strlen(BROWSER_ABOUT_SCHEME), NULL);
89
90 return g_strdup(uri);
91}
92
93static void browserWindowSetStatusText(BrowserWindow *window, const char *text)
94{
95 browser_tab_set_status_text(window->activeTab, text);
96}
97
98static void resetStatusText(GtkWidget *widget, BrowserWindow *window)
99{
100 browserWindowSetStatusText(window, NULL);
101}
102
103static void activateUriEntryCallback(BrowserWindow *window)
104{
105 browser_window_load_uri(window, gtk_entry_get_text(GTK_ENTRY(window->uriEntry)));
106}
107
108static void reloadOrStopCallback(BrowserWindow *window)
109{
110 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
111 if (webkit_web_view_is_loading(webView))
112 webkit_web_view_stop_loading(webView);
113 else
114 webkit_web_view_reload(webView);
115}
116
117static void goBackCallback(BrowserWindow *window)
118{
119 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
120 webkit_web_view_go_back(webView);
121}
122
123static void goForwardCallback(BrowserWindow *window)
124{
125 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
126 webkit_web_view_go_forward(webView);
127}
128
129static void settingsCallback(BrowserWindow *window)
130{
131 if (window->settingsDialog) {
132 gtk_window_present(GTK_WINDOW(window->settingsDialog));
133 return;
134 }
135
136 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
137 window->settingsDialog = browser_settings_dialog_new(webkit_web_view_get_settings(webView));
138 gtk_window_set_transient_for(GTK_WINDOW(window->settingsDialog), GTK_WINDOW(window));
139 g_object_add_weak_pointer(G_OBJECT(window->settingsDialog), (gpointer *)&window->settingsDialog);
140 gtk_widget_show(window->settingsDialog);
141}
142
143static void webViewURIChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
144{
145 char *externalURI = getExternalURI(webkit_web_view_get_uri(webView));
146 if (externalURI) {
147 gtk_entry_set_text(GTK_ENTRY(window->uriEntry), externalURI);
148 g_free(externalURI);
149 } else
150 gtk_entry_set_text(GTK_ENTRY(window->uriEntry), "");
151}
152
153static void webViewTitleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
154{
155 const char *title = webkit_web_view_get_title(webView);
156 if (!title)
157 title = defaultWindowTitle;
158 char *privateTitle = NULL;
159 if (webkit_web_view_is_controlled_by_automation(webView))
160 privateTitle = g_strdup_printf("[Automation] %s", title);
161 else if (webkit_web_view_is_ephemeral(webView))
162 privateTitle = g_strdup_printf("[Private] %s", title);
163 gtk_window_set_title(GTK_WINDOW(window), privateTitle ? privateTitle : title);
164 g_free(privateTitle);
165}
166
167static gboolean resetEntryProgress(BrowserWindow *window)
168{
169 gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), 0);
170 window->resetEntryProgressTimeoutId = 0;
171 return FALSE;
172}
173
174static void webViewLoadProgressChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
175{
176 gdouble progress = webkit_web_view_get_estimated_load_progress(webView);
177 gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), progress);
178 if (progress == 1.0) {
179 window->resetEntryProgressTimeoutId = g_timeout_add(500, (GSourceFunc)resetEntryProgress, window);
180 g_source_set_name_by_id(window->resetEntryProgressTimeoutId, "[WebKit] resetEntryProgress");
181 } else if (window->resetEntryProgressTimeoutId) {
182 g_source_remove(window->resetEntryProgressTimeoutId);
183 window->resetEntryProgressTimeoutId = 0;
184 }
185}
186
187static void downloadStarted(WebKitWebContext *webContext, WebKitDownload *download, BrowserWindow *window)
188{
189 if (!window->downloadsBar) {
190 window->downloadsBar = browser_downloads_bar_new();
191 gtk_box_pack_start(GTK_BOX(window->mainBox), window->downloadsBar, FALSE, FALSE, 0);
192 gtk_box_reorder_child(GTK_BOX(window->mainBox), window->downloadsBar, 0);
193 g_object_add_weak_pointer(G_OBJECT(window->downloadsBar), (gpointer *)&(window->downloadsBar));
194 gtk_widget_show(window->downloadsBar);
195 }
196 browser_downloads_bar_add_download(BROWSER_DOWNLOADS_BAR(window->downloadsBar), download);
197}
198
199static void browserWindowHistoryItemSelected(BrowserWindow *window, GtkMenuItem *item)
200{
201 GtkAction *action = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item));
202 browserWindowSetStatusText(window, action ? gtk_action_get_name(action) : NULL);
203}
204
205static void browserWindowHistoryItemActivated(BrowserWindow *window, GtkAction *action)
206{
207 WebKitBackForwardListItem *item = g_object_get_data(G_OBJECT(action), "back-forward-list-item");
208 if (!item)
209 return;
210
211 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
212 webkit_web_view_go_to_back_forward_list_item(webView, item);
213}
214
215static GtkWidget *browserWindowCreateBackForwardMenu(BrowserWindow *window, GList *list)
216{
217 if (!list)
218 return NULL;
219
220 GtkWidget *menu = gtk_menu_new();
221 GList *listItem;
222 for (listItem = list; listItem; listItem = g_list_next(listItem)) {
223 WebKitBackForwardListItem *item = (WebKitBackForwardListItem *)listItem->data;
224 const char *uri = webkit_back_forward_list_item_get_uri(item);
225 const char *title = webkit_back_forward_list_item_get_title(item);
226
227 GtkAction *action = gtk_action_new(uri, title, NULL, NULL);
228 g_object_set_data_full(G_OBJECT(action), "back-forward-list-item", g_object_ref(item), g_object_unref);
229 g_signal_connect_swapped(action, "activate", G_CALLBACK(browserWindowHistoryItemActivated), window);
230
231 GtkWidget *menuItem = gtk_action_create_menu_item(action);
232 g_signal_connect_swapped(menuItem, "select", G_CALLBACK(browserWindowHistoryItemSelected), window);
233 g_object_unref(action);
234
235 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuItem);
236 gtk_widget_show(menuItem);
237 }
238
239 g_signal_connect(menu, "hide", G_CALLBACK(resetStatusText), window);
240
241 return menu;
242}
243
244static void browserWindowUpdateNavigationActions(BrowserWindow *window, WebKitBackForwardList *backForwardlist)
245{
246 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
247 gtk_widget_set_sensitive(window->backItem, webkit_web_view_can_go_back(webView));
248 gtk_widget_set_sensitive(window->forwardItem, webkit_web_view_can_go_forward(webView));
249
250 GList *list = g_list_reverse(webkit_back_forward_list_get_back_list_with_limit(backForwardlist, 10));
251 gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->backItem),
252 browserWindowCreateBackForwardMenu(window, list));
253 g_list_free(list);
254
255 list = webkit_back_forward_list_get_forward_list_with_limit(backForwardlist, 10);
256 gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->forwardItem),
257 browserWindowCreateBackForwardMenu(window, list));
258 g_list_free(list);
259}
260
261static void browserWindowTryCloseCurrentWebView(BrowserWindow *window)
262{
263 int currentPage = gtk_notebook_get_current_page(GTK_NOTEBOOK(window->notebook));
264 BrowserTab *tab = (BrowserTab *)gtk_notebook_get_nth_page(GTK_NOTEBOOK(window->notebook), currentPage);
265 webkit_web_view_try_close(browser_tab_get_web_view(tab));
266}
267
268static void browserWindowTryClose(BrowserWindow *window)
269{
270 GSList *webViews = NULL;
271 int n = gtk_notebook_get_n_pages(GTK_NOTEBOOK(window->notebook));
272 int i;
273
274 for (i = 0; i < n; ++i) {
275 BrowserTab *tab = (BrowserTab *)gtk_notebook_get_nth_page(GTK_NOTEBOOK(window->notebook), i);
276 webViews = g_slist_prepend(webViews, browser_tab_get_web_view(tab));
277 }
278
279 GSList *link;
280 for (link = webViews; link; link = link->next)
281 webkit_web_view_try_close(link->data);
282}
283
284static void backForwardlistChanged(WebKitBackForwardList *backForwardlist, WebKitBackForwardListItem *itemAdded, GList *itemsRemoved, BrowserWindow *window)
285{
286 browserWindowUpdateNavigationActions(window, backForwardlist);
287}
288
289static void webViewClose(WebKitWebView *webView, BrowserWindow *window)
290{
291 int tabsCount = gtk_notebook_get_n_pages(GTK_NOTEBOOK(window->notebook));
292 if (tabsCount == 1) {
293 gtk_widget_destroy(GTK_WIDGET(window));
294 return;
295 }
296
297 int i;
298 for (i = 0; i < tabsCount; ++i) {
299 BrowserTab *tab = (BrowserTab *)gtk_notebook_get_nth_page(GTK_NOTEBOOK(window->notebook), i);
300 if (browser_tab_get_web_view(tab) == webView) {
301 gtk_widget_destroy(GTK_WIDGET(tab));
302 return;
303 }
304 }
305}
306
307static void webViewRunAsModal(WebKitWebView *webView, BrowserWindow *window)
308{
309 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
310 gtk_window_set_transient_for(GTK_WINDOW(window), window->parentWindow);
311}
312
313static void webViewReadyToShow(WebKitWebView *webView, BrowserWindow *window)
314{
315 WebKitWindowProperties *windowProperties = webkit_web_view_get_window_properties(webView);
316
317 GdkRectangle geometry;
318 webkit_window_properties_get_geometry(windowProperties, &geometry);
319 if (geometry.x >= 0 && geometry.y >= 0)
320 gtk_window_move(GTK_WINDOW(window), geometry.x, geometry.y);
321 if (geometry.width > 0 && geometry.height > 0)
322 gtk_window_resize(GTK_WINDOW(window), geometry.width, geometry.height);
323
324 if (!webkit_window_properties_get_toolbar_visible(windowProperties))
325 gtk_widget_hide(window->toolbar);
326 else if (!webkit_window_properties_get_locationbar_visible(windowProperties))
327 gtk_widget_hide(window->uriEntry);
328
329 if (!webkit_window_properties_get_resizable(windowProperties))
330 gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
331
332 gtk_widget_show(GTK_WIDGET(window));
333}
334
335static GtkWidget *webViewCreate(WebKitWebView *webView, WebKitNavigationAction *navigation, BrowserWindow *window)
336{
337 WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webView));
338 webkit_web_view_set_settings(newWebView, webkit_web_view_get_settings(webView));
339
340 GtkWidget *newWindow = browser_window_new(GTK_WINDOW(window), window->webContext);
341 browser_window_append_view(BROWSER_WINDOW(newWindow), newWebView);
342 gtk_widget_grab_focus(GTK_WIDGET(newWebView));
343 g_signal_connect(newWebView, "ready-to-show", G_CALLBACK(webViewReadyToShow), newWindow);
344 g_signal_connect(newWebView, "run-as-modal", G_CALLBACK(webViewRunAsModal), newWindow);
345 g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), newWindow);
346 return GTK_WIDGET(newWebView);
347}
348
349static gboolean webViewEnterFullScreen(WebKitWebView *webView, BrowserWindow *window)
350{
351 gtk_widget_hide(window->toolbar);
352 browser_tab_enter_fullscreen(window->activeTab);
353 return FALSE;
354}
355
356static gboolean webViewLeaveFullScreen(WebKitWebView *webView, BrowserWindow *window)
357{
358 browser_tab_leave_fullscreen(window->activeTab);
359 gtk_widget_show(window->toolbar);
360 return FALSE;
361}
362
363static gboolean webViewLoadFailed(WebKitWebView *webView, WebKitLoadEvent loadEvent, const char *failingURI, GError *error, BrowserWindow *window)
364{
365 gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), 0.);
366 return FALSE;
367}
368
369static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserWindow *window)
370{
371 if (decisionType != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
372 return FALSE;
373
374 WebKitNavigationAction *navigationAction = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
375 if (webkit_navigation_action_get_navigation_type(navigationAction) != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
376 || webkit_navigation_action_get_mouse_button(navigationAction) != GDK_BUTTON_MIDDLE)
377 return FALSE;
378
379 /* Multiple tabs are not allowed in editor mode. */
380 if (webkit_web_view_is_editable(webView))
381 return FALSE;
382
383 /* Opening a new tab if link clicked with the middle button. */
384 WebKitWebView *newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
385 "web-context", webkit_web_view_get_context(webView),
386 "settings", webkit_web_view_get_settings(webView),
387 "user-content-manager", webkit_web_view_get_user_content_manager(webView),
388 "is-controlled-by-automation", webkit_web_view_is_controlled_by_automation(webView),
389 NULL));
390 browser_window_append_view(window, newWebView);
391 webkit_web_view_load_request(newWebView, webkit_navigation_action_get_request(navigationAction));
392
393 webkit_policy_decision_ignore(decision);
394 return TRUE;
395}
396
397static void webViewMouseTargetChanged(WebKitWebView *webView, WebKitHitTestResult *hitTestResult, guint mouseModifiers, BrowserWindow *window)
398{
399 if (!webkit_hit_test_result_context_is_link(hitTestResult)) {
400 browserWindowSetStatusText(window, NULL);
401 return;
402 }
403 browserWindowSetStatusText(window, webkit_hit_test_result_get_link_uri(hitTestResult));
404}
405
406static gboolean browserWindowCanZoomIn(BrowserWindow *window)
407{
408 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
409 gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) * zoomStep;
410 return zoomLevel < maximumZoomLevel;
411}
412
413static gboolean browserWindowCanZoomOut(BrowserWindow *window)
414{
415 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
416 gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) / zoomStep;
417 return zoomLevel > minimumZoomLevel;
418}
419
420static gboolean browserWindowZoomIn(BrowserWindow *window)
421{
422 if (browserWindowCanZoomIn(window)) {
423 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
424 gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) * zoomStep;
425 webkit_web_view_set_zoom_level(webView, zoomLevel);
426 return TRUE;
427 }
428 return FALSE;
429}
430
431static gboolean browserWindowZoomOut(BrowserWindow *window)
432{
433 if (browserWindowCanZoomOut(window)) {
434 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
435 gdouble zoomLevel = webkit_web_view_get_zoom_level(webView) / zoomStep;
436 webkit_web_view_set_zoom_level(webView, zoomLevel);
437 return TRUE;
438 }
439 return FALSE;
440}
441
442static gboolean scrollEventCallback(WebKitWebView *webView, const GdkEventScroll *event, BrowserWindow *window)
443{
444 GdkModifierType mod = gtk_accelerator_get_default_mod_mask();
445
446 if ((event->state & mod) != GDK_CONTROL_MASK)
447 return FALSE;
448
449 if (event->delta_y < 0)
450 return browserWindowZoomIn(window);
451
452 return browserWindowZoomOut(window);
453}
454
455static void browserWindowUpdateZoomActions(BrowserWindow *window)
456{
457 gtk_widget_set_sensitive(window->zoomInItem, browserWindowCanZoomIn(window));
458 gtk_widget_set_sensitive(window->zoomOutItem, browserWindowCanZoomOut(window));
459}
460
461static void webViewZoomLevelChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
462{
463 browserWindowUpdateZoomActions(window);
464}
465
466static void updateUriEntryIcon(BrowserWindow *window)
467{
468 GtkEntry *entry = GTK_ENTRY(window->uriEntry);
469 if (window->favicon)
470 gtk_entry_set_icon_from_pixbuf(entry, GTK_ENTRY_ICON_PRIMARY, window->favicon);
471 else
472 gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_NEW);
473}
474
475static void faviconChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserWindow *window)
476{
477 GdkPixbuf *favicon = NULL;
478 cairo_surface_t *surface = webkit_web_view_get_favicon(webView);
479
480 if (surface) {
481 int width = cairo_image_surface_get_width(surface);
482 int height = cairo_image_surface_get_height(surface);
483 favicon = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
484 }
485
486 if (window->favicon)
487 g_object_unref(window->favicon);
488 window->favicon = favicon;
489
490 updateUriEntryIcon(window);
491}
492
493static void webViewIsLoadingChanged(WebKitWebView *webView, GParamSpec *paramSpec, BrowserWindow *window)
494{
495 gboolean isLoading = webkit_web_view_is_loading(webView);
496 gtk_tool_button_set_label(GTK_TOOL_BUTTON(window->reloadOrStopButton), isLoading ? "Stop" : "Reload");
497 gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(window->reloadOrStopButton), isLoading ? GTK_STOCK_STOP : GTK_STOCK_REFRESH);
498}
499
500static void zoomInCallback(BrowserWindow *window)
501{
502 browserWindowZoomIn(window);
503}
504
505static void zoomOutCallback(BrowserWindow *window)
506{
507 browserWindowZoomOut(window);
508}
509
510static void defaultZoomCallback(BrowserWindow *window)
511{
512 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
513 webkit_web_view_set_zoom_level(webView, defaultZoomLevel);
514}
515
516static void searchCallback(BrowserWindow *window)
517{
518 browser_tab_start_search(window->activeTab);
519}
520
521static void newTabCallback(BrowserWindow *window)
522{
523 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
524 if (webkit_web_view_is_editable(webView))
525 return;
526
527 browser_window_append_view(window, WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
528 "web-context", webkit_web_view_get_context(webView),
529 "settings", webkit_web_view_get_settings(webView),
530 "user-content-manager", webkit_web_view_get_user_content_manager(webView),
531 "is-controlled-by-automation", webkit_web_view_is_controlled_by_automation(webView),
532 NULL)));
533 gtk_widget_grab_focus(window->uriEntry);
534 gtk_notebook_set_current_page(GTK_NOTEBOOK(window->notebook), -1);
535}
536
537static void toggleWebInspector(BrowserWindow *window)
538{
539 browser_tab_toggle_inspector(window->activeTab);
540}
541
542static void openPrivateWindow(BrowserWindow *window)
543{
544 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
545 WebKitWebView *newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
546 "web-context", webkit_web_view_get_context(webView),
547 "settings", webkit_web_view_get_settings(webView),
548 "user-content-manager", webkit_web_view_get_user_content_manager(webView),
549 "is-ephemeral", TRUE,
550 "is-controlled-by-automation", webkit_web_view_is_controlled_by_automation(webView),
551 NULL));
552 GtkWidget *newWindow = browser_window_new(GTK_WINDOW(window), window->webContext);
553 browser_window_append_view(BROWSER_WINDOW(newWindow), newWebView);
554 gtk_widget_grab_focus(GTK_WIDGET(newWebView));
555 gtk_widget_show(GTK_WIDGET(newWindow));
556}
557
558static void reloadPage(BrowserWindow *window)
559{
560 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
561 webkit_web_view_reload(webView);
562}
563
564static void reloadPageIgnoringCache(BrowserWindow *window)
565{
566 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
567 webkit_web_view_reload_bypass_cache(webView);
568}
569
570static void stopPageLoad(BrowserWindow *window)
571{
572 browser_tab_stop_search(window->activeTab);
573 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
574 if (webkit_web_view_is_loading(webView))
575 webkit_web_view_stop_loading(webView);
576}
577
578static void loadHomePage(BrowserWindow *window)
579{
580 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
581 webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL);
582}
583
584static gboolean toggleFullScreen(BrowserWindow *window, gpointer user_data)
585{
586 if (!window->fullScreenIsEnabled) {
587 gtk_window_fullscreen(GTK_WINDOW(window));
588 gtk_widget_hide(window->toolbar);
589 window->fullScreenIsEnabled = TRUE;
590 } else {
591 gtk_window_unfullscreen(GTK_WINDOW(window));
592 gtk_widget_show(window->toolbar);
593 window->fullScreenIsEnabled = FALSE;
594 }
595 return TRUE;
596}
597
598static void webKitPrintOperationFailedCallback(WebKitPrintOperation *printOperation, GError *error)
599{
600 g_warning("Print failed: '%s'", error->message);
601}
602
603static gboolean printPage(BrowserWindow *window, gpointer user_data)
604{
605 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
606 WebKitPrintOperation *printOperation = webkit_print_operation_new(webView);
607
608 g_signal_connect(printOperation, "failed", G_CALLBACK(webKitPrintOperationFailedCallback), NULL);
609 webkit_print_operation_run_dialog(printOperation, GTK_WINDOW(window));
610 g_object_unref(printOperation);
611
612 return TRUE;
613}
614
615static void editingCommandCallback(GtkWidget *widget, BrowserWindow *window)
616{
617 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
618 webkit_web_view_execute_editing_command(webView, gtk_widget_get_name(widget));
619}
620
621static void insertImageCommandCallback(GtkWidget *widget, BrowserWindow *window)
622{
623 GtkWidget *fileChooser = gtk_file_chooser_dialog_new("Insert Image", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN,
624 "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
625
626 GtkFileFilter *filter = gtk_file_filter_new();
627 gtk_file_filter_set_name(filter, "Images");
628 gtk_file_filter_add_pixbuf_formats(filter);
629 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fileChooser), filter);
630
631 if (gtk_dialog_run(GTK_DIALOG(fileChooser)) == GTK_RESPONSE_ACCEPT) {
632 char *uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(fileChooser));
633 if (uri) {
634 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
635 webkit_web_view_execute_editing_command_with_argument(webView, WEBKIT_EDITING_COMMAND_INSERT_IMAGE, uri);
636 g_free(uri);
637 }
638 }
639
640 gtk_widget_destroy(fileChooser);
641}
642
643static void insertLinkCommandCallback(GtkWidget *widget, BrowserWindow *window)
644{
645 GtkWidget *dialog = gtk_dialog_new_with_buttons("Insert Link", GTK_WINDOW(window), GTK_DIALOG_MODAL, "Insert", GTK_RESPONSE_ACCEPT, NULL);
646 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
647 GtkWidget *entry = gtk_entry_new();
648 gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "URL");
649 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
650 gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), entry);
651 gtk_widget_show(entry);
652
653 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
654 const char *url = gtk_entry_get_text(GTK_ENTRY(entry));
655 if (url && *url) {
656 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
657 webkit_web_view_execute_editing_command_with_argument(webView, WEBKIT_EDITING_COMMAND_CREATE_LINK, url);
658 }
659 }
660
661 gtk_widget_destroy(dialog);
662}
663
664static void browserWindowEditingCommandToggleButtonSetActive(BrowserWindow *window, GtkWidget *button, gboolean active)
665{
666 g_signal_handlers_block_by_func(button, G_CALLBACK(editingCommandCallback), window);
667 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(button), active);
668 g_signal_handlers_unblock_by_func(button, G_CALLBACK(editingCommandCallback), window);
669}
670
671static void typingAttributesChanged(WebKitEditorState *editorState, GParamSpec *spec, BrowserWindow *window)
672{
673 unsigned typingAttributes = webkit_editor_state_get_typing_attributes(editorState);
674 browserWindowEditingCommandToggleButtonSetActive(window, window->boldItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_BOLD);
675 browserWindowEditingCommandToggleButtonSetActive(window, window->italicItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_ITALIC);
676 browserWindowEditingCommandToggleButtonSetActive(window, window->underlineItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_UNDERLINE);
677 browserWindowEditingCommandToggleButtonSetActive(window, window->strikethroughItem, typingAttributes & WEBKIT_EDITOR_TYPING_ATTRIBUTE_STRIKETHROUGH);
678}
679
680static void browserWindowFinalize(GObject *gObject)
681{
682 BrowserWindow *window = BROWSER_WINDOW(gObject);
683
684 g_signal_handlers_disconnect_matched(window->webContext, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, window);
685
686 if (window->favicon) {
687 g_object_unref(window->favicon);
688 window->favicon = NULL;
689 }
690
691 if (window->accelGroup) {
692 g_object_unref(window->accelGroup);
693 window->accelGroup = NULL;
694 }
695
696 if (window->resetEntryProgressTimeoutId)
697 g_source_remove(window->resetEntryProgressTimeoutId);
698
699 g_free(window->sessionFile);
700
701 windowList = g_list_remove(windowList, window);
702
703 G_OBJECT_CLASS(browser_window_parent_class)->finalize(gObject);
704
705 if (!windowList)
706 gtk_main_quit();
707}
708
709static void browserWindowSetupEditorToolbar(BrowserWindow *window)
710{
711 GtkWidget *toolbar = gtk_toolbar_new();
712 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
713 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
714
715 GtkToolItem *item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_BOLD);
716 window->boldItem = GTK_WIDGET(item);
717 gtk_widget_set_name(GTK_WIDGET(item), "Bold");
718 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Bold");
719 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
720 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
721 gtk_widget_show(GTK_WIDGET(item));
722
723 item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_ITALIC);
724 window->italicItem = GTK_WIDGET(item);
725 gtk_widget_set_name(GTK_WIDGET(item), "Italic");
726 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Italic");
727 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
728 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
729 gtk_widget_show(GTK_WIDGET(item));
730
731 item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_UNDERLINE);
732 window->underlineItem = GTK_WIDGET(item);
733 gtk_widget_set_name(GTK_WIDGET(item), "Underline");
734 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Underline");
735 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
736 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
737 gtk_widget_show(GTK_WIDGET(item));
738
739 item = gtk_toggle_tool_button_new_from_stock(GTK_STOCK_STRIKETHROUGH);
740 gtk_widget_set_name(GTK_WIDGET(item), "Strikethrough");
741 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Strikethrough");
742 window->strikethroughItem = GTK_WIDGET(item);
743 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
744 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
745 gtk_widget_show(GTK_WIDGET(item));
746
747 item = gtk_separator_tool_item_new();
748 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
749 gtk_widget_show(GTK_WIDGET(item));
750
751 item = gtk_tool_button_new_from_stock(GTK_STOCK_CUT);
752 gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_CUT);
753 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), WEBKIT_EDITING_COMMAND_CUT);
754 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
755 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
756 gtk_widget_show(GTK_WIDGET(item));
757
758 item = gtk_tool_button_new_from_stock(GTK_STOCK_COPY);
759 gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_COPY);
760 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), WEBKIT_EDITING_COMMAND_COPY);
761 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
762 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
763 gtk_widget_show(GTK_WIDGET(item));
764
765 item = gtk_tool_button_new_from_stock(GTK_STOCK_PASTE);
766 gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_PASTE);
767 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), WEBKIT_EDITING_COMMAND_PASTE);
768 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
769 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
770 gtk_widget_show(GTK_WIDGET(item));
771
772 item = gtk_separator_tool_item_new();
773 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
774 gtk_widget_show(GTK_WIDGET(item));
775
776 item = gtk_tool_button_new_from_stock(GTK_STOCK_UNDO);
777 gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_UNDO);
778 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), WEBKIT_EDITING_COMMAND_UNDO);
779 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
780 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
781 gtk_widget_show(GTK_WIDGET(item));
782
783 item = gtk_tool_button_new_from_stock(GTK_STOCK_REDO);
784 gtk_widget_set_name(GTK_WIDGET(item), WEBKIT_EDITING_COMMAND_REDO);
785 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), WEBKIT_EDITING_COMMAND_REDO);
786 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
787 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
788 gtk_widget_show(GTK_WIDGET(item));
789
790 item = gtk_separator_tool_item_new();
791 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
792 gtk_widget_show(GTK_WIDGET(item));
793
794 item = gtk_radio_tool_button_new_from_stock(NULL, GTK_STOCK_JUSTIFY_LEFT);
795 GSList *justifyRadioGroup = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(item));
796 gtk_widget_set_name(GTK_WIDGET(item), "JustifyLeft");
797 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Justify Left");
798 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
799 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
800 gtk_widget_show(GTK_WIDGET(item));
801
802 item = gtk_radio_tool_button_new_from_stock(justifyRadioGroup, GTK_STOCK_JUSTIFY_CENTER);
803 justifyRadioGroup = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(item));
804 gtk_widget_set_name(GTK_WIDGET(item), "JustifyCenter");
805 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Justify Center");
806 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
807 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
808 gtk_widget_show(GTK_WIDGET(item));
809
810 item = gtk_radio_tool_button_new_from_stock(justifyRadioGroup, GTK_STOCK_JUSTIFY_RIGHT);
811 gtk_widget_set_name(GTK_WIDGET(item), "JustifyRight");
812 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Justify Right");
813 g_signal_connect(G_OBJECT(item), "toggled", G_CALLBACK(editingCommandCallback), window);
814 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
815 gtk_widget_show(GTK_WIDGET(item));
816
817 item = gtk_separator_tool_item_new();
818 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
819 gtk_widget_show(GTK_WIDGET(item));
820
821 item = gtk_tool_button_new_from_stock(GTK_STOCK_INDENT);
822 gtk_widget_set_name(GTK_WIDGET(item), "Indent");
823 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Indent");
824 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
825 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
826 gtk_widget_show(GTK_WIDGET(item));
827
828 item = gtk_tool_button_new_from_stock(GTK_STOCK_UNINDENT);
829 gtk_widget_set_name(GTK_WIDGET(item), "Outdent");
830 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Outdent");
831 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(editingCommandCallback), window);
832 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
833 gtk_widget_show(GTK_WIDGET(item));
834
835 item = gtk_separator_tool_item_new();
836 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
837 gtk_widget_show(GTK_WIDGET(item));
838
839 item = gtk_tool_button_new(NULL, NULL);
840 gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "insert-image");
841 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Insert Image");
842 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(insertImageCommandCallback), window);
843 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
844 gtk_widget_show(GTK_WIDGET(item));
845
846 item = gtk_tool_button_new(NULL, NULL);
847 gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "insert-link");
848 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Insert Link");
849 g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(insertLinkCommandCallback), window);
850 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
851 gtk_widget_show(GTK_WIDGET(item));
852
853 gtk_box_pack_start(GTK_BOX(window->mainBox), toolbar, FALSE, FALSE, 0);
854 gtk_box_reorder_child(GTK_BOX(window->mainBox), toolbar, 1);
855 gtk_widget_show(toolbar);
856}
857
858static void browserWindowSwitchTab(GtkNotebook *notebook, BrowserTab *tab, guint tabIndex, BrowserWindow *window)
859{
860 if (window->activeTab == tab)
861 return;
862
863 if (window->activeTab) {
864 browser_tab_set_status_text(window->activeTab, NULL);
865 g_clear_object(&window->favicon);
866
867 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
868 g_signal_handlers_disconnect_by_data(webView, window);
869
870 /* We always want close to be connected even for not active tabs */
871 g_signal_connect(webView, "close", G_CALLBACK(webViewClose), window);
872
873 WebKitBackForwardList *backForwardlist = webkit_web_view_get_back_forward_list(webView);
874 g_signal_handlers_disconnect_by_data(backForwardlist, window);
875 }
876
877 window->activeTab = tab;
878
879 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
880 if (webkit_web_view_is_editable(webView)) {
881 browserWindowSetupEditorToolbar(window);
882 g_signal_connect(webkit_web_view_get_editor_state(webView), "notify::typing-attributes", G_CALLBACK(typingAttributesChanged), window);
883 }
884 webViewURIChanged(webView, NULL, window);
885 webViewTitleChanged(webView, NULL, window);
886 webViewIsLoadingChanged(webView, NULL, window);
887 faviconChanged(webView, NULL, window);
888 browserWindowUpdateZoomActions(window);
889 if (webkit_web_view_is_loading(webView))
890 webViewLoadProgressChanged(webView, NULL, window);
891
892 g_signal_connect(webView, "notify::uri", G_CALLBACK(webViewURIChanged), window);
893 g_signal_connect(webView, "notify::estimated-load-progress", G_CALLBACK(webViewLoadProgressChanged), window);
894 g_signal_connect(webView, "notify::title", G_CALLBACK(webViewTitleChanged), window);
895 g_signal_connect(webView, "notify::is-loading", G_CALLBACK(webViewIsLoadingChanged), window);
896 g_signal_connect(webView, "create", G_CALLBACK(webViewCreate), window);
897 g_signal_connect(webView, "close", G_CALLBACK(webViewClose), window);
898 g_signal_connect(webView, "load-failed", G_CALLBACK(webViewLoadFailed), window);
899 g_signal_connect(webView, "decide-policy", G_CALLBACK(webViewDecidePolicy), window);
900 g_signal_connect(webView, "mouse-target-changed", G_CALLBACK(webViewMouseTargetChanged), window);
901 g_signal_connect(webView, "notify::zoom-level", G_CALLBACK(webViewZoomLevelChanged), window);
902 g_signal_connect(webView, "notify::favicon", G_CALLBACK(faviconChanged), window);
903 g_signal_connect(webView, "enter-fullscreen", G_CALLBACK(webViewEnterFullScreen), window);
904 g_signal_connect(webView, "leave-fullscreen", G_CALLBACK(webViewLeaveFullScreen), window);
905 g_signal_connect(webView, "scroll-event", G_CALLBACK(scrollEventCallback), window);
906
907 WebKitBackForwardList *backForwardlist = webkit_web_view_get_back_forward_list(webView);
908 browserWindowUpdateNavigationActions(window, backForwardlist);
909 g_signal_connect(backForwardlist, "changed", G_CALLBACK(backForwardlistChanged), window);
910}
911
912static void browserWindowTabAddedOrRemoved(GtkNotebook *notebook, BrowserTab *tab, guint tabIndex, BrowserWindow *window)
913{
914 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(window->notebook), gtk_notebook_get_n_pages(notebook) > 1);
915}
916
917static void browser_window_init(BrowserWindow *window)
918{
919 windowList = g_list_append(windowList, window);
920
921 window->backgroundColor.red = window->backgroundColor.green = window->backgroundColor.blue = 255;
922 window->backgroundColor.alpha = 1;
923
924 gtk_window_set_title(GTK_WINDOW(window), defaultWindowTitle);
925 gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
926
927 window->uriEntry = gtk_entry_new();
928 g_signal_connect_swapped(window->uriEntry, "activate", G_CALLBACK(activateUriEntryCallback), (gpointer)window);
929 gtk_entry_set_icon_activatable(GTK_ENTRY(window->uriEntry), GTK_ENTRY_ICON_PRIMARY, FALSE);
930 updateUriEntryIcon(window);
931
932 /* Keyboard accelerators */
933 window->accelGroup = gtk_accel_group_new();
934 gtk_window_add_accel_group(GTK_WINDOW(window), window->accelGroup);
935
936 /* Global accelerators */
937 gtk_accel_group_connect(window->accelGroup, GDK_KEY_I, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE,
938 g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL));
939 gtk_accel_group_connect(window->accelGroup, GDK_KEY_F12, 0, GTK_ACCEL_VISIBLE,
940 g_cclosure_new_swap(G_CALLBACK(toggleWebInspector), window, NULL));
941 gtk_accel_group_connect(window->accelGroup, GDK_KEY_P, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE,
942 g_cclosure_new_swap(G_CALLBACK(openPrivateWindow), window, NULL));
943
944 /* Reload page */
945 gtk_accel_group_connect(window->accelGroup, GDK_KEY_F5, 0, GTK_ACCEL_VISIBLE,
946 g_cclosure_new_swap(G_CALLBACK(reloadPage), window, NULL));
947 gtk_accel_group_connect(window->accelGroup, GDK_KEY_R, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
948 g_cclosure_new_swap(G_CALLBACK(reloadPage), window, NULL));
949
950 /* Reload page ignoring cache */
951 gtk_accel_group_connect(window->accelGroup, GDK_KEY_F5, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
952 g_cclosure_new_swap(G_CALLBACK(reloadPageIgnoringCache), window, NULL));
953 gtk_accel_group_connect(window->accelGroup, GDK_KEY_R, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE,
954 g_cclosure_new_swap(G_CALLBACK(reloadPageIgnoringCache), window, NULL));
955
956 /* Stop page load */
957 gtk_accel_group_connect(window->accelGroup, GDK_KEY_F6, 0, GTK_ACCEL_VISIBLE,
958 g_cclosure_new_swap(G_CALLBACK(stopPageLoad), window, NULL));
959 gtk_accel_group_connect(window->accelGroup, GDK_KEY_Escape, 0, GTK_ACCEL_VISIBLE,
960 g_cclosure_new_swap(G_CALLBACK(stopPageLoad), window, NULL));
961
962 /* Load home page */
963 gtk_accel_group_connect(window->accelGroup, GDK_KEY_Home, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE,
964 g_cclosure_new_swap(G_CALLBACK(loadHomePage), window, NULL));
965
966 /* Zoom in, zoom out and default zoom*/
967 gtk_accel_group_connect(window->accelGroup, GDK_KEY_equal, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
968 g_cclosure_new_swap(G_CALLBACK(zoomInCallback), window, NULL));
969 gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_Add, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
970 g_cclosure_new_swap(G_CALLBACK(zoomInCallback), window, NULL));
971 gtk_accel_group_connect(window->accelGroup, GDK_KEY_minus, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
972 g_cclosure_new_swap(G_CALLBACK(zoomOutCallback), window, NULL));
973 gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
974 g_cclosure_new_swap(G_CALLBACK(zoomOutCallback), window, NULL));
975 gtk_accel_group_connect(window->accelGroup, GDK_KEY_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
976 g_cclosure_new_swap(G_CALLBACK(defaultZoomCallback), window, NULL));
977 gtk_accel_group_connect(window->accelGroup, GDK_KEY_KP_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
978 g_cclosure_new_swap(G_CALLBACK(defaultZoomCallback), window, NULL));
979
980 /* Toggle fullscreen */
981 gtk_accel_group_connect(window->accelGroup, GDK_KEY_F11, 0, GTK_ACCEL_VISIBLE,
982 g_cclosure_new_swap(G_CALLBACK(toggleFullScreen), window, NULL));
983
984 /* Quit */
985 gtk_accel_group_connect(window->accelGroup, GDK_KEY_Q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
986 g_cclosure_new_swap(G_CALLBACK(browserWindowTryClose), window, NULL));
987 gtk_accel_group_connect(window->accelGroup, GDK_KEY_W, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
988 g_cclosure_new_swap(G_CALLBACK(browserWindowTryCloseCurrentWebView), window, NULL));
989
990 /* Print */
991 gtk_accel_group_connect(window->accelGroup, GDK_KEY_P, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
992 g_cclosure_new_swap(G_CALLBACK(printPage), window, NULL));
993
994 GtkWidget *toolbar = gtk_toolbar_new();
995 window->toolbar = toolbar;
996 gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
997 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
998
999 GtkToolItem *item = gtk_menu_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
1000 window->backItem = GTK_WIDGET(item);
1001 gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(item), 0);
1002 g_signal_connect_swapped(item, "clicked", G_CALLBACK(goBackCallback), (gpointer)window);
1003 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Back");
1004 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1005 gtk_widget_show(GTK_WIDGET(item));
1006
1007 item = gtk_menu_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
1008 window->forwardItem = GTK_WIDGET(item);
1009 gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(item), 0);
1010 g_signal_connect_swapped(G_OBJECT(item), "clicked", G_CALLBACK(goForwardCallback), (gpointer)window);
1011 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Forward");
1012 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1013 gtk_widget_show(GTK_WIDGET(item));
1014
1015 item = gtk_tool_button_new_from_stock(GTK_STOCK_PREFERENCES);
1016 g_signal_connect_swapped(G_OBJECT(item), "clicked", G_CALLBACK(settingsCallback), window);
1017 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Preferences");
1018 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1019 gtk_widget_show(GTK_WIDGET(item));
1020
1021 item = gtk_tool_button_new_from_stock(GTK_STOCK_ZOOM_OUT);
1022 window->zoomOutItem = GTK_WIDGET(item);
1023 g_signal_connect_swapped(item, "clicked", G_CALLBACK(zoomOutCallback), window);
1024 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Zoom Out");
1025 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1026 gtk_widget_show(GTK_WIDGET(item));
1027
1028 item = gtk_tool_button_new_from_stock(GTK_STOCK_ZOOM_IN);
1029 window->zoomInItem = GTK_WIDGET(item);
1030 g_signal_connect_swapped(item, "clicked", G_CALLBACK(zoomInCallback), window);
1031 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Zoom In");
1032 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1033 gtk_widget_show(GTK_WIDGET(item));
1034
1035 item = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
1036 g_signal_connect_swapped(item, "clicked", G_CALLBACK(searchCallback), window);
1037 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Find");
1038 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1039 gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_F, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
1040 gtk_widget_show(GTK_WIDGET(item));
1041
1042 item = gtk_tool_button_new_from_stock(GTK_STOCK_HOME);
1043 g_signal_connect_swapped(item, "clicked", G_CALLBACK(loadHomePage), window);
1044 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Home");
1045 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1046 gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_Home, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE);
1047 gtk_widget_show(GTK_WIDGET(item));
1048
1049 item = gtk_tool_button_new(gtk_image_new_from_icon_name("tab-new", GTK_ICON_SIZE_SMALL_TOOLBAR), NULL);
1050 g_signal_connect_swapped(item, "clicked", G_CALLBACK(newTabCallback), window);
1051 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "New Tab");
1052 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1053 gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_T, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
1054 gtk_widget_show_all(GTK_WIDGET(item));
1055
1056 item = gtk_tool_item_new();
1057 gtk_tool_item_set_expand(item, TRUE);
1058 gtk_container_add(GTK_CONTAINER(item), window->uriEntry);
1059 gtk_widget_show(window->uriEntry);
1060 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1061 gtk_widget_show(GTK_WIDGET(item));
1062
1063 item = gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH);
1064 window->reloadOrStopButton = GTK_WIDGET(item);
1065 g_signal_connect_swapped(item, "clicked", G_CALLBACK(reloadOrStopCallback), window);
1066 gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), "Reload");
1067 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1068 gtk_widget_add_accelerator(window->reloadOrStopButton, "clicked", window->accelGroup, GDK_KEY_F5, 0, GTK_ACCEL_VISIBLE);
1069 gtk_widget_show(window->reloadOrStopButton);
1070
1071 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1072 window->mainBox = vbox;
1073 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
1074 gtk_widget_show(toolbar);
1075
1076 window->notebook = gtk_notebook_new();
1077 g_signal_connect(window->notebook, "switch-page", G_CALLBACK(browserWindowSwitchTab), window);
1078 g_signal_connect(window->notebook, "page-added", G_CALLBACK(browserWindowTabAddedOrRemoved), window);
1079 g_signal_connect(window->notebook, "page-removed", G_CALLBACK(browserWindowTabAddedOrRemoved), window);
1080 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(window->notebook), FALSE);
1081 gtk_notebook_set_show_border(GTK_NOTEBOOK(window->notebook), FALSE);
1082 gtk_box_pack_start(GTK_BOX(window->mainBox), window->notebook, TRUE, TRUE, 0);
1083 gtk_widget_show(window->notebook);
1084
1085 gtk_container_add(GTK_CONTAINER(window), vbox);
1086 gtk_widget_show(vbox);
1087}
1088
1089static void browserWindowConstructed(GObject *gObject)
1090{
1091 G_OBJECT_CLASS(browser_window_parent_class)->constructed(gObject);
1092}
1093
1094static void browserWindowSaveSession(BrowserWindow *window)
1095{
1096 if (!window->sessionFile)
1097 return;
1098
1099 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
1100 WebKitWebViewSessionState *state = webkit_web_view_get_session_state(webView);
1101 GBytes *bytes = webkit_web_view_session_state_serialize(state);
1102 webkit_web_view_session_state_unref(state);
1103 g_file_set_contents(window->sessionFile, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), NULL);
1104 g_bytes_unref(bytes);
1105}
1106
1107static gboolean browserWindowDeleteEvent(GtkWidget *widget, GdkEventAny* event)
1108{
1109 BrowserWindow *window = BROWSER_WINDOW(widget);
1110 browserWindowSaveSession(window);
1111 browserWindowTryClose(window);
1112 return TRUE;
1113}
1114
1115static void browser_window_class_init(BrowserWindowClass *klass)
1116{
1117 GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
1118
1119 gobjectClass->constructed = browserWindowConstructed;
1120 gobjectClass->finalize = browserWindowFinalize;
1121
1122 GtkWidgetClass *widgetClass = GTK_WIDGET_CLASS(klass);
1123 widgetClass->delete_event = browserWindowDeleteEvent;
1124}
1125
1126/* Public API. */
1127GtkWidget *browser_window_new(GtkWindow *parent, WebKitWebContext *webContext)
1128{
1129 g_return_val_if_fail(WEBKIT_IS_WEB_CONTEXT(webContext), NULL);
1130
1131 BrowserWindow *window = BROWSER_WINDOW(g_object_new(BROWSER_TYPE_WINDOW,
1132 "type", GTK_WINDOW_TOPLEVEL, NULL));
1133
1134 window->webContext = webContext;
1135 g_signal_connect(window->webContext, "download-started", G_CALLBACK(downloadStarted), window);
1136 if (parent) {
1137 window->parentWindow = parent;
1138 g_object_add_weak_pointer(G_OBJECT(parent), (gpointer *)&window->parentWindow);
1139 }
1140
1141 return GTK_WIDGET(window);
1142}
1143
1144WebKitWebContext *browser_window_get_web_context(BrowserWindow *window)
1145{
1146 g_return_val_if_fail(BROWSER_IS_WINDOW(window), NULL);
1147
1148 return window->webContext;
1149}
1150
1151void browser_window_append_view(BrowserWindow *window, WebKitWebView *webView)
1152{
1153 g_return_if_fail(BROWSER_IS_WINDOW(window));
1154 g_return_if_fail(WEBKIT_IS_WEB_VIEW(webView));
1155
1156 if (window->activeTab && webkit_web_view_is_editable(browser_tab_get_web_view(window->activeTab))) {
1157 g_warning("Only one tab is allowed in editable mode");
1158 return;
1159 }
1160
1161 GtkWidget *tab = browser_tab_new(webView);
1162 if (gtk_widget_get_app_paintable(GTK_WIDGET(window)))
1163 browser_tab_set_background_color(BROWSER_TAB(tab), &window->backgroundColor);
1164 browser_tab_add_accelerators(BROWSER_TAB(tab), window->accelGroup);
1165 gtk_notebook_append_page(GTK_NOTEBOOK(window->notebook), tab, browser_tab_get_title_widget(BROWSER_TAB(tab)));
1166 gtk_container_child_set(GTK_CONTAINER(window->notebook), tab, "tab-expand", TRUE, NULL);
1167 gtk_widget_show(tab);
1168}
1169
1170void browser_window_load_uri(BrowserWindow *window, const char *uri)
1171{
1172 g_return_if_fail(BROWSER_IS_WINDOW(window));
1173 g_return_if_fail(uri);
1174
1175 browser_tab_load_uri(window->activeTab, uri);
1176}
1177
1178void browser_window_load_session(BrowserWindow *window, const char *sessionFile)
1179{
1180 g_return_if_fail(BROWSER_IS_WINDOW(window));
1181 g_return_if_fail(sessionFile);
1182
1183 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
1184 window->sessionFile = g_strdup(sessionFile);
1185 gchar *data = NULL;
1186 gsize dataLength;
1187 if (g_file_get_contents(sessionFile, &data, &dataLength, NULL)) {
1188 GBytes *bytes = g_bytes_new_take(data, dataLength);
1189 WebKitWebViewSessionState *state = webkit_web_view_session_state_new(bytes);
1190 g_bytes_unref(bytes);
1191
1192 if (state) {
1193 webkit_web_view_restore_session_state(webView, state);
1194 webkit_web_view_session_state_unref(state);
1195 }
1196 }
1197
1198 WebKitBackForwardList *bfList = webkit_web_view_get_back_forward_list(webView);
1199 WebKitBackForwardListItem *item = webkit_back_forward_list_get_current_item(bfList);
1200 if (item)
1201 webkit_web_view_go_to_back_forward_list_item(webView, item);
1202 else
1203 webkit_web_view_load_uri(webView, BROWSER_DEFAULT_URL);
1204
1205}
1206
1207void browser_window_set_background_color(BrowserWindow *window, GdkRGBA *rgba)
1208{
1209 g_return_if_fail(BROWSER_IS_WINDOW(window));
1210 g_return_if_fail(rgba);
1211
1212 g_assert(!window->activeTab);
1213
1214 if (gdk_rgba_equal(rgba, &window->backgroundColor))
1215 return;
1216
1217 window->backgroundColor = *rgba;
1218
1219 GdkVisual *rgbaVisual = gdk_screen_get_rgba_visual(gtk_window_get_screen(GTK_WINDOW(window)));
1220 if (!rgbaVisual)
1221 return;
1222
1223 gtk_widget_set_visual(GTK_WIDGET(window), rgbaVisual);
1224 gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
1225}
1226
1227WebKitWebView *browser_window_get_or_create_web_view_for_automation(void)
1228{
1229 if (!windowList)
1230 return NULL;
1231
1232 BrowserWindow *window = (BrowserWindow *)windowList->data;
1233 WebKitWebView *webView = browser_tab_get_web_view(window->activeTab);
1234 if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(window->notebook)) == 1 && !webkit_web_view_get_uri(webView)) {
1235 webkit_web_view_load_uri(webView, "about:blank");
1236 return webView;
1237 }
1238
1239 WebKitWebView *newWebView = WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
1240 "web-context", webkit_web_view_get_context(webView),
1241 "settings", webkit_web_view_get_settings(webView),
1242 "user-content-manager", webkit_web_view_get_user_content_manager(webView),
1243 "is-controlled-by-automation", TRUE,
1244 NULL));
1245 browser_window_append_view(window, newWebView);
1246 webkit_web_view_load_uri(newWebView, "about:blank");
1247 gtk_widget_grab_focus(GTK_WIDGET(newWebView));
1248 return newWebView;
1249}
1250