1 /*
2 *
3 * Copyright (C) 2010-2011 Colomban Wendling <ban@herbesfolles.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include "gwh-browser.h"
21
22 #include "config.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
28 #include <glib/gi18n-lib.h>
29 #include <gtk/gtk.h>
30 #include <webkit/webkit.h>
31
32 #include "gwh-utils.h"
33 #include "gwh-settings.h"
34 #include "gwh-keybindings.h"
35 #include "gwh-plugin.h"
36
37
38 #if ! GTK_CHECK_VERSION (2, 18, 0)
39 # ifndef gtk_widget_set_visible
40 # define gtk_widget_set_visible(w, v) \
41 ((v) ? gtk_widget_show (w) : gtk_widget_hide (w))
42 # endif /* defined (gtk_widget_set_visible) */
43 # ifndef gtk_widget_get_visible
44 # define gtk_widget_get_visible(w) \
45 (GTK_WIDGET_VISIBLE (w))
46 # endif /* defined (gtk_widget_get_visible) */
47 #endif /* GTK_CHECK_VERSION (2, 18, 0) */
48 #if ! GTK_CHECK_VERSION (2, 20, 0)
49 # ifndef gtk_widget_get_mapped
50 # define gtk_widget_get_mapped(w) \
51 (GTK_WIDGET_MAPPED ((w)))
52 # endif /* defined (gtk_widget_get_mapped) */
53 #endif /* GTK_CHECK_VERSION (2, 20, 0) */
54 #if ! GTK_CHECK_VERSION (3, 0, 0)
55 /* the GtkComboBoxText API is actually available in 2.24, but Geany's
56 * gtkcompat.h hides it on < 3.0.0 to keep ABI compatibility on all 2.x, so we
57 * need to use the old API there too. */
58 # define gtk_combo_box_get_entry_text_column(c) \
59 (gtk_combo_box_entry_get_text_column (GTK_COMBO_BOX_ENTRY (c)))
60 #endif /* GTK_CHECK_VERSION (3, 0, 0) */
61 #if ! GTK_CHECK_VERSION (3, 0, 0)
62 static void
combo_box_text_remove_all(GtkComboBoxText * combo_box)63 combo_box_text_remove_all (GtkComboBoxText *combo_box)
64 {
65 GtkListStore *store;
66
67 g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
68
69 store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
70 gtk_list_store_clear (store);
71 }
72 # define gtk_combo_box_text_remove_all combo_box_text_remove_all
73 #endif /* GTK_CHECK_VERSION (3, 0, 0) */
74 #if GTK_CHECK_VERSION (3, 0, 0)
75 /* alias GtkObject, we implement the :destroy signal */
76 # define GtkObject GtkWidget
77 # define GtkObjectClass GtkWidgetClass
78 # define GTK_OBJECT_CLASS GTK_WIDGET_CLASS
79 #endif /* GTK_CHECK_VERSION (3, 0, 0) */
80
81
82 struct _GwhBrowserPrivate
83 {
84 GwhSettings *settings;
85
86 GIcon *default_icon;
87
88 GtkWidget *toolbar;
89 GtkWidget *paned;
90 GtkWidget *web_view;
91 WebKitWebInspector *inspector;
92 GtkWidget *inspector_view; /* the widget that should be shown to
93 * display the inspector, not necessarily
94 * a WebKitWebView */
95 GtkWidget *inspector_window;
96 gint inspector_window_x;
97 gint inspector_window_y;
98 GtkWidget *inspector_web_view;
99
100 GtkWidget *url_entry;
101 GtkWidget *url_combo;
102 GtkToolItem *item_prev;
103 GtkToolItem *item_next;
104 GtkToolItem *item_cancel;
105 GtkToolItem *item_reload;
106 GtkToolItem *item_inspector;
107
108 GtkWidget *statusbar;
109 gchar *hovered_link;
110 };
111
112 enum {
113 PROP_0,
114 PROP_INSPECTOR_TRANSIENT_FOR,
115 PROP_ORIENTATION,
116 PROP_URI,
117 PROP_WEB_VIEW,
118 PROP_TOOLBAR
119 };
120
121 enum {
122 POPULATE_POPUP,
123 LAST_SIGNAL
124 };
125
126 static guint signals[LAST_SIGNAL] = {0};
127
128
129 G_DEFINE_TYPE_WITH_CODE (GwhBrowser, gwh_browser, GTK_TYPE_VBOX,
130 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
131
132
133 static void inspector_set_detached (GwhBrowser *self,
134 gboolean detached);
135
136
137 static void
set_location_icon(GwhBrowser * self,const gchar * icon_uri)138 set_location_icon (GwhBrowser *self,
139 const gchar *icon_uri)
140 {
141 gboolean success = FALSE;
142
143 if (icon_uri) {
144 GdkPixbuf *icon;
145
146 icon = gwh_pixbuf_new_from_uri (icon_uri, NULL);
147 if (icon) {
148 gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (self->priv->url_entry),
149 GTK_ENTRY_ICON_PRIMARY, icon);
150 g_object_unref (icon);
151 success = TRUE;
152 }
153 }
154 if (! success) {
155 if (G_UNLIKELY (self->priv->default_icon == NULL)) {
156 gchar *ctype;
157
158 ctype = g_content_type_from_mime_type ("text/html");
159 self->priv->default_icon = g_content_type_get_icon (ctype);
160 g_free (ctype);
161 }
162 gtk_entry_set_icon_from_gicon (GTK_ENTRY (self->priv->url_entry),
163 GTK_ENTRY_ICON_PRIMARY,
164 self->priv->default_icon);
165 }
166 }
167
168 static gchar *
get_web_inspector_window_geometry(GwhBrowser * self)169 get_web_inspector_window_geometry (GwhBrowser *self)
170 {
171 return gwh_get_window_geometry (GTK_WINDOW (self->priv->inspector_window),
172 self->priv->inspector_window_x,
173 self->priv->inspector_window_y);
174 }
175
176 static void
set_web_inspector_window_geometry(GwhBrowser * self,const gchar * geometry)177 set_web_inspector_window_geometry (GwhBrowser *self,
178 const gchar *geometry)
179 {
180 gwh_set_window_geometry (GTK_WINDOW (self->priv->inspector_window),
181 geometry, &self->priv->inspector_window_x,
182 &self->priv->inspector_window_y);
183 }
184
185 /* settings change notifications */
186
187 static void
on_settings_browser_last_uri_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)188 on_settings_browser_last_uri_notify (GObject *object,
189 GParamSpec *pspec,
190 GwhBrowser *self)
191 {
192 gchar *uri;
193
194 g_object_get (object, pspec->name, &uri, NULL);
195 gwh_browser_set_uri (self, uri);
196 g_free (uri);
197 }
198
199 static void
on_settings_browser_bookmarks_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)200 on_settings_browser_bookmarks_notify (GObject *object,
201 GParamSpec *pspec,
202 GwhBrowser *self)
203 {
204 gchar **bookmarks;
205
206 g_return_if_fail (GWH_IS_BROWSER (self));
207
208 gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (self->priv->url_combo));
209 bookmarks = gwh_browser_get_bookmarks (self);
210 if (bookmarks) {
211 gchar **p;
212
213 for (p = bookmarks; *p; p++) {
214 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (self->priv->url_combo),
215 *p);
216 }
217 g_strfreev (bookmarks);
218 }
219 }
220
221 static void
on_settings_browser_orientation_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)222 on_settings_browser_orientation_notify (GObject *object,
223 GParamSpec *pspec,
224 GwhBrowser *self)
225 {
226 GtkOrientation orientation;
227
228 g_object_get (object, pspec->name, &orientation, NULL);
229 if (orientation != gtk_orientable_get_orientation (GTK_ORIENTABLE (self))) {
230 gtk_orientable_set_orientation (GTK_ORIENTABLE (self), orientation);
231 }
232 }
233
234 static void
on_settings_inspector_window_geometry_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)235 on_settings_inspector_window_geometry_notify (GObject *object,
236 GParamSpec *pspec,
237 GwhBrowser *self)
238 {
239 gchar *geometry;
240
241 g_object_get (object, pspec->name, &geometry, NULL);
242 set_web_inspector_window_geometry (self, geometry);
243 g_free (geometry);
244 }
245
246 static void
on_settings_inspector_detached_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)247 on_settings_inspector_detached_notify (GObject *object,
248 GParamSpec *pspec,
249 GwhBrowser *self)
250 {
251 gboolean detached;
252
253 g_object_get (object, pspec->name, &detached, NULL);
254 inspector_set_detached (self, detached);
255 }
256
257 static void
on_settings_wm_windows_skip_taskbar_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)258 on_settings_wm_windows_skip_taskbar_notify (GObject *object,
259 GParamSpec *pspec,
260 GwhBrowser *self)
261 {
262 gboolean skips_taskbar;
263
264 g_object_get (object, pspec->name, &skips_taskbar, NULL);
265 gtk_window_set_skip_taskbar_hint (GTK_WINDOW (self->priv->inspector_window),
266 skips_taskbar);
267 }
268
269 static void
on_settings_wm_windows_type_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)270 on_settings_wm_windows_type_notify (GObject *object,
271 GParamSpec *pspec,
272 GwhBrowser *self)
273 {
274 gint type;
275 gboolean remap = gtk_widget_get_mapped (self->priv->inspector_window);
276
277 /* We need to remap the window if we change its type because it's not doable
278 * when mapped. So, hack around. */
279
280 g_object_get (object, pspec->name, &type, NULL);
281 if (remap) {
282 gtk_widget_unmap (self->priv->inspector_window);
283 }
284 gtk_window_set_type_hint (GTK_WINDOW (self->priv->inspector_window), type);
285 if (remap) {
286 gtk_widget_map (self->priv->inspector_window);
287 }
288 }
289
290 /* web inspector events handling */
291
292 #define INSPECTOR_DETACHED(self) \
293 (gtk_bin_get_child (GTK_BIN ((self)->priv->inspector_window)) != NULL)
294
295 #define INSPECTOR_VISIBLE(self) \
296 (gtk_widget_get_visible ((self)->priv->inspector_view))
297
298 static void
inspector_set_visible(GwhBrowser * self,gboolean visible)299 inspector_set_visible (GwhBrowser *self,
300 gboolean visible)
301 {
302 if (visible != INSPECTOR_VISIBLE (self)) {
303 if (visible) {
304 webkit_web_inspector_show (self->priv->inspector);
305 } else {
306 webkit_web_inspector_close (self->priv->inspector);
307 }
308 }
309 }
310
311 static void
inspector_hide_window(GwhBrowser * self)312 inspector_hide_window (GwhBrowser *self)
313 {
314 if (gtk_widget_get_visible (self->priv->inspector_window)) {
315 gtk_window_get_position (GTK_WINDOW (self->priv->inspector_window),
316 &self->priv->inspector_window_x,
317 &self->priv->inspector_window_y);
318 gtk_widget_hide (self->priv->inspector_window);
319 }
320 }
321
322 static void
inspector_show_window(GwhBrowser * self)323 inspector_show_window (GwhBrowser *self)
324 {
325 if (! gtk_widget_get_visible (self->priv->inspector_window)) {
326 gtk_widget_show (self->priv->inspector_window);
327 gtk_window_move (GTK_WINDOW (self->priv->inspector_window),
328 self->priv->inspector_window_x,
329 self->priv->inspector_window_y);
330 }
331 }
332
333 static void
inspector_set_detached(GwhBrowser * self,gboolean detached)334 inspector_set_detached (GwhBrowser *self,
335 gboolean detached)
336 {
337 if (detached != INSPECTOR_DETACHED (self)) {
338 if (detached) {
339 gtk_widget_reparent (self->priv->inspector_view,
340 self->priv->inspector_window);
341 if (INSPECTOR_VISIBLE (self)) {
342 inspector_show_window (self);
343 }
344 } else {
345 gtk_widget_reparent (self->priv->inspector_view, self->priv->paned);
346 inspector_hide_window (self);
347 }
348 g_object_set (self->priv->settings, "inspector-detached", detached, NULL);
349 }
350 }
351
352 static WebKitWebView *
on_inspector_inspect_web_view(WebKitWebInspector * inspector,WebKitWebView * view,GwhBrowser * self)353 on_inspector_inspect_web_view (WebKitWebInspector *inspector,
354 WebKitWebView *view,
355 GwhBrowser *self)
356 {
357 if (self->priv->inspector_web_view) {
358 gtk_widget_destroy (self->priv->inspector_web_view);
359 }
360
361 self->priv->inspector_web_view = webkit_web_view_new ();
362 gtk_widget_show (self->priv->inspector_web_view);
363 gtk_container_add (GTK_CONTAINER (self->priv->inspector_view),
364 self->priv->inspector_web_view);
365
366 return WEBKIT_WEB_VIEW (self->priv->inspector_web_view);
367 }
368
369 static gboolean
on_inspector_show_window(WebKitWebInspector * inspector,GwhBrowser * self)370 on_inspector_show_window (WebKitWebInspector *inspector,
371 GwhBrowser *self)
372 {
373 gtk_widget_show (self->priv->inspector_view);
374 if (INSPECTOR_DETACHED (self)) {
375 inspector_show_window (self);
376 }
377 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (self->priv->item_inspector),
378 TRUE);
379
380 return TRUE;
381 }
382
383 static gboolean
on_inspector_close_window(WebKitWebInspector * inspector,GwhBrowser * self)384 on_inspector_close_window (WebKitWebInspector *inspector,
385 GwhBrowser *self)
386 {
387 gtk_widget_hide (self->priv->inspector_view);
388 inspector_hide_window (self);
389 gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (self->priv->item_inspector),
390 FALSE);
391 gtk_widget_grab_focus (gtk_widget_get_toplevel (self->priv->web_view));
392
393 return TRUE;
394 }
395
396 static gboolean
on_inspector_detach_window(WebKitWebInspector * inspector,GwhBrowser * self)397 on_inspector_detach_window (WebKitWebInspector *inspector,
398 GwhBrowser *self)
399 {
400 inspector_set_detached (self, TRUE);
401
402 return TRUE;
403 }
404
405 static gboolean
on_inspector_attach_window(WebKitWebInspector * inspector,GwhBrowser * self)406 on_inspector_attach_window (WebKitWebInspector *inspector,
407 GwhBrowser *self)
408 {
409 inspector_set_detached (self, FALSE);
410
411 return TRUE;
412 }
413
414 static gboolean
on_inspector_window_delete_event(GtkWidget * window,GdkEvent * event,GwhBrowser * self)415 on_inspector_window_delete_event (GtkWidget *window,
416 GdkEvent *event,
417 GwhBrowser *self)
418 {
419 webkit_web_inspector_close (self->priv->inspector);
420
421 return TRUE;
422 }
423
424 /* web view events hanlding */
425
426 static void
on_item_inspector_toggled(GtkToggleToolButton * button,GwhBrowser * self)427 on_item_inspector_toggled (GtkToggleToolButton *button,
428 GwhBrowser *self)
429 {
430 inspector_set_visible (self, gtk_toggle_tool_button_get_active (button));
431 }
432
433 static void
on_url_entry_activate(GtkEntry * entry,GwhBrowser * self)434 on_url_entry_activate (GtkEntry *entry,
435 GwhBrowser *self)
436 {
437 gwh_browser_set_uri (self, gtk_entry_get_text (entry));
438 }
439
440 static void
on_url_combo_active_notify(GtkComboBox * combo,GParamSpec * pspec,GwhBrowser * self)441 on_url_combo_active_notify (GtkComboBox *combo,
442 GParamSpec *pspec,
443 GwhBrowser *self)
444 {
445 if (gtk_combo_box_get_active (combo) != -1) {
446 const gchar *uri = gtk_entry_get_text (GTK_ENTRY (self->priv->url_entry));
447
448 gwh_browser_set_uri (self, uri);
449 }
450 }
451
452 static void
on_item_bookmark_toggled(GtkCheckMenuItem * item,GwhBrowser * self)453 on_item_bookmark_toggled (GtkCheckMenuItem *item,
454 GwhBrowser *self)
455 {
456 if (gtk_check_menu_item_get_active (item)) {
457 gwh_browser_add_bookmark (self, gwh_browser_get_uri (self));
458 } else {
459 gwh_browser_remove_bookmark (self, gwh_browser_get_uri (self));
460 }
461 }
462
463 static void
on_url_entry_icon_press(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEventButton * event,GwhBrowser * self)464 on_url_entry_icon_press (GtkEntry *entry,
465 GtkEntryIconPosition icon_pos,
466 GdkEventButton *event,
467 GwhBrowser *self)
468 {
469 if (icon_pos == GTK_ENTRY_ICON_PRIMARY) {
470 GtkWidget *menu = gtk_menu_new ();
471 GtkWidget *item;
472 const gchar *uri = gwh_browser_get_uri (self);
473
474 item = gtk_check_menu_item_new_with_mnemonic (_("Bookmark this website"));
475 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
476 gwh_browser_has_bookmark (self, uri));
477 g_signal_connect (item, "toggled",
478 G_CALLBACK (on_item_bookmark_toggled), self);
479 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
480 gtk_widget_show (item);
481
482 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
483 event->button, event->time);
484 }
485 }
486
487 static gboolean
on_entry_completion_match_selected(GtkEntryCompletion * comp,GtkTreeModel * model,GtkTreeIter * iter,GwhBrowser * self)488 on_entry_completion_match_selected (GtkEntryCompletion *comp,
489 GtkTreeModel *model,
490 GtkTreeIter *iter,
491 GwhBrowser *self)
492 {
493 gint column = gtk_entry_completion_get_text_column (comp);
494 gchar *row;
495
496 gtk_tree_model_get (model, iter, column, &row, -1);
497 /* set the entry value too in the unlikely case the selected URI is the
498 * currently viewed one, in which case set_uri() won't change it */
499 gtk_entry_set_text (GTK_ENTRY (self->priv->url_entry), row);
500 gwh_browser_set_uri (self, row);
501 g_free (row);
502
503 return TRUE;
504 }
505
506 static void
update_history(GwhBrowser * self)507 update_history (GwhBrowser *self)
508 {
509 WebKitWebView *web_view = WEBKIT_WEB_VIEW (self->priv->web_view);
510
511 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_prev),
512 webkit_web_view_can_go_back (web_view));
513 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_next),
514 webkit_web_view_can_go_forward (web_view));
515 }
516
517 static void
update_load_status(GwhBrowser * self)518 update_load_status (GwhBrowser *self)
519 {
520 gboolean loading = FALSE;
521 WebKitWebView *web_view = WEBKIT_WEB_VIEW (self->priv->web_view);
522
523 switch (webkit_web_view_get_load_status (web_view)) {
524 case WEBKIT_LOAD_PROVISIONAL:
525 case WEBKIT_LOAD_COMMITTED:
526 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
527 loading = TRUE;
528 break;
529
530 case WEBKIT_LOAD_FINISHED:
531 case WEBKIT_LOAD_FAILED:
532 loading = FALSE;
533 break;
534 }
535
536 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_reload), ! loading);
537 gtk_widget_set_visible (GTK_WIDGET (self->priv->item_reload), ! loading);
538 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_cancel), loading);
539 gtk_widget_set_visible (GTK_WIDGET (self->priv->item_cancel), loading);
540
541 update_history (self);
542 }
543
544 static void
on_web_view_load_status_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)545 on_web_view_load_status_notify (GObject *object,
546 GParamSpec *pspec,
547 GwhBrowser *self)
548 {
549 update_load_status (self);
550 }
551
552 static gboolean
on_web_view_load_error(WebKitWebView * web_view,WebKitWebFrame * web_frame,gchar * uri,gpointer web_error,GwhBrowser * self)553 on_web_view_load_error (WebKitWebView *web_view,
554 WebKitWebFrame *web_frame,
555 gchar *uri,
556 gpointer web_error,
557 GwhBrowser *self)
558 {
559 update_load_status (self);
560
561 return FALSE; /* we didn't really handled the error, so return %FALSE */
562 }
563
564 static void
on_web_view_uri_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)565 on_web_view_uri_notify (GObject *object,
566 GParamSpec *pspec,
567 GwhBrowser *self)
568 {
569 const gchar *uri;
570
571 uri = webkit_web_view_get_uri (WEBKIT_WEB_VIEW (self->priv->web_view));
572 gtk_entry_set_text (GTK_ENTRY (self->priv->url_entry), uri);
573 g_object_set (self->priv->settings, "browser-last-uri", uri, NULL);
574 update_history (self);
575 }
576
577 static void
on_web_view_icon_uri_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)578 on_web_view_icon_uri_notify (GObject *object,
579 GParamSpec *pspec,
580 GwhBrowser *self)
581 {
582 const gchar *icon_uri;
583
584 icon_uri = webkit_web_view_get_icon_uri (WEBKIT_WEB_VIEW (self->priv->web_view));
585 set_location_icon (self, icon_uri);
586 }
587
588 static void
on_web_view_progress_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)589 on_web_view_progress_notify (GObject *object,
590 GParamSpec *pspec,
591 GwhBrowser *self)
592 {
593 gdouble value;
594
595 value = webkit_web_view_get_progress (WEBKIT_WEB_VIEW (self->priv->web_view));
596 if (value >= 1.0) {
597 value = 0.0;
598 }
599 gtk_entry_set_progress_fraction (GTK_ENTRY (self->priv->url_entry), value);
600 }
601
602
603 static void
on_item_flip_orientation_activate(GtkMenuItem * item,GwhBrowser * self)604 on_item_flip_orientation_activate (GtkMenuItem *item,
605 GwhBrowser *self)
606 {
607 gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
608 gtk_orientable_get_orientation (GTK_ORIENTABLE (self)) == GTK_ORIENTATION_VERTICAL
609 ? GTK_ORIENTATION_HORIZONTAL
610 : GTK_ORIENTATION_VERTICAL);
611 }
612
613 static void
on_item_zoom_100_activate(GtkMenuItem * item,GwhBrowser * self)614 on_item_zoom_100_activate (GtkMenuItem *item,
615 GwhBrowser *self)
616 {
617 webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (self->priv->web_view), 1.0);
618 }
619
620 static void
on_item_full_content_zoom_activate(GtkCheckMenuItem * item,GwhBrowser * self)621 on_item_full_content_zoom_activate (GtkCheckMenuItem *item,
622 GwhBrowser *self)
623 {
624 webkit_web_view_set_full_content_zoom (WEBKIT_WEB_VIEW (self->priv->web_view),
625 gtk_check_menu_item_get_active (item));
626 }
627
628 static void
on_web_view_populate_popup(WebKitWebView * view,GtkMenu * menu,GwhBrowser * self)629 on_web_view_populate_popup (WebKitWebView *view,
630 GtkMenu *menu,
631 GwhBrowser *self)
632 {
633 GtkWidget *item;
634 GtkWidget *submenu;
635
636 #define ADD_SEPARATOR(menu) \
637 item = gtk_separator_menu_item_new (); \
638 gtk_widget_show (item); \
639 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item)
640
641 ADD_SEPARATOR (menu);
642
643 /* Zoom menu */
644 submenu = gtk_menu_new ();
645 item = gtk_menu_item_new_with_mnemonic (_("_Zoom"));
646 gtk_widget_show (item);
647 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
648 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
649 /* zoom in */
650 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_IN, NULL);
651 g_signal_connect_swapped (item, "activate",
652 G_CALLBACK (webkit_web_view_zoom_in), view);
653 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
654 /* zoom out */
655 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_OUT, NULL);
656 g_signal_connect_swapped (item, "activate",
657 G_CALLBACK (webkit_web_view_zoom_out), view);
658 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
659 /* zoom 1:1 */
660 ADD_SEPARATOR (submenu);
661 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_100, NULL);
662 g_signal_connect (item, "activate",
663 G_CALLBACK (on_item_zoom_100_activate), self);
664 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
665 /* full content zoom */
666 ADD_SEPARATOR (submenu);
667 item = gtk_check_menu_item_new_with_mnemonic (_("Full-_content zoom"));
668 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
669 webkit_web_view_get_full_content_zoom (view));
670 g_signal_connect (item, "activate",
671 G_CALLBACK (on_item_full_content_zoom_activate), self);
672 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
673 /* show zoom sumbenu */
674 gtk_widget_show_all (submenu);
675
676 ADD_SEPARATOR (menu);
677
678 item = gtk_menu_item_new_with_label (_("Flip panes orientation"));
679 g_signal_connect (item, "activate",
680 G_CALLBACK (on_item_flip_orientation_activate), self);
681 gtk_widget_show (item);
682 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
683 if (! INSPECTOR_VISIBLE (self) || INSPECTOR_DETACHED (self)) {
684 gtk_widget_set_sensitive (item, FALSE);
685 }
686
687 #undef ADD_SEPARATOR
688
689 g_signal_emit (self, signals[POPULATE_POPUP], 0, menu);
690 }
691
692 static gboolean
on_web_view_scroll_event(GtkWidget * widget,GdkEventScroll * event,GwhBrowser * self)693 on_web_view_scroll_event (GtkWidget *widget,
694 GdkEventScroll *event,
695 GwhBrowser *self)
696 {
697 guint mods = event->state & gtk_accelerator_get_default_mod_mask ();
698 gboolean handled = FALSE;
699
700 if (mods == GDK_CONTROL_MASK) {
701 handled = TRUE;
702 switch (event->direction) {
703 case GDK_SCROLL_DOWN:
704 webkit_web_view_zoom_out (WEBKIT_WEB_VIEW (self->priv->web_view));
705 break;
706
707 case GDK_SCROLL_UP:
708 webkit_web_view_zoom_in (WEBKIT_WEB_VIEW (self->priv->web_view));
709 break;
710
711 default:
712 handled = FALSE;
713 }
714 }
715
716 return handled;
717 }
718
719 static void
on_orientation_notify(GObject * object,GParamSpec * pspec,GwhBrowser * self)720 on_orientation_notify (GObject *object,
721 GParamSpec *pspec,
722 GwhBrowser *self)
723 {
724 g_object_set (G_OBJECT (self->priv->settings), "browser-orientation",
725 gtk_orientable_get_orientation (GTK_ORIENTABLE (self)), NULL);
726 }
727
728 static void
gwh_browser_destroy(GtkObject * object)729 gwh_browser_destroy (GtkObject *object)
730 {
731 GwhBrowser *self = GWH_BROWSER (object);
732 gchar *geometry;
733
734 /* save the setting now because we can't really set it at the time it changed,
735 * but it's not a problem, anyway probably nobody but us is interested by the
736 * geometry of our inspector window. */
737 geometry = get_web_inspector_window_geometry (self);
738 g_object_set (self->priv->settings, "inspector-window-geometry", geometry,
739 NULL);
740 g_free (geometry);
741
742 /* remove signal handlers that might get called during the destruction phase
743 * but that rely on stuff that might already heave been destroyed */
744 g_signal_handlers_disconnect_matched (self->priv->inspector,
745 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
746 self);
747 g_signal_handlers_disconnect_matched (self->priv->web_view,
748 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
749 self);
750 g_signal_handlers_disconnect_matched (self->priv->settings,
751 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
752 self);
753 /* also destroy the window, since it has no parent that will tell it to die */
754 gtk_widget_destroy (self->priv->inspector_window);
755
756 GTK_OBJECT_CLASS (gwh_browser_parent_class)->destroy (object);
757 }
758
759 static void
gwh_browser_finalize(GObject * object)760 gwh_browser_finalize (GObject *object)
761 {
762 GwhBrowser *self = GWH_BROWSER (object);
763
764 if (self->priv->default_icon) {
765 g_object_unref (self->priv->default_icon);
766 }
767 g_object_unref (self->priv->settings);
768 g_object_unref (self->priv->statusbar);
769 g_free (self->priv->hovered_link);
770
771 G_OBJECT_CLASS (gwh_browser_parent_class)->finalize (object);
772 }
773
774 static void
gwh_browser_constructed(GObject * object)775 gwh_browser_constructed (GObject *object)
776 {
777 GwhBrowser *self = GWH_BROWSER (object);
778
779 if (G_OBJECT_CLASS (gwh_browser_parent_class)->constructed) {
780 G_OBJECT_CLASS (gwh_browser_parent_class)->constructed (object);
781 }
782
783 /* a bit ugly, fake notifications */
784 g_object_notify (G_OBJECT (self->priv->settings), "browser-last-uri");
785 g_object_notify (G_OBJECT (self->priv->settings), "browser-bookmarks");
786 g_object_notify (G_OBJECT (self->priv->settings), "browser-orientation");
787 g_object_notify (G_OBJECT (self->priv->settings), "inspector-window-geometry");
788 }
789
790 static void
gwh_browser_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)791 gwh_browser_get_property (GObject *object,
792 guint prop_id,
793 GValue *value,
794 GParamSpec *pspec)
795 {
796 switch (prop_id) {
797 case PROP_INSPECTOR_TRANSIENT_FOR:
798 g_value_set_object (value,
799 gwh_browser_get_inspector_transient_for (GWH_BROWSER (object)));
800 break;
801
802 case PROP_URI:
803 g_value_set_string (value, gwh_browser_get_uri (GWH_BROWSER (object)));
804 break;
805
806 case PROP_ORIENTATION:
807 g_value_set_enum (value,
808 gtk_orientable_get_orientation (GTK_ORIENTABLE (GWH_BROWSER (object)->priv->paned)));
809 break;
810
811 case PROP_TOOLBAR:
812 g_value_set_object (value, GWH_BROWSER (object)->priv->toolbar);
813 break;
814
815 case PROP_WEB_VIEW:
816 g_value_set_object (value, GWH_BROWSER (object)->priv->web_view);
817 break;
818
819 default:
820 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
821 }
822 }
823
824 static void
gwh_browser_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)825 gwh_browser_set_property (GObject *object,
826 guint prop_id,
827 const GValue *value,
828 GParamSpec *pspec)
829 {
830 switch (prop_id) {
831 case PROP_INSPECTOR_TRANSIENT_FOR:
832 gwh_browser_set_inspector_transient_for (GWH_BROWSER (object),
833 g_value_get_object (value));
834 break;
835
836 case PROP_URI:
837 gwh_browser_set_uri (GWH_BROWSER (object), g_value_get_string (value));
838 break;
839
840 case PROP_ORIENTATION:
841 gtk_orientable_set_orientation (GTK_ORIENTABLE (GWH_BROWSER (object)->priv->paned),
842 g_value_get_enum (value));
843 break;
844
845 default:
846 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
847 }
848 }
849
850 static void
gwh_browser_show_all(GtkWidget * widget)851 gwh_browser_show_all (GtkWidget *widget)
852 {
853 /* don't show_all() on this widget, it would show the cancel or reload
854 * button */
855 gtk_widget_show (widget);
856 }
857
858 static void
gwh_browser_class_init(GwhBrowserClass * klass)859 gwh_browser_class_init (GwhBrowserClass *klass)
860 {
861 GObjectClass *object_class = G_OBJECT_CLASS (klass);
862 GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
863 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
864
865 object_class->finalize = gwh_browser_finalize;
866 object_class->constructed = gwh_browser_constructed;
867 object_class->get_property = gwh_browser_get_property;
868 object_class->set_property = gwh_browser_set_property;
869
870 gtkobject_class->destroy = gwh_browser_destroy;
871
872 widget_class->show_all = gwh_browser_show_all;
873
874 signals[POPULATE_POPUP] = g_signal_new ("populate-popup",
875 G_OBJECT_CLASS_TYPE (object_class),
876 G_SIGNAL_RUN_LAST,
877 G_STRUCT_OFFSET (GwhBrowserClass, populate_popup),
878 NULL, NULL,
879 g_cclosure_marshal_VOID__OBJECT,
880 G_TYPE_NONE, 1, GTK_TYPE_MENU);
881
882 g_object_class_override_property (object_class,
883 PROP_ORIENTATION,
884 "orientation");
885
886 g_object_class_install_property (object_class, PROP_INSPECTOR_TRANSIENT_FOR,
887 g_param_spec_object ("inspector-transient-for",
888 "Inspector transient for",
889 "The parent window of the inspector when detached",
890 GTK_TYPE_WINDOW,
891 G_PARAM_READWRITE));
892 g_object_class_install_property (object_class, PROP_URI,
893 g_param_spec_string ("uri",
894 "URI",
895 "The browser's URI",
896 NULL,
897 G_PARAM_READWRITE));
898 g_object_class_install_property (object_class, PROP_WEB_VIEW,
899 g_param_spec_object ("web-view",
900 "Web view",
901 "The browser's web view",
902 WEBKIT_TYPE_WEB_VIEW,
903 G_PARAM_READABLE));
904 g_object_class_install_property (object_class, PROP_TOOLBAR,
905 g_param_spec_object ("toolbar",
906 "Toolbar",
907 "The browser's toolbar",
908 GTK_TYPE_TOOLBAR,
909 G_PARAM_READABLE));
910
911 g_type_class_add_private (klass, sizeof (GwhBrowserPrivate));
912 }
913
914 /* a GtkEntryCompletionMatchFunc matching anywhere in the haystack */
915 static gboolean
url_completion_match_func(GtkEntryCompletion * comp,const gchar * key,GtkTreeIter * iter,gpointer dummy)916 url_completion_match_func (GtkEntryCompletion *comp,
917 const gchar *key,
918 GtkTreeIter *iter,
919 gpointer dummy)
920 {
921 GtkTreeModel *model = gtk_entry_completion_get_model (comp);
922 gint column = gtk_entry_completion_get_text_column (comp);
923 gchar *row = NULL;
924 gboolean match = FALSE;
925
926 gtk_tree_model_get (model, iter, column, &row, -1);
927 if (row) {
928 SETPTR (row, g_utf8_normalize (row, -1, G_NORMALIZE_DEFAULT));
929 SETPTR (row, g_utf8_casefold (row, -1));
930 match = strstr (row, key) != NULL;
931 g_free (row);
932 }
933
934 return match;
935 }
936
937 static GtkWidget *
create_toolbar(GwhBrowser * self)938 create_toolbar (GwhBrowser *self)
939 {
940 GtkWidget *toolbar;
941 GtkToolItem *item;
942 GtkEntryCompletion *comp;
943
944 toolbar = g_object_new (GTK_TYPE_TOOLBAR,
945 "icon-size", GTK_ICON_SIZE_MENU,
946 "toolbar-style", GTK_TOOLBAR_ICONS,
947 NULL);
948
949 self->priv->item_prev = gtk_tool_button_new_from_stock (GTK_STOCK_GO_BACK);
950 gtk_tool_item_set_tooltip_text (self->priv->item_prev, _("Back"));
951 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->item_prev, -1);
952 gtk_widget_show (GTK_WIDGET (self->priv->item_prev));
953 self->priv->item_next = gtk_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD);
954 gtk_tool_item_set_tooltip_text (self->priv->item_next, _("Forward"));
955 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->item_next, -1);
956 gtk_widget_show (GTK_WIDGET (self->priv->item_next));
957 self->priv->item_cancel = gtk_tool_button_new_from_stock (GTK_STOCK_CANCEL);
958 gtk_tool_item_set_tooltip_text (self->priv->item_cancel, _("Cancel loading"));
959 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->item_cancel, -1);
960 /* don't show cancel */
961 self->priv->item_reload = gtk_tool_button_new_from_stock (GTK_STOCK_REFRESH);
962 gtk_tool_item_set_tooltip_text (self->priv->item_reload, _("Reload current page"));
963 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->item_reload, -1);
964 gtk_widget_show (GTK_WIDGET (self->priv->item_reload));
965
966 self->priv->url_combo = gtk_combo_box_text_new_with_entry ();
967 item = gtk_tool_item_new ();
968 gtk_tool_item_set_is_important (item, TRUE);
969 gtk_container_add (GTK_CONTAINER (item), self->priv->url_combo);
970 gtk_tool_item_set_expand (item, TRUE);
971 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
972 gtk_widget_show_all (GTK_WIDGET (item));
973
974 self->priv->url_entry = gtk_bin_get_child (GTK_BIN (self->priv->url_combo));
975 set_location_icon (self, NULL);
976 gtk_entry_set_icon_tooltip_text (GTK_ENTRY (self->priv->url_entry),
977 GTK_ENTRY_ICON_PRIMARY,
978 _("Website information and settings"));
979
980 comp = gtk_entry_completion_new ();
981 gtk_entry_completion_set_model (comp, gtk_combo_box_get_model (GTK_COMBO_BOX (self->priv->url_combo)));
982 gtk_entry_completion_set_text_column (comp, gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (self->priv->url_combo)));
983 gtk_entry_completion_set_match_func (comp, url_completion_match_func, NULL, NULL);
984 gtk_entry_set_completion (GTK_ENTRY (self->priv->url_entry), comp);
985
986 self->priv->item_inspector = gtk_toggle_tool_button_new_from_stock (GTK_STOCK_INFO);
987 gtk_tool_button_set_label (GTK_TOOL_BUTTON (self->priv->item_inspector), _("Web inspector"));
988 gtk_tool_item_set_tooltip_text (self->priv->item_inspector, _("Toggle web inspector"));
989 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->item_inspector, -1);
990 gtk_widget_show (GTK_WIDGET (self->priv->item_inspector));
991
992 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_prev), FALSE);
993 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_next), FALSE);
994 gtk_widget_set_sensitive (GTK_WIDGET (self->priv->item_cancel), FALSE);
995
996 g_signal_connect_swapped (G_OBJECT (self->priv->item_prev), "clicked",
997 G_CALLBACK (webkit_web_view_go_back),
998 self->priv->web_view);
999 g_signal_connect_swapped (G_OBJECT (self->priv->item_next), "clicked",
1000 G_CALLBACK (webkit_web_view_go_forward),
1001 self->priv->web_view);
1002 g_signal_connect_swapped (G_OBJECT (self->priv->item_cancel), "clicked",
1003 G_CALLBACK (webkit_web_view_stop_loading),
1004 self->priv->web_view);
1005 g_signal_connect_swapped (G_OBJECT (self->priv->item_reload), "clicked",
1006 G_CALLBACK (webkit_web_view_reload),
1007 self->priv->web_view);
1008 g_signal_connect (G_OBJECT (self->priv->item_inspector), "toggled",
1009 G_CALLBACK (on_item_inspector_toggled), self);
1010 g_signal_connect (G_OBJECT (self->priv->url_entry), "activate",
1011 G_CALLBACK (on_url_entry_activate), self);
1012 g_signal_connect (G_OBJECT (self->priv->url_entry), "icon-press",
1013 G_CALLBACK (on_url_entry_icon_press), self);
1014 g_signal_connect (G_OBJECT (self->priv->url_combo), "notify::active",
1015 G_CALLBACK (on_url_combo_active_notify), self);
1016 g_signal_connect (G_OBJECT (comp), "match-selected",
1017 G_CALLBACK (on_entry_completion_match_selected), self);
1018
1019 return toolbar;
1020 }
1021
1022 static GtkWidget *
create_inspector_window(GwhBrowser * self)1023 create_inspector_window (GwhBrowser *self)
1024 {
1025 gboolean skips_taskbar;
1026 gboolean window_type;
1027
1028 g_object_get (self->priv->settings,
1029 "wm-secondary-windows-skip-taskbar", &skips_taskbar,
1030 "wm-secondary-windows-type", &window_type,
1031 NULL);
1032 self->priv->inspector_window_x = self->priv->inspector_window_y = 0;
1033 self->priv->inspector_window = g_object_new (GTK_TYPE_WINDOW,
1034 "type", GTK_WINDOW_TOPLEVEL,
1035 "skip-taskbar-hint", skips_taskbar,
1036 "type-hint", window_type,
1037 "title", _("Web inspector"),
1038 NULL);
1039 g_signal_connect (self->priv->inspector_window, "delete-event",
1040 G_CALLBACK (on_inspector_window_delete_event), self);
1041 g_signal_connect (self->priv->settings, "notify::wm-secondary-windows-skip-taskbar",
1042 G_CALLBACK (on_settings_wm_windows_skip_taskbar_notify), self);
1043 g_signal_connect (self->priv->settings, "notify::wm-secondary-windows-type",
1044 G_CALLBACK (on_settings_wm_windows_type_notify), self);
1045
1046 return self->priv->inspector_window;
1047 }
1048
1049 static guint
get_statusbar_context_id(GtkStatusbar * statusbar)1050 get_statusbar_context_id (GtkStatusbar *statusbar)
1051 {
1052 static guint id = 0;
1053
1054 if (id == 0) {
1055 id = gtk_statusbar_get_context_id (statusbar, "gwh-browser-hovered-link");
1056 }
1057
1058 return id;
1059 }
1060
1061 static void
on_web_view_hovering_over_link(WebKitWebView * view,gchar * title,gchar * uri,GwhBrowser * self)1062 on_web_view_hovering_over_link (WebKitWebView *view,
1063 gchar *title,
1064 gchar *uri,
1065 GwhBrowser *self)
1066 {
1067 GtkStatusbar *statusbar = GTK_STATUSBAR (self->priv->statusbar);
1068
1069 if (self->priv->hovered_link) {
1070 gtk_statusbar_pop (statusbar, get_statusbar_context_id (statusbar));
1071 g_free (self->priv->hovered_link);
1072 self->priv->hovered_link = NULL;
1073 }
1074 if (uri && *uri) {
1075 self->priv->hovered_link = g_strdup (uri);
1076 gtk_statusbar_push (statusbar, get_statusbar_context_id (statusbar),
1077 self->priv->hovered_link);
1078 }
1079 }
1080
1081 static void
on_web_view_leave_notify_event(GtkWidget * widget,GdkEventCrossing * event,GwhBrowser * self)1082 on_web_view_leave_notify_event (GtkWidget *widget,
1083 GdkEventCrossing *event,
1084 GwhBrowser *self)
1085 {
1086 if (self->priv->hovered_link) {
1087 GtkStatusbar *statusbar = GTK_STATUSBAR (self->priv->statusbar);
1088
1089 gtk_statusbar_pop (statusbar, get_statusbar_context_id (statusbar));
1090 }
1091 }
1092
1093 static void
on_web_view_enter_notify_event(GtkWidget * widget,GdkEventCrossing * event,GwhBrowser * self)1094 on_web_view_enter_notify_event (GtkWidget *widget,
1095 GdkEventCrossing *event,
1096 GwhBrowser *self)
1097 {
1098 if (self->priv->hovered_link) {
1099 GtkStatusbar *statusbar = GTK_STATUSBAR (self->priv->statusbar);
1100
1101 gtk_statusbar_push (statusbar, get_statusbar_context_id (statusbar),
1102 self->priv->hovered_link);
1103 }
1104 }
1105
1106 static void
on_web_view_realize(GtkWidget * widget,GwhBrowser * self)1107 on_web_view_realize (GtkWidget *widget,
1108 GwhBrowser *self)
1109 {
1110 gtk_widget_add_events (widget, GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK);
1111 }
1112
1113 static void
gwh_browser_init(GwhBrowser * self)1114 gwh_browser_init (GwhBrowser *self)
1115 {
1116 GtkWidget *scrolled;
1117 WebKitWebSettings *wkws;
1118 gboolean inspector_detached;
1119
1120 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GWH_TYPE_BROWSER,
1121 GwhBrowserPrivate);
1122
1123 self->priv->default_icon = NULL;
1124 /* web view need to be created first because we use it in create_toolbar() */
1125 self->priv->web_view = webkit_web_view_new ();
1126 wkws = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (self->priv->web_view));
1127 g_object_set (wkws, "enable-developer-extras", TRUE, NULL);
1128
1129 self->priv->settings = gwh_settings_get_default ();
1130 g_object_get (self->priv->settings,
1131 "inspector-detached", &inspector_detached,
1132 NULL);
1133
1134 self->priv->toolbar = create_toolbar (self);
1135 self->priv->paned = gtk_vpaned_new ();
1136 scrolled = gtk_scrolled_window_new (NULL, NULL);
1137 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
1138 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1139 gtk_container_add (GTK_CONTAINER (scrolled), self->priv->web_view);
1140
1141 gtk_box_pack_start (GTK_BOX (self), self->priv->toolbar, FALSE, TRUE, 0);
1142 gtk_widget_show (self->priv->toolbar);
1143 gtk_box_pack_start (GTK_BOX (self), self->priv->paned, TRUE, TRUE, 0);
1144 gtk_paned_pack1 (GTK_PANED (self->priv->paned), scrolled, TRUE, TRUE);
1145 gtk_widget_show_all (self->priv->paned);
1146
1147 self->priv->inspector_view = gtk_scrolled_window_new (NULL, NULL);
1148 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self->priv->inspector_view),
1149 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1150 self->priv->inspector_web_view = NULL;
1151
1152 self->priv->inspector_window = create_inspector_window (self);
1153 gtk_container_add (GTK_CONTAINER (inspector_detached
1154 ? self->priv->inspector_window
1155 : self->priv->paned),
1156 self->priv->inspector_view);
1157
1158 self->priv->statusbar = ui_lookup_widget (geany->main_widgets->window, "statusbar");
1159 if (self->priv->statusbar) {
1160 g_object_ref (self->priv->statusbar);
1161 } else {
1162 /* in the unlikely case we can't get the Geany statusbar, fake one */
1163 self->priv->statusbar = gtk_statusbar_new ();
1164 }
1165 self->priv->hovered_link = NULL;
1166
1167 g_signal_connect (self, "notify::orientation",
1168 G_CALLBACK (on_orientation_notify), self);
1169
1170 self->priv->inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (self->priv->web_view));
1171 g_signal_connect (self->priv->inspector, "inspect-web-view",
1172 G_CALLBACK (on_inspector_inspect_web_view), self);
1173 g_signal_connect (self->priv->inspector, "show-window",
1174 G_CALLBACK (on_inspector_show_window), self);
1175 g_signal_connect (self->priv->inspector, "close-window",
1176 G_CALLBACK (on_inspector_close_window), self);
1177 g_signal_connect (self->priv->inspector, "detach-window",
1178 G_CALLBACK (on_inspector_detach_window), self);
1179 g_signal_connect (self->priv->inspector, "attach-window",
1180 G_CALLBACK (on_inspector_attach_window), self);
1181
1182 g_signal_connect (G_OBJECT (self->priv->web_view), "notify::progress",
1183 G_CALLBACK (on_web_view_progress_notify), self);
1184 g_signal_connect (G_OBJECT (self->priv->web_view), "notify::uri",
1185 G_CALLBACK (on_web_view_uri_notify), self);
1186 g_signal_connect (G_OBJECT (self->priv->web_view), "notify::load-status",
1187 G_CALLBACK (on_web_view_load_status_notify), self);
1188 g_signal_connect (G_OBJECT (self->priv->web_view), "notify::load-error",
1189 G_CALLBACK (on_web_view_load_error), self);
1190 g_signal_connect (G_OBJECT (self->priv->web_view), "notify::icon-uri",
1191 G_CALLBACK (on_web_view_icon_uri_notify), self);
1192 g_signal_connect (G_OBJECT (self->priv->web_view), "populate-popup",
1193 G_CALLBACK (on_web_view_populate_popup), self);
1194 g_signal_connect (G_OBJECT (self->priv->web_view), "scroll-event",
1195 G_CALLBACK (on_web_view_scroll_event), self);
1196 g_signal_connect (G_OBJECT (self->priv->web_view), "hovering-over-link",
1197 G_CALLBACK (on_web_view_hovering_over_link), self);
1198 g_signal_connect (G_OBJECT (self->priv->web_view), "leave-notify-event",
1199 G_CALLBACK (on_web_view_leave_notify_event), self);
1200 g_signal_connect (G_OBJECT (self->priv->web_view), "enter-notify-event",
1201 G_CALLBACK (on_web_view_enter_notify_event), self);
1202 g_signal_connect_after (self->priv->web_view, "realize",
1203 G_CALLBACK (on_web_view_realize), self);
1204
1205 g_signal_connect (self->priv->web_view, "key-press-event",
1206 G_CALLBACK (gwh_keybindings_handle_event), self);
1207 g_signal_connect (self->priv->inspector_view, "key-press-event",
1208 G_CALLBACK (gwh_keybindings_handle_event), self);
1209
1210 gtk_widget_grab_focus (self->priv->url_entry);
1211
1212 g_signal_connect (self->priv->settings, "notify::browser-last-uri",
1213 G_CALLBACK (on_settings_browser_last_uri_notify), self);
1214 g_signal_connect (self->priv->settings, "notify::browser-bookmarks",
1215 G_CALLBACK (on_settings_browser_bookmarks_notify), self);
1216 g_signal_connect (self->priv->settings, "notify::browser-orientation",
1217 G_CALLBACK (on_settings_browser_orientation_notify), self);
1218 g_signal_connect (self->priv->settings, "notify::inspector-detached",
1219 G_CALLBACK (on_settings_inspector_detached_notify), self);
1220 g_signal_connect (self->priv->settings, "notify::inspector-window-geometry",
1221 G_CALLBACK (on_settings_inspector_window_geometry_notify), self);
1222 }
1223
1224
1225 /*----------------------------- Begin public API -----------------------------*/
1226
1227 GtkWidget *
gwh_browser_new(void)1228 gwh_browser_new (void)
1229 {
1230 return g_object_new (GWH_TYPE_BROWSER, NULL);
1231 }
1232
1233 void
gwh_browser_set_uri(GwhBrowser * self,const gchar * uri)1234 gwh_browser_set_uri (GwhBrowser *self,
1235 const gchar *uri)
1236 {
1237 gchar *real_uri;
1238 gchar *scheme;
1239
1240 g_return_if_fail (GWH_IS_BROWSER (self));
1241 g_return_if_fail (uri != NULL);
1242
1243 real_uri = g_strdup (uri);
1244 scheme = g_uri_parse_scheme (real_uri);
1245 if (! scheme) {
1246 gchar *tmp;
1247
1248 tmp = g_strconcat ("http://", uri, NULL);
1249 g_free (real_uri);
1250 real_uri = tmp;
1251 }
1252 g_free (scheme);
1253 if (g_strcmp0 (real_uri, gwh_browser_get_uri (self)) != 0) {
1254 webkit_web_view_open (WEBKIT_WEB_VIEW (self->priv->web_view), real_uri);
1255 g_object_notify (G_OBJECT (self), "uri");
1256 }
1257 g_free (real_uri);
1258 }
1259
1260 const gchar *
gwh_browser_get_uri(GwhBrowser * self)1261 gwh_browser_get_uri (GwhBrowser *self)
1262 {
1263 g_return_val_if_fail (GWH_IS_BROWSER (self), NULL);
1264
1265 return webkit_web_view_get_uri (WEBKIT_WEB_VIEW (self->priv->web_view));
1266 }
1267
1268 GtkToolbar *
gwh_browser_get_toolbar(GwhBrowser * self)1269 gwh_browser_get_toolbar (GwhBrowser *self)
1270 {
1271 g_return_val_if_fail (GWH_IS_BROWSER (self), NULL);
1272
1273 return GTK_TOOLBAR (self->priv->toolbar);
1274 }
1275
1276 WebKitWebView *
gwh_browser_get_web_view(GwhBrowser * self)1277 gwh_browser_get_web_view (GwhBrowser *self)
1278 {
1279 g_return_val_if_fail (GWH_IS_BROWSER (self), NULL);
1280
1281 return WEBKIT_WEB_VIEW (self->priv->web_view);
1282 }
1283
1284 void
gwh_browser_reload(GwhBrowser * self)1285 gwh_browser_reload (GwhBrowser *self)
1286 {
1287 g_return_if_fail (GWH_IS_BROWSER (self));
1288
1289 webkit_web_view_reload (WEBKIT_WEB_VIEW (self->priv->web_view));
1290 }
1291
1292 void
gwh_browser_set_inspector_transient_for(GwhBrowser * self,GtkWindow * window)1293 gwh_browser_set_inspector_transient_for (GwhBrowser *self,
1294 GtkWindow *window)
1295 {
1296 g_return_if_fail (GWH_IS_BROWSER (self));
1297 g_return_if_fail (window == NULL || GTK_IS_WINDOW (window));
1298
1299 gtk_window_set_transient_for (GTK_WINDOW (self->priv->inspector_window),
1300 window);
1301 }
1302
1303 GtkWindow *
gwh_browser_get_inspector_transient_for(GwhBrowser * self)1304 gwh_browser_get_inspector_transient_for (GwhBrowser *self)
1305 {
1306 g_return_val_if_fail (GWH_IS_BROWSER (self), NULL);
1307
1308 return gtk_window_get_transient_for (GTK_WINDOW (self->priv->inspector_window));
1309 }
1310
1311 void
gwh_browser_toggle_inspector(GwhBrowser * self)1312 gwh_browser_toggle_inspector (GwhBrowser *self)
1313 {
1314 g_return_if_fail (GWH_IS_BROWSER (self));
1315
1316 inspector_set_visible (self, ! INSPECTOR_VISIBLE (self));
1317 }
1318
1319 gchar **
gwh_browser_get_bookmarks(GwhBrowser * self)1320 gwh_browser_get_bookmarks (GwhBrowser *self)
1321 {
1322 gchar **bookmarks = NULL;
1323
1324 g_return_val_if_fail (GWH_IS_BROWSER (self), NULL);
1325
1326 g_object_get (self->priv->settings, "browser-bookmarks", &bookmarks, NULL);
1327
1328 return bookmarks;
1329 }
1330
1331 static void
gwh_browser_set_bookmarks(GwhBrowser * self,gchar ** bookmarks)1332 gwh_browser_set_bookmarks (GwhBrowser *self,
1333 gchar **bookmarks)
1334 {
1335 g_object_set (self->priv->settings, "browser-bookmarks", bookmarks, NULL);
1336 }
1337
1338 static gint
strv_index(gchar ** strv,const gchar * str)1339 strv_index (gchar **strv,
1340 const gchar *str)
1341 {
1342 g_return_val_if_fail (str != NULL, -1);
1343
1344 if (strv) {
1345 gint idx;
1346
1347 for (idx = 0; *strv; strv++, idx++) {
1348 if (strcmp (str, *strv) == 0) {
1349 return idx;
1350 }
1351 }
1352 }
1353
1354 return -1;
1355 }
1356
1357 gboolean
gwh_browser_has_bookmark(GwhBrowser * self,const gchar * uri)1358 gwh_browser_has_bookmark (GwhBrowser *self,
1359 const gchar *uri)
1360 {
1361 gchar **bookmarks = NULL;
1362 gboolean exists = FALSE;
1363
1364 g_return_val_if_fail (GWH_IS_BROWSER (self), FALSE);
1365 g_return_val_if_fail (uri != NULL, FALSE);
1366
1367 bookmarks = gwh_browser_get_bookmarks (self);
1368 exists = strv_index (bookmarks, uri) >= 0;
1369 g_strfreev (bookmarks);
1370
1371 return exists;
1372 }
1373
1374 static const gchar *
uri_skip_scheme(const gchar * uri)1375 uri_skip_scheme (const gchar *uri)
1376 {
1377 if (g_ascii_isalpha (*uri)) {
1378 do {
1379 uri++;
1380 } while (*uri == '+' || *uri == '-' || *uri == '.' ||
1381 g_ascii_isalnum (*uri));
1382 /* this is not strictly correct but good enough for what we do */
1383 while (*uri == ':' || *uri == '/')
1384 uri++;
1385 }
1386
1387 return uri;
1388 }
1389
1390 static int
sort_uris(gconstpointer a,gconstpointer b)1391 sort_uris (gconstpointer a,
1392 gconstpointer b)
1393 {
1394 const gchar *uri1 = uri_skip_scheme (*(const gchar *const *) a);
1395 const gchar *uri2 = uri_skip_scheme (*(const gchar *const *) b);
1396
1397 return g_ascii_strcasecmp (uri1, uri2);
1398 }
1399
1400 void
gwh_browser_add_bookmark(GwhBrowser * self,const gchar * uri)1401 gwh_browser_add_bookmark (GwhBrowser *self,
1402 const gchar *uri)
1403 {
1404 gchar **bookmarks = NULL;
1405
1406 g_return_if_fail (GWH_IS_BROWSER (self));
1407 g_return_if_fail (uri != NULL);
1408
1409 bookmarks = gwh_browser_get_bookmarks (self);
1410 if (strv_index (bookmarks, uri) < 0) {
1411 gsize length = bookmarks ? g_strv_length (bookmarks) : 0;
1412
1413 bookmarks = g_realloc (bookmarks, (length + 2) * sizeof *bookmarks);
1414 bookmarks[length] = g_strdup (uri);
1415 bookmarks[length + 1] = NULL;
1416 /* it would be faster to insert directly at the right place but who cares */
1417 qsort (bookmarks, length + 1, sizeof *bookmarks, sort_uris);
1418 gwh_browser_set_bookmarks (self, bookmarks);
1419 }
1420 g_strfreev (bookmarks);
1421 }
1422
1423 void
gwh_browser_remove_bookmark(GwhBrowser * self,const gchar * uri)1424 gwh_browser_remove_bookmark (GwhBrowser *self,
1425 const gchar *uri)
1426 {
1427 gchar **bookmarks = NULL;
1428 gint idx;
1429
1430 g_return_if_fail (GWH_IS_BROWSER (self));
1431 g_return_if_fail (uri != NULL);
1432
1433 bookmarks = gwh_browser_get_bookmarks (self);
1434 idx = strv_index (bookmarks, uri);
1435 if (idx >= 0) {
1436 gsize length = g_strv_length (bookmarks);
1437
1438 memmove (&bookmarks[idx], &bookmarks[idx + 1],
1439 (length - (gsize) idx) * sizeof *bookmarks);
1440 gwh_browser_set_bookmarks (self, bookmarks);
1441 }
1442 g_strfreev (bookmarks);
1443 }
1444