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