Name all the GLib timeout sources
[WebKit-https.git] / Tools / MiniBrowser / gtk / BrowserWindow.c
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 #include "BrowserWindow.h"
29
30 #include "BrowserDownloadsBar.h"
31 #include "BrowserSearchBar.h"
32 #include "BrowserSettingsDialog.h"
33 #include <gdk/gdkkeysyms.h>
34 #include <string.h>
35
36 enum {
37     PROP_0,
38
39     PROP_VIEW
40 };
41
42 struct _BrowserWindow {
43     GtkWindow parent;
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 *statusLabel;
54     GtkWidget *settingsDialog;
55     WebKitWebView *webView;
56     GtkWidget *downloadsBar;
57     BrowserSearchBar *searchBar;
58     gboolean searchBarVisible;
59     GdkPixbuf *favicon;
60     GtkWidget *reloadOrStopButton;
61     GtkWidget *fullScreenMessageLabel;
62     guint fullScreenMessageLabelId;
63 };
64
65 struct _BrowserWindowClass {
66     GtkWindowClass parent;
67 };
68
69 static const char *defaultWindowTitle = "WebKitGTK+ MiniBrowser";
70 static const char *miniBrowserAboutScheme = "minibrowser-about";
71 static const gdouble minimumZoomLevel = 0.5;
72 static const gdouble maximumZoomLevel = 3;
73 static const gdouble zoomStep = 1.2;
74 static gint windowCount = 0;
75
76 G_DEFINE_TYPE(BrowserWindow, browser_window, GTK_TYPE_WINDOW)
77
78 static char *getInternalURI(const char *uri)
79 {
80     // Internally we use minibrowser-about: as about: prefix is ignored by WebKit.
81     if (g_str_has_prefix(uri, "about:") && !g_str_equal(uri, "about:blank"))
82         return g_strconcat(miniBrowserAboutScheme, uri + strlen ("about"), NULL);
83
84     return g_strdup(uri);
85 }
86
87 static char *getExternalURI(const char *uri)
88 {
89     // From the user point of view we support about: prefix.
90     if (g_str_has_prefix(uri, miniBrowserAboutScheme))
91         return g_strconcat("about", uri + strlen(miniBrowserAboutScheme), NULL);
92
93     return g_strdup(uri);
94 }
95
96 static void browserWindowSetStatusText(BrowserWindow *window, const char *text)
97 {
98     gtk_label_set_text(GTK_LABEL(window->statusLabel), text);
99     gtk_widget_set_visible(window->statusLabel, !!text);
100 }
101
102 static void resetStatusText(GtkWidget *widget, BrowserWindow *window)
103 {
104     browserWindowSetStatusText(window, NULL);
105 }
106
107 static void activateUriEntryCallback(BrowserWindow *window)
108 {
109     browser_window_load_uri(window, gtk_entry_get_text(GTK_ENTRY(window->uriEntry)));
110 }
111
112 static void reloadOrStopCallback(BrowserWindow *window)
113 {
114     if (webkit_web_view_is_loading(window->webView))
115         webkit_web_view_stop_loading(window->webView);
116     else
117         webkit_web_view_reload(window->webView);
118 }
119
120 static void goBackCallback(BrowserWindow *window)
121 {
122     webkit_web_view_go_back(window->webView);
123 }
124
125 static void goForwardCallback(BrowserWindow *window)
126 {
127     webkit_web_view_go_forward(window->webView);
128 }
129
130 static void settingsCallback(BrowserWindow *window)
131 {
132     if (window->settingsDialog) {
133         gtk_window_present(GTK_WINDOW(window->settingsDialog));
134         return;
135     }
136
137     window->settingsDialog = browser_settings_dialog_new(webkit_web_view_get_settings(window->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
143 static void webViewURIChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
144 {
145     char *externalURI = getExternalURI(webkit_web_view_get_uri(webView));
146     gtk_entry_set_text(GTK_ENTRY(window->uriEntry), externalURI);
147     g_free(externalURI);
148 }
149
150 static void webViewTitleChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
151 {
152     const char *title = webkit_web_view_get_title(webView);
153     gtk_window_set_title(GTK_WINDOW(window), title ? title : defaultWindowTitle);
154 }
155
156 static gboolean resetEntryProgress(GtkEntry *entry)
157 {
158     gtk_entry_set_progress_fraction(entry, 0);
159     return FALSE;
160 }
161
162 static void webViewLoadProgressChanged(WebKitWebView *webView, GParamSpec *pspec, BrowserWindow *window)
163 {
164     gdouble progress = webkit_web_view_get_estimated_load_progress(webView);
165     gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), progress);
166     if (progress == 1.0) {
167         guint id = g_timeout_add(500, (GSourceFunc)resetEntryProgress, window->uriEntry);
168         g_source_set_name_by_id(id, "[WebKit] resetEntryProgress");
169     }
170 }
171
172 static void downloadStarted(WebKitWebContext *webContext, WebKitDownload *download, BrowserWindow *window)
173 {
174     if (!window->downloadsBar) {
175         window->downloadsBar = browser_downloads_bar_new();
176         gtk_box_pack_start(GTK_BOX(window->mainBox), window->downloadsBar, FALSE, FALSE, 0);
177         gtk_box_reorder_child(GTK_BOX(window->mainBox), window->downloadsBar, 0);
178         g_object_add_weak_pointer(G_OBJECT(window->downloadsBar), (gpointer *)&(window->downloadsBar));
179         gtk_widget_show(window->downloadsBar);
180     }
181     browser_downloads_bar_add_download(BROWSER_DOWNLOADS_BAR(window->downloadsBar), download);
182 }
183
184 static void browserWindowHistoryItemSelected(BrowserWindow *window, GtkMenuItem *item)
185 {
186     GtkAction *action = gtk_activatable_get_related_action(GTK_ACTIVATABLE(item));
187     browserWindowSetStatusText(window, action ? gtk_action_get_name(action) : NULL);
188 }
189
190 static void browserWindowHistoryItemActivated(BrowserWindow *window, GtkAction *action)
191 {
192     WebKitBackForwardListItem *item = g_object_get_data(G_OBJECT(action), "back-forward-list-item");
193     if (!item)
194         return;
195
196     webkit_web_view_go_to_back_forward_list_item(window->webView, item);
197 }
198
199 static GtkWidget *browserWindowCreateBackForwardMenu(BrowserWindow *window, GList *list)
200 {
201     if (!list)
202         return NULL;
203
204     GtkWidget *menu = gtk_menu_new();
205     GList *listItem;
206     for (listItem = list; listItem; listItem = g_list_next(listItem)) {
207         WebKitBackForwardListItem *item = (WebKitBackForwardListItem *)listItem->data;
208         const char *uri = webkit_back_forward_list_item_get_uri(item);
209         const char *title = webkit_back_forward_list_item_get_title(item);
210
211         GtkAction *action = gtk_action_new(uri, title, NULL, NULL);
212         g_object_set_data_full(G_OBJECT(action), "back-forward-list-item", g_object_ref(item), g_object_unref);
213         g_signal_connect_swapped(action, "activate", G_CALLBACK(browserWindowHistoryItemActivated), window);
214
215         GtkWidget *menuItem = gtk_action_create_menu_item(action);
216         g_signal_connect_swapped(menuItem, "select", G_CALLBACK(browserWindowHistoryItemSelected), window);
217         g_object_unref(action);
218
219         gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuItem);
220         gtk_widget_show(menuItem);
221     }
222
223     g_signal_connect(menu, "hide", G_CALLBACK(resetStatusText), window);
224
225     return menu;
226 }
227
228 static void browserWindowUpdateNavigationActions(BrowserWindow *window, WebKitBackForwardList *backForwadlist)
229 {
230     gtk_widget_set_sensitive(window->backItem, webkit_web_view_can_go_back(window->webView));
231     gtk_widget_set_sensitive(window->forwardItem, webkit_web_view_can_go_forward(window->webView));
232
233     GList *list = webkit_back_forward_list_get_back_list_with_limit(backForwadlist, 10);
234     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->backItem),
235                                   browserWindowCreateBackForwardMenu(window, list));
236     g_list_free(list);
237
238     list = webkit_back_forward_list_get_forward_list_with_limit(backForwadlist, 10);
239     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(window->forwardItem),
240                                   browserWindowCreateBackForwardMenu(window, list));
241     g_list_free(list);
242 }
243
244 static void backForwadlistChanged(WebKitBackForwardList *backForwadlist, WebKitBackForwardListItem *itemAdded, GList *itemsRemoved, BrowserWindow *window)
245 {
246     browserWindowUpdateNavigationActions(window, backForwadlist);
247 }
248
249 static void geolocationRequestDialogCallback(GtkDialog *dialog, gint response, WebKitPermissionRequest *request)
250 {
251     switch (response) {
252     case GTK_RESPONSE_YES:
253         webkit_permission_request_allow(request);
254         break;
255     default:
256         webkit_permission_request_deny(request);
257         break;
258     }
259
260     gtk_widget_destroy(GTK_WIDGET(dialog));
261     g_object_unref(request);
262 }
263
264 static void webViewClose(WebKitWebView *webView, BrowserWindow *window)
265 {
266     gtk_widget_destroy(GTK_WIDGET(window));
267 }
268
269 static void webViewRunAsModal(WebKitWebView *webView, BrowserWindow *window)
270 {
271     gtk_window_set_modal(GTK_WINDOW(window), TRUE);
272 }
273
274 static void webViewReadyToShow(WebKitWebView *webView, BrowserWindow *window)
275 {
276     WebKitWindowProperties *windowProperties = webkit_web_view_get_window_properties(webView);
277
278     GdkRectangle geometry;
279     webkit_window_properties_get_geometry(windowProperties, &geometry);
280     if (geometry.x >= 0 && geometry.y >= 0)
281         gtk_window_move(GTK_WINDOW(window), geometry.x, geometry.y);
282     if (geometry.width > 0 && geometry.height > 0)
283         gtk_window_resize(GTK_WINDOW(window), geometry.width, geometry.height);
284
285     if (!webkit_window_properties_get_toolbar_visible(windowProperties))
286         gtk_widget_hide(window->toolbar);
287     else if (!webkit_window_properties_get_locationbar_visible(windowProperties))
288         gtk_widget_hide(window->uriEntry);
289
290     if (!webkit_window_properties_get_resizable(windowProperties))
291         gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
292
293     gtk_widget_show(GTK_WIDGET(window));
294 }
295
296 static gboolean fullScreenMessageTimeoutCallback(BrowserWindow *window)
297 {
298     gtk_widget_hide(window->fullScreenMessageLabel);
299     window->fullScreenMessageLabelId = 0;
300     return FALSE;
301 }
302
303 static gboolean webViewEnterFullScreen(WebKitWebView *webView, BrowserWindow *window)
304 {
305     gchar *titleOrURI = g_strdup(webkit_web_view_get_title(window->webView));
306     if (!titleOrURI)
307         titleOrURI = getExternalURI(webkit_web_view_get_uri(window->webView));
308     gchar *message = g_strdup_printf("%s is now full screen. Press ESC or f to exit.", titleOrURI);
309     gtk_label_set_text(GTK_LABEL(window->fullScreenMessageLabel), message);
310     g_free(titleOrURI);
311     g_free(message);
312
313     gtk_widget_show(window->fullScreenMessageLabel);
314
315     window->fullScreenMessageLabelId = g_timeout_add_seconds(2, (GSourceFunc)fullScreenMessageTimeoutCallback, window);
316     g_source_set_name_by_id(window->fullScreenMessageLabelId, "[WebKit] fullScreenMessageTimeoutCallback");
317     gtk_widget_hide(window->toolbar);
318     window->searchBarVisible = gtk_widget_get_visible(GTK_WIDGET(window->searchBar));
319     browser_search_bar_close(window->searchBar);
320
321     return FALSE;
322 }
323
324 static gboolean webViewLeaveFullScreen(WebKitWebView *webView, BrowserWindow *window)
325 {
326     if (window->fullScreenMessageLabelId) {
327         g_source_remove(window->fullScreenMessageLabelId);
328         window->fullScreenMessageLabelId = 0;
329     }
330     gtk_widget_hide(window->fullScreenMessageLabel);
331     gtk_widget_show(window->toolbar);
332     if (window->searchBarVisible) {
333         // Opening the search bar steals the focus. Usually, we want
334         // this but not when coming back from fullscreen.
335         GtkWidget *focusWidget = gtk_window_get_focus(GTK_WINDOW(window));
336         browser_search_bar_open(window->searchBar);
337         gtk_window_set_focus(GTK_WINDOW(window), focusWidget);
338     }
339
340     return FALSE;
341 }
342
343 static GtkWidget *webViewCreate(WebKitWebView *webView, BrowserWindow *window)
344 {
345     WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(webkit_web_view_get_context(webView)));
346     webkit_web_view_set_settings(newWebView, webkit_web_view_get_settings(webView));
347
348     GtkWidget *newWindow = browser_window_new(newWebView, GTK_WINDOW(window));
349     g_signal_connect(newWebView, "ready-to-show", G_CALLBACK(webViewReadyToShow), newWindow);
350     g_signal_connect(newWebView, "run-as-modal", G_CALLBACK(webViewRunAsModal), newWindow);
351     g_signal_connect(newWebView, "close", G_CALLBACK(webViewClose), newWindow);
352     return GTK_WIDGET(newWebView);
353 }
354
355 static gboolean webViewLoadFailed(WebKitWebView *webView, WebKitLoadEvent loadEvent, const char *failingURI, GError *error, BrowserWindow *window)
356 {
357     gtk_entry_set_progress_fraction(GTK_ENTRY(window->uriEntry), 0.);
358     return FALSE;
359 }
360
361 static gboolean webViewDecidePolicy(WebKitWebView *webView, WebKitPolicyDecision *decision, WebKitPolicyDecisionType decisionType, BrowserWindow *window)
362 {
363     switch (decisionType) {
364     case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION: {
365         WebKitNavigationPolicyDecision *navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION(decision);
366         if (webkit_navigation_policy_decision_get_navigation_type(navigationDecision) != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
367             || webkit_navigation_policy_decision_get_mouse_button(navigationDecision) != GDK_BUTTON_MIDDLE)
368             return FALSE;
369
370         // Opening a new window if link clicked with the middle button.
371         WebKitWebView *newWebView = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(webkit_web_view_get_context(webView)));
372         GtkWidget *newWindow = browser_window_new(newWebView, GTK_WINDOW(window));
373         webkit_web_view_load_request(newWebView, webkit_navigation_policy_decision_get_request(navigationDecision));
374         gtk_widget_show(newWindow);
375
376         webkit_policy_decision_ignore(decision);
377         return TRUE;
378     }
379     case WEBKIT_POLICY_DECISION_TYPE_RESPONSE: {
380         WebKitResponsePolicyDecision *responseDecision = WEBKIT_RESPONSE_POLICY_DECISION(decision);
381         WebKitURIResponse *response = webkit_response_policy_decision_get_response(responseDecision);
382         const char *mimeType = webkit_uri_response_get_mime_type(response);
383
384         if (webkit_web_view_can_show_mime_type(webView, mimeType))
385             return FALSE;
386
387         WebKitWebResource *mainResource = webkit_web_view_get_main_resource(webView);
388         WebKitURIRequest *request = webkit_response_policy_decision_get_request(responseDecision);
389         const char *requestURI = webkit_uri_request_get_uri(request);
390         if (g_strcmp0(webkit_web_resource_get_uri(mainResource), requestURI))
391             return FALSE;
392
393         webkit_policy_decision_download(decision);
394         return TRUE;
395     }
396     case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
397     default:
398         return FALSE;
399     }
400 }
401
402 static gboolean webViewDecidePermissionRequest(WebKitWebView *webView, WebKitPermissionRequest *request, BrowserWindow *window)
403 {
404     if (!WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request))
405         return FALSE;
406
407     GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window),
408                                                GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
409                                                GTK_MESSAGE_QUESTION,
410                                                GTK_BUTTONS_YES_NO,
411                                                "Geolocation request");
412
413     gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "Allow geolocation request?");
414     g_signal_connect(dialog, "response", G_CALLBACK(geolocationRequestDialogCallback), g_object_ref(request));
415     gtk_widget_show(dialog);
416
417     return TRUE;
418 }
419
420 static void webViewMouseTargetChanged(WebKitWebView *webView, WebKitHitTestResult *hitTestResult, guint mouseModifiers, BrowserWindow *window)
421 {
422     if (!webkit_hit_test_result_context_is_link(hitTestResult)) {
423         browserWindowSetStatusText(window, NULL);
424         return;
425     }
426     browserWindowSetStatusText(window, webkit_hit_test_result_get_link_uri(hitTestResult));
427 }
428
429 static gboolean browserWindowCanZoomIn(BrowserWindow *window)
430 {
431     gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep;
432     return zoomLevel < maximumZoomLevel;
433 }
434
435 static gboolean browserWindowCanZoomOut(BrowserWindow *window)
436 {
437     gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep;
438     return zoomLevel > minimumZoomLevel;
439 }
440
441 static void browserWindowUpdateZoomActions(BrowserWindow *window)
442 {
443     gtk_widget_set_sensitive(window->zoomInItem, browserWindowCanZoomIn(window));
444     gtk_widget_set_sensitive(window->zoomOutItem, browserWindowCanZoomOut(window));
445 }
446
447 static void webViewZoomLevelChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
448 {
449     browserWindowUpdateZoomActions(window);
450 }
451
452 static void updateUriEntryIcon(BrowserWindow *window)
453 {
454     GtkEntry *entry = GTK_ENTRY(window->uriEntry);
455     if (window->favicon)
456         gtk_entry_set_icon_from_pixbuf(entry, GTK_ENTRY_ICON_PRIMARY, window->favicon);
457     else
458         gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_NEW);
459 }
460
461 static void faviconChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
462 {
463     GdkPixbuf *favicon = NULL;
464     cairo_surface_t *surface = webkit_web_view_get_favicon(window->webView);
465
466     if (surface) {
467         int width = cairo_image_surface_get_width(surface);
468         int height = cairo_image_surface_get_height(surface);
469         favicon = gdk_pixbuf_get_from_surface(surface, 0, 0, width, height);
470     }
471
472     if (window->favicon)
473         g_object_unref(window->favicon);
474     window->favicon = favicon;
475
476     updateUriEntryIcon(window);
477 }
478
479 static void webViewIsLoadingChanged(GObject *object, GParamSpec *paramSpec, BrowserWindow *window)
480 {
481     gboolean isLoading = webkit_web_view_is_loading(window->webView);
482     gtk_tool_button_set_stock_id(GTK_TOOL_BUTTON(window->reloadOrStopButton), isLoading ? GTK_STOCK_STOP : GTK_STOCK_REFRESH);
483 }
484
485 static void zoomInCallback(BrowserWindow *window)
486 {
487     gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) * zoomStep;
488     webkit_web_view_set_zoom_level(window->webView, zoomLevel);
489 }
490
491 static void zoomOutCallback(BrowserWindow *window)
492 {
493     gdouble zoomLevel = webkit_web_view_get_zoom_level(window->webView) / zoomStep;
494     webkit_web_view_set_zoom_level(window->webView, zoomLevel);
495 }
496
497 static void searchCallback(BrowserWindow *window)
498 {
499     browser_search_bar_open(window->searchBar);
500 }
501
502 static void browserWindowFinalize(GObject *gObject)
503 {
504     BrowserWindow *window = BROWSER_WINDOW(gObject);
505     if (window->favicon) {
506         g_object_unref(window->favicon);
507         window->favicon = NULL;
508     }
509
510     if (window->accelGroup) {
511         g_object_unref(window->accelGroup);
512         window->accelGroup = NULL;
513     }
514
515     if (window->fullScreenMessageLabelId)
516         g_source_remove(window->fullScreenMessageLabelId);
517
518     G_OBJECT_CLASS(browser_window_parent_class)->finalize(gObject);
519
520     if (g_atomic_int_dec_and_test(&windowCount))
521         gtk_main_quit();
522 }
523
524 static void browserWindowGetProperty(GObject *object, guint propId, GValue *value, GParamSpec *pspec)
525 {
526     BrowserWindow *window = BROWSER_WINDOW(object);
527
528     switch (propId) {
529     case PROP_VIEW:
530         g_value_set_object(value, browser_window_get_view(window));
531         break;
532     default:
533         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
534     }
535 }
536
537 static void browserWindowSetProperty(GObject *object, guint propId, const GValue *value, GParamSpec *pspec)
538 {
539     BrowserWindow* window = BROWSER_WINDOW(object);
540
541     switch (propId) {
542     case PROP_VIEW:
543         window->webView = g_value_get_object(value);
544         break;
545     default:
546         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
547     }
548 }
549
550 static void browser_window_init(BrowserWindow *window)
551 {
552     g_atomic_int_inc(&windowCount);
553
554     gtk_window_set_title(GTK_WINDOW(window), defaultWindowTitle);
555     gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
556
557     window->uriEntry = gtk_entry_new();
558     g_signal_connect_swapped(window->uriEntry, "activate", G_CALLBACK(activateUriEntryCallback), (gpointer)window);
559     gtk_entry_set_icon_activatable(GTK_ENTRY(window->uriEntry), GTK_ENTRY_ICON_PRIMARY, FALSE);
560     updateUriEntryIcon(window);
561
562     /* Keyboard accelerators */
563     window->accelGroup = gtk_accel_group_new();
564     gtk_window_add_accel_group(GTK_WINDOW(window), window->accelGroup);
565
566     GtkWidget *toolbar = gtk_toolbar_new();
567     window->toolbar = toolbar;
568     gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL);
569     gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
570
571     GtkToolItem *item = gtk_menu_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
572     window->backItem = GTK_WIDGET(item);
573     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(item), 0);
574     g_signal_connect_swapped(item, "clicked", G_CALLBACK(goBackCallback), (gpointer)window);
575     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
576     gtk_widget_show(GTK_WIDGET(item));
577
578     item = gtk_menu_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
579     window->forwardItem = GTK_WIDGET(item);
580     gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(item), 0);
581     g_signal_connect_swapped(G_OBJECT(item), "clicked", G_CALLBACK(goForwardCallback), (gpointer)window);
582     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
583     gtk_widget_show(GTK_WIDGET(item));
584
585     item = gtk_tool_button_new_from_stock(GTK_STOCK_PREFERENCES);
586     g_signal_connect_swapped(G_OBJECT(item), "clicked", G_CALLBACK(settingsCallback), window);
587     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
588     gtk_widget_show(GTK_WIDGET(item));
589
590     item = gtk_tool_button_new_from_stock(GTK_STOCK_ZOOM_OUT);
591     window->zoomOutItem = GTK_WIDGET(item);
592     g_signal_connect_swapped(item, "clicked", G_CALLBACK(zoomOutCallback), window);
593     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
594     gtk_widget_show(GTK_WIDGET(item));
595
596     item = gtk_tool_button_new_from_stock(GTK_STOCK_ZOOM_IN);
597     window->zoomInItem = GTK_WIDGET(item);
598     g_signal_connect_swapped(item, "clicked", G_CALLBACK(zoomInCallback), window);
599     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
600     gtk_widget_show(GTK_WIDGET(item));
601
602     item = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
603     g_signal_connect_swapped(item, "clicked", G_CALLBACK(searchCallback), window);
604     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
605     gtk_widget_add_accelerator(GTK_WIDGET(item), "clicked", window->accelGroup, GDK_KEY_F, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
606     gtk_widget_show(GTK_WIDGET(item));
607
608     item = gtk_tool_item_new();
609     gtk_tool_item_set_expand(item, TRUE);
610     gtk_container_add(GTK_CONTAINER(item), window->uriEntry);
611     gtk_widget_show(window->uriEntry);
612     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
613     gtk_widget_show(GTK_WIDGET(item));
614
615     item = gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH);
616     window->reloadOrStopButton = GTK_WIDGET(item);
617     g_signal_connect_swapped(item, "clicked", G_CALLBACK(reloadOrStopCallback), window);
618     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
619     gtk_widget_add_accelerator(window->reloadOrStopButton, "clicked", window->accelGroup, GDK_KEY_F5, 0, GTK_ACCEL_VISIBLE);
620     gtk_widget_show(window->reloadOrStopButton);
621
622     GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
623     window->mainBox = vbox;
624     gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
625     gtk_widget_show(toolbar);
626
627     gtk_container_add(GTK_CONTAINER(window), vbox);
628     gtk_widget_show(vbox);
629 }
630
631 static void browserWindowConstructed(GObject *gObject)
632 {
633     BrowserWindow *window = BROWSER_WINDOW(gObject);
634
635     browserWindowUpdateZoomActions(window);
636
637     g_signal_connect(window->webView, "notify::uri", G_CALLBACK(webViewURIChanged), window);
638     g_signal_connect(window->webView, "notify::estimated-load-progress", G_CALLBACK(webViewLoadProgressChanged), window);
639     g_signal_connect(window->webView, "notify::title", G_CALLBACK(webViewTitleChanged), window);
640     g_signal_connect(window->webView, "create", G_CALLBACK(webViewCreate), window);
641     g_signal_connect(window->webView, "load-failed", G_CALLBACK(webViewLoadFailed), window);
642     g_signal_connect(window->webView, "decide-policy", G_CALLBACK(webViewDecidePolicy), window);
643     g_signal_connect(window->webView, "permission-request", G_CALLBACK(webViewDecidePermissionRequest), window);
644     g_signal_connect(window->webView, "mouse-target-changed", G_CALLBACK(webViewMouseTargetChanged), window);
645     g_signal_connect(window->webView, "notify::zoom-level", G_CALLBACK(webViewZoomLevelChanged), window);
646     g_signal_connect(window->webView, "notify::favicon", G_CALLBACK(faviconChanged), window);
647     g_signal_connect(window->webView, "enter-fullscreen", G_CALLBACK(webViewEnterFullScreen), window);
648     g_signal_connect(window->webView, "leave-fullscreen", G_CALLBACK(webViewLeaveFullScreen), window);
649     g_signal_connect(window->webView, "notify::is-loading", G_CALLBACK(webViewIsLoadingChanged), window);
650
651     g_signal_connect(webkit_web_view_get_context(window->webView), "download-started", G_CALLBACK(downloadStarted), window);
652
653     window->searchBar = BROWSER_SEARCH_BAR(browser_search_bar_new(window->webView));
654     browser_search_bar_add_accelerators(window->searchBar, window->accelGroup);
655     gtk_box_pack_start(GTK_BOX(window->mainBox), GTK_WIDGET(window->searchBar), FALSE, FALSE, 0);
656
657     WebKitBackForwardList *backForwadlist = webkit_web_view_get_back_forward_list(window->webView);
658     g_signal_connect(backForwadlist, "changed", G_CALLBACK(backForwadlistChanged), window);
659
660     GtkWidget *overlay = gtk_overlay_new();
661     gtk_box_pack_start(GTK_BOX(window->mainBox), overlay, TRUE, TRUE, 0);
662     gtk_widget_show(overlay);
663
664     window->statusLabel = gtk_label_new(NULL);
665     gtk_widget_set_halign(window->statusLabel, GTK_ALIGN_START);
666     gtk_widget_set_valign(window->statusLabel, GTK_ALIGN_END);
667     gtk_widget_set_margin_left(window->statusLabel, 1);
668     gtk_widget_set_margin_right(window->statusLabel, 1);
669     gtk_widget_set_margin_top(window->statusLabel, 1);
670     gtk_widget_set_margin_bottom(window->statusLabel, 1);
671     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), window->statusLabel);
672
673     gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(window->webView));
674
675     window->fullScreenMessageLabel = gtk_label_new(NULL);
676     gtk_widget_set_halign(window->fullScreenMessageLabel, GTK_ALIGN_CENTER);
677     gtk_widget_set_valign(window->fullScreenMessageLabel, GTK_ALIGN_CENTER);
678     gtk_widget_set_no_show_all(window->fullScreenMessageLabel, TRUE);
679     gtk_overlay_add_overlay(GTK_OVERLAY(overlay), window->fullScreenMessageLabel);
680     gtk_widget_show(GTK_WIDGET(window->webView));
681 }
682
683 static void browser_window_class_init(BrowserWindowClass *klass)
684 {
685     GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
686
687     gobjectClass->constructed = browserWindowConstructed;
688     gobjectClass->get_property = browserWindowGetProperty;
689     gobjectClass->set_property = browserWindowSetProperty;
690     gobjectClass->finalize = browserWindowFinalize;
691
692     g_object_class_install_property(gobjectClass,
693                                     PROP_VIEW,
694                                     g_param_spec_object("view",
695                                                         "View",
696                                                         "The web view of this window",
697                                                         WEBKIT_TYPE_WEB_VIEW,
698                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
699 }
700
701 // Public API.
702 GtkWidget *browser_window_new(WebKitWebView *view, GtkWindow *parent)
703 {
704     g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(view), 0);
705
706     return GTK_WIDGET(g_object_new(BROWSER_TYPE_WINDOW,
707                                    "transient-for", parent,
708                                    "type", GTK_WINDOW_TOPLEVEL,
709                                    "view", view, NULL));
710 }
711
712 WebKitWebView *browser_window_get_view(BrowserWindow *window)
713 {
714     g_return_val_if_fail(BROWSER_IS_WINDOW(window), 0);
715
716     return window->webView;
717 }
718
719 void browser_window_load_uri(BrowserWindow *window, const char *uri)
720 {
721     g_return_if_fail(BROWSER_IS_WINDOW(window));
722     g_return_if_fail(uri);
723
724     if (!g_str_has_prefix(uri, "javascript:")) {
725         char *internalURI = getInternalURI(uri);
726         webkit_web_view_load_uri(window->webView, internalURI);
727         g_free(internalURI);
728         return;
729     }
730
731     webkit_web_view_run_javascript(window->webView, strstr(uri, "javascript:"), NULL, NULL, NULL);
732 }