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 "config.h"
21
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <glib.h>
25 #include <glib/gi18n-lib.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
28
29 #include <geanyplugin.h>
30 #include <geany.h>
31 #include <document.h>
32
33 #include "gwh-utils.h"
34 #include "gwh-browser.h"
35 #include "gwh-settings.h"
36 #include "gwh-plugin.h"
37 #include "gwh-keybindings.h"
38 #include "gwh-enum-types.h"
39
40
41 GeanyPlugin *geany_plugin;
42 GeanyData *geany_data;
43
44
45 PLUGIN_VERSION_CHECK(224)
46
47 PLUGIN_SET_TRANSLATABLE_INFO (
48 LOCALEDIR, GETTEXT_PACKAGE,
49 _("Web helper"),
50 _("Display a preview web page that gets updated upon document saving and "
51 "provide web analysis and debugging tools (aka Web Inspector), all using "
52 "WebKit."),
53 GWH_PLUGIN_VERSION,
54 "Colomban Wendling <ban@herbesfolles.org>"
55 )
56
57
58 enum {
59 CONTAINER_NOTEBOOK,
60 CONTAINER_WINDOW
61 };
62
63 static GtkWidget *G_browser = NULL;
64 static struct {
65 guint type;
66 GtkWidget *widget;
67
68 /* only valid if type == CONTAINER_WINDOW */
69 gboolean visible;
70 } G_container;
71 static GwhSettings *G_settings = NULL;
72
73
74 static void
separate_window_set_visible(gboolean visible)75 separate_window_set_visible (gboolean visible)
76 {
77 if (visible != G_container.visible) {
78 gchar *geometry;
79
80 G_container.visible = visible;
81 if (visible) {
82 gtk_widget_show (G_container.widget);
83 g_object_get (G_settings, "browser-separate-window-geometry", &geometry, NULL);
84 gwh_set_window_geometry (GTK_WINDOW (G_container.widget), geometry, NULL, NULL);
85 g_free (geometry);
86 } else {
87 geometry = gwh_get_window_geometry (GTK_WINDOW (G_container.widget), 0, 0);
88 g_object_set (G_settings, "browser-separate-window-geometry", geometry, NULL);
89 g_free (geometry);
90 gtk_widget_hide (G_container.widget);
91 }
92 }
93 }
94
95 static gboolean
on_separate_window_delete_event(GtkWidget * widget,GdkEvent * event,gpointer data)96 on_separate_window_delete_event (GtkWidget *widget,
97 GdkEvent *event,
98 gpointer data)
99 {
100 /* never honor delete-event */
101 return TRUE;
102 }
103
104 static void
on_separate_window_destroy(GtkWidget * widget,gpointer data)105 on_separate_window_destroy (GtkWidget *widget,
106 gpointer data)
107 {
108 gwh_browser_set_inspector_transient_for (GWH_BROWSER (G_browser), NULL);
109 gtk_container_remove (GTK_CONTAINER (G_container.widget), G_browser);
110 }
111
112 static gboolean
on_idle_widget_show(gpointer data)113 on_idle_widget_show (gpointer data)
114 {
115 separate_window_set_visible (TRUE);
116 /* present back the Geany's window because it is very unlikely the user
117 * expects the focus on our newly created window at this point, since we
118 * either just loaded the plugin or activated a element from Geany's UI */
119 gtk_window_present (GTK_WINDOW (geany_data->main_widgets->window));
120
121 return FALSE;
122 }
123
124 static GtkWidget *
create_separate_window(void)125 create_separate_window (void)
126 {
127 GtkWidget *window;
128 gboolean skips_taskbar;
129 gboolean is_transient;
130 gint window_type;
131
132 g_object_get (G_settings,
133 "wm-secondary-windows-skip-taskbar", &skips_taskbar,
134 "wm-secondary-windows-are-transient", &is_transient,
135 "wm-secondary-windows-type", &window_type,
136 NULL);
137 window = g_object_new (GTK_TYPE_WINDOW,
138 "type", GTK_WINDOW_TOPLEVEL,
139 "skip-taskbar-hint", skips_taskbar,
140 "title", _("Web view"),
141 "deletable", FALSE,
142 "type-hint", window_type,
143 NULL);
144 g_signal_connect (window, "delete-event",
145 G_CALLBACK (on_separate_window_delete_event), NULL);
146 g_signal_connect (window, "destroy",
147 G_CALLBACK (on_separate_window_destroy), NULL);
148 gtk_container_add (GTK_CONTAINER (window), G_browser);
149 if (is_transient) {
150 gtk_window_set_transient_for (GTK_WINDOW (window),
151 GTK_WINDOW (geany_data->main_widgets->window));
152 } else {
153 GList *icons;
154
155 icons = gtk_window_get_icon_list (GTK_WINDOW (geany_data->main_widgets->window));
156 gtk_window_set_icon_list (GTK_WINDOW (window), icons);
157 g_list_free (icons);
158 }
159 gwh_browser_set_inspector_transient_for (GWH_BROWSER (G_browser),
160 GTK_WINDOW (window));
161
162 return window;
163 }
164
165 static void
attach_browser(void)166 attach_browser (void)
167 {
168 GwhBrowserPosition position;
169
170 g_object_get (G_settings, "browser-position", &position, NULL);
171 if (position == GWH_BROWSER_POSITION_SEPARATE_WINDOW) {
172 G_container.type = CONTAINER_WINDOW;
173 G_container.widget = create_separate_window ();
174 /* seems that if a window is shown before it's transient parent, bad stuff
175 * happend. so, show our window a little later. */
176 g_idle_add (on_idle_widget_show, G_container.widget);
177 } else {
178 G_container.type = CONTAINER_NOTEBOOK;
179 if (position == GWH_BROWSER_POSITION_SIDEBAR) {
180 G_container.widget = geany_data->main_widgets->sidebar_notebook;
181 } else {
182 G_container.widget = geany_data->main_widgets->message_window_notebook;
183 }
184 gtk_notebook_append_page (GTK_NOTEBOOK (G_container.widget),
185 G_browser, gtk_label_new (_("Web preview")));
186 gwh_browser_set_inspector_transient_for (GWH_BROWSER (G_browser),
187 GTK_WINDOW (geany_data->main_widgets->window));
188 }
189 }
190
191 static void
detach_browser(void)192 detach_browser (void)
193 {
194 if (G_container.type == CONTAINER_WINDOW) {
195 separate_window_set_visible (FALSE); /* saves the geometry */
196 gtk_widget_destroy (G_container.widget);
197 } else {
198 gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (G_browser)),
199 G_browser);
200 }
201 }
202
203 static void
on_settings_browser_position_notify(GObject * object,GParamSpec * pspec,gpointer data)204 on_settings_browser_position_notify (GObject *object,
205 GParamSpec *pspec,
206 gpointer data)
207 {
208 g_object_ref (G_browser);
209 detach_browser ();
210 attach_browser ();
211 g_object_unref (G_browser);
212 }
213
214 static void
on_settings_windows_attrs_notify(GObject * object,GParamSpec * pspec,gpointer data)215 on_settings_windows_attrs_notify (GObject *object,
216 GParamSpec *pspec,
217 gpointer data)
218 {
219 /* recreate the window to apply the new attributes */
220 if (G_container.type == CONTAINER_WINDOW) {
221 g_object_ref (G_browser);
222 detach_browser ();
223 attach_browser ();
224 g_object_unref (G_browser);
225 }
226 }
227
228 static void
on_document_save(GObject * obj,GeanyDocument * doc,gpointer user_data)229 on_document_save (GObject *obj,
230 GeanyDocument *doc,
231 gpointer user_data)
232 {
233 gboolean auto_reload = FALSE;
234
235 g_object_get (G_OBJECT (G_settings), "browser-auto-reload", &auto_reload,
236 NULL);
237 if (auto_reload) {
238 gwh_browser_reload (GWH_BROWSER (G_browser));
239 }
240 }
241
242 static void
on_item_auto_reload_toggled(GtkCheckMenuItem * item,gpointer dummy)243 on_item_auto_reload_toggled (GtkCheckMenuItem *item,
244 gpointer dummy)
245 {
246 g_object_set (G_OBJECT (G_settings), "browser-auto-reload",
247 gtk_check_menu_item_get_active (item), NULL);
248 }
249
250 static void
on_browser_populate_popup(GwhBrowser * browser,GtkMenu * menu,gpointer dummy)251 on_browser_populate_popup (GwhBrowser *browser,
252 GtkMenu *menu,
253 gpointer dummy)
254 {
255 GtkWidget *item;
256 gboolean auto_reload = FALSE;
257
258 item = gtk_separator_menu_item_new ();
259 gtk_widget_show (item);
260 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
261
262 g_object_get (G_OBJECT (G_settings), "browser-auto-reload", &auto_reload,
263 NULL);
264 item = gtk_check_menu_item_new_with_mnemonic (_("Reload upon document saving"));
265 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), auto_reload);
266 gtk_widget_show (item);
267 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
268 g_signal_connect (item, "toggled", G_CALLBACK (on_item_auto_reload_toggled),
269 NULL);
270 }
271
272 static void
on_kb_toggle_inspector(guint key_id)273 on_kb_toggle_inspector (guint key_id)
274 {
275 gwh_browser_toggle_inspector (GWH_BROWSER (G_browser));
276 }
277
278 static void
on_kb_show_hide_separate_window(guint key_id)279 on_kb_show_hide_separate_window (guint key_id)
280 {
281 if (G_container.type == CONTAINER_WINDOW) {
282 separate_window_set_visible (! G_container.visible);
283 }
284 }
285
286 static void
on_kb_toggle_bookmark(guint key_id)287 on_kb_toggle_bookmark (guint key_id)
288 {
289 const gchar *uri = gwh_browser_get_uri (GWH_BROWSER (G_browser));
290
291 if (gwh_browser_has_bookmark (GWH_BROWSER (G_browser), uri)) {
292 gwh_browser_remove_bookmark (GWH_BROWSER (G_browser), uri);
293 } else {
294 gwh_browser_add_bookmark (GWH_BROWSER (G_browser), uri);
295 }
296 }
297
298
299 static gchar *
get_config_filename(void)300 get_config_filename (void)
301 {
302 return g_build_filename (geany_data->app->configdir, "plugins",
303 GWH_PLUGIN_TARNAME, GWH_PLUGIN_TARNAME".conf", NULL);
304 }
305
306 static void
load_config(void)307 load_config (void)
308 {
309 gchar *path;
310 GError *err = NULL;
311
312 G_settings = gwh_settings_get_default ();
313
314 gwh_settings_install_property (G_settings, g_param_spec_boolean (
315 "browser-auto-reload",
316 _("Browser auto reload"),
317 _("Whether the browser reloads itself upon document saving"),
318 TRUE,
319 G_PARAM_READWRITE));
320 gwh_settings_install_property (G_settings, g_param_spec_string (
321 "browser-last-uri",
322 _("Browser last URI"),
323 _("Last URI visited by the browser"),
324 "about:blank",
325 G_PARAM_READWRITE));
326 gwh_settings_install_property (G_settings, g_param_spec_boxed (
327 "browser-bookmarks",
328 _("Bookmarks"),
329 _("List of bookmarks"),
330 G_TYPE_STRV,
331 G_PARAM_READWRITE));
332 gwh_settings_install_property (G_settings, g_param_spec_enum (
333 "browser-orientation",
334 _("Browser orientation"),
335 _("Orientation of the browser widget"),
336 GTK_TYPE_ORIENTATION,
337 GTK_ORIENTATION_VERTICAL,
338 G_PARAM_READWRITE));
339 gwh_settings_install_property (G_settings, g_param_spec_enum (
340 "browser-position",
341 _("Browser position"),
342 _("Position of the browser widget in Geany's UI"),
343 GWH_TYPE_BROWSER_POSITION,
344 GWH_BROWSER_POSITION_MESSAGE_WINDOW,
345 G_PARAM_READWRITE));
346 gwh_settings_install_property (G_settings, g_param_spec_string (
347 "browser-separate-window-geometry",
348 _("Browser separate window geometry"),
349 _("Last geometry of the separated browser's window"),
350 "400x300",
351 G_PARAM_READWRITE));
352 gwh_settings_install_property (G_settings, g_param_spec_string (
353 "inspector-window-geometry",
354 _("Inspector window geometry"),
355 _("Last geometry of the inspector window"),
356 "400x300",
357 G_PARAM_READWRITE));
358 gwh_settings_install_property (G_settings, g_param_spec_boolean (
359 "inspector-detached",
360 _("Inspector detached"),
361 _("Whether the inspector is in a separate window or docked in the browser"),
362 FALSE,
363 G_PARAM_READWRITE));
364 gwh_settings_install_property (G_settings, g_param_spec_boolean (
365 "wm-secondary-windows-skip-taskbar",
366 _("Secondary windows skip task bar"),
367 _("Whether to tell the window manager not to show the secondary windows in the task bar"),
368 TRUE,
369 G_PARAM_READWRITE));
370 gwh_settings_install_property (G_settings, g_param_spec_boolean (
371 "wm-secondary-windows-are-transient",
372 _("Secondary windows are transient"),
373 _("Whether secondary windows are transient children of their parent"),
374 TRUE,
375 G_PARAM_READWRITE));
376 gwh_settings_install_property (G_settings, g_param_spec_enum (
377 "wm-secondary-windows-type",
378 _("Secondary windows type"),
379 _("The type of the secondary windows"),
380 GWH_TYPE_WINDOW_TYPE,
381 GWH_WINDOW_TYPE_NORMAL,
382 G_PARAM_READWRITE));
383
384 path = get_config_filename ();
385 if (! gwh_settings_load_from_file (G_settings, path, &err)) {
386 g_warning ("Failed to load configuration: %s", err->message);
387 g_error_free (err);
388 }
389 g_free (path);
390 }
391
392 static void
save_config(void)393 save_config (void)
394 {
395 gchar *path;
396 gchar *dirname;
397 GError *err = NULL;
398
399 path = get_config_filename ();
400 dirname = g_path_get_dirname (path);
401 utils_mkdir (dirname, TRUE);
402 g_free (dirname);
403 if (! gwh_settings_save_to_file (G_settings, path, &err)) {
404 g_warning ("Failed to save configuration: %s", err->message);
405 g_error_free (err);
406 }
407 g_free (path);
408 g_object_unref (G_settings);
409 G_settings = NULL;
410 }
411
412 void
plugin_init(GeanyData * data)413 plugin_init (GeanyData *data)
414 {
415 /* even though it's not really a good idea to keep all the library we load
416 * into memory, this is needed for webkit. first, without this we creash after
417 * module unloading, and webkitgtk inserts static data into the GLib
418 * (g_quark_from_static_string() for example) so it's not safe to remove it */
419 plugin_module_make_resident (geany_plugin);
420
421 /* webkit uses threads but don't initialize the thread system */
422 if (! g_thread_supported ()) {
423 g_thread_init (NULL);
424 }
425
426 load_config ();
427 gwh_keybindings_init ();
428
429 G_browser = gwh_browser_new ();
430 g_signal_connect (G_browser, "populate-popup",
431 G_CALLBACK (on_browser_populate_popup), NULL);
432
433 attach_browser ();
434 gtk_widget_show_all (G_browser);
435
436 plugin_signal_connect (geany_plugin, G_OBJECT (G_settings),
437 "notify::browser-position", FALSE,
438 G_CALLBACK (on_settings_browser_position_notify), NULL);
439 plugin_signal_connect (geany_plugin, G_OBJECT (G_settings),
440 "notify::wm-secondary-windows-skip-taskbar", FALSE,
441 G_CALLBACK (on_settings_windows_attrs_notify), NULL);
442 plugin_signal_connect (geany_plugin, G_OBJECT (G_settings),
443 "notify::wm-secondary-windows-are-transient", FALSE,
444 G_CALLBACK (on_settings_windows_attrs_notify), NULL);
445 plugin_signal_connect (geany_plugin, G_OBJECT (G_settings),
446 "notify::wm-secondary-windows-type", FALSE,
447 G_CALLBACK (on_settings_windows_attrs_notify), NULL);
448
449 plugin_signal_connect (geany_plugin, NULL, "document-save", TRUE,
450 G_CALLBACK (on_document_save), NULL);
451
452 /* add keybindings */
453 keybindings_set_item (gwh_keybindings_get_group (), GWH_KB_TOGGLE_INSPECTOR,
454 on_kb_toggle_inspector, GDK_F12, 0, "toggle_inspector",
455 _("Toggle Web Inspector"), NULL);
456 keybindings_set_item (gwh_keybindings_get_group (),
457 GWH_KB_SHOW_HIDE_SEPARATE_WINDOW,
458 on_kb_show_hide_separate_window, 0, 0,
459 "show_hide_separate_window",
460 _("Show/Hide Web View's Window"), NULL);
461 keybindings_set_item (gwh_keybindings_get_group (), GWH_KB_TOGGLE_BOOKMARK,
462 on_kb_toggle_bookmark, 0, 0, "toggle_bookmark",
463 _("Toggle bookmark for the current website"), NULL);
464 }
465
466 void
plugin_cleanup(void)467 plugin_cleanup (void)
468 {
469 detach_browser ();
470
471 gwh_keybindings_cleanup ();
472 save_config ();
473 }
474
475
476 typedef struct _GwhConfigDialog GwhConfigDialog;
477 struct _GwhConfigDialog
478 {
479 GtkWidget *browser_position;
480 GtkWidget *browser_auto_reload;
481
482 GtkWidget *secondary_windows_skip_taskbar;
483 GtkWidget *secondary_windows_are_transient;
484 GtkWidget *secondary_windows_type;
485 };
486
487 static void
on_configure_dialog_response(GtkDialog * dialog,gint response_id,GwhConfigDialog * cdialog)488 on_configure_dialog_response (GtkDialog *dialog,
489 gint response_id,
490 GwhConfigDialog *cdialog)
491 {
492 switch (response_id) {
493 case GTK_RESPONSE_ACCEPT:
494 case GTK_RESPONSE_APPLY:
495 case GTK_RESPONSE_OK:
496 case GTK_RESPONSE_YES: {
497 gwh_settings_widget_sync_v (G_settings,
498 cdialog->browser_position,
499 cdialog->browser_auto_reload,
500 cdialog->secondary_windows_skip_taskbar,
501 cdialog->secondary_windows_are_transient,
502 cdialog->secondary_windows_type,
503 NULL);
504 break;
505 }
506
507 default: break;
508 }
509
510 if (response_id != GTK_RESPONSE_APPLY) {
511 g_free (cdialog);
512 }
513 }
514
515 GtkWidget *
plugin_configure(GtkDialog * dialog)516 plugin_configure (GtkDialog *dialog)
517 {
518 GtkWidget *box1;
519 GtkWidget *box;
520 GtkWidget *alignment;
521 GwhConfigDialog *cdialog;
522
523 cdialog = g_malloc (sizeof *cdialog);
524
525 /* Top-level box, containing the different frames */
526 box1 = gtk_vbox_new (FALSE, 12);
527
528 /* Browser */
529 gtk_box_pack_start (GTK_BOX (box1), ui_frame_new_with_alignment (_("Browser"), &alignment), FALSE, FALSE, 0);
530 box = gtk_vbox_new (FALSE, 0);
531 gtk_container_add (GTK_CONTAINER (alignment), box);
532 /* browser position */
533 cdialog->browser_position = gwh_settings_widget_new (G_settings, "browser-position");
534 gtk_box_pack_start (GTK_BOX (box), cdialog->browser_position, FALSE, TRUE, 0);
535 /* auto-reload */
536 cdialog->browser_auto_reload = gwh_settings_widget_new (G_settings,
537 "browser-auto-reload");
538 gtk_box_pack_start (GTK_BOX (box), cdialog->browser_auto_reload, FALSE, TRUE, 0);
539
540 /* Windows */
541 gtk_box_pack_start (GTK_BOX (box1), ui_frame_new_with_alignment (_("Windows"), &alignment), FALSE, FALSE, 0);
542 box = gtk_vbox_new (FALSE, 0);
543 gtk_container_add (GTK_CONTAINER (alignment), box);
544 /* skip taskbar */
545 cdialog->secondary_windows_skip_taskbar = gwh_settings_widget_new (G_settings,
546 "wm-secondary-windows-skip-taskbar");
547 gtk_box_pack_start (GTK_BOX (box), cdialog->secondary_windows_skip_taskbar, FALSE, TRUE, 0);
548 /* tranisent */
549 cdialog->secondary_windows_are_transient = gwh_settings_widget_new (G_settings,
550 "wm-secondary-windows-are-transient");
551 gtk_box_pack_start (GTK_BOX (box), cdialog->secondary_windows_are_transient, FALSE, TRUE, 0);
552 /* type */
553 cdialog->secondary_windows_type = gwh_settings_widget_new (G_settings,
554 "wm-secondary-windows-type");
555 gtk_box_pack_start (GTK_BOX (box), cdialog->secondary_windows_type, FALSE, TRUE, 0);
556
557 g_signal_connect (dialog, "response",
558 G_CALLBACK (on_configure_dialog_response), cdialog);
559
560 return box1;
561 }
562