1 /*
2 * @file browser_tabs.c internal browsing using multiple tabs
3 *
4 * Copyright (C) 2004-2012 Lars Windolf <lars.windolf@gmx.de>
5 * Copyright (C) 2006 Nathan Conrad <conrad@bungled.net>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #include "ui/browser_tabs.h"
23
24 #include <string.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "common.h"
28 #include "ui/liferea_shell.h"
29 #include "ui/item_list_view.h"
30 #include "ui/gedit-close-button.h"
31
32 /**
33 * tab_info_copy: (skip)
34 *
35 * Creates a copy of @tabInfo
36 */
tab_info_copy(gpointer orig)37 static gpointer tab_info_copy (gpointer orig)
38 {
39 tabInfo *a = orig;
40 tabInfo *b;
41 b = g_new0 (tabInfo, 1);
42 b->label = a->label;
43 b->widget = a->widget;
44 b->htmlview = a->htmlview;
45 // Shall we?
46 g_object_ref(b->label);
47 g_object_ref(b->widget);
48 g_object_ref(b->htmlview);
49 return b;
50 }
51
52 /**
53 * tab_info_free: (skip)
54 *
55 * free @tabInfo
56 */
tab_info_free(gpointer orig)57 static void tab_info_free (gpointer orig)
58 {
59 tabInfo *a = orig;
60 g_object_unref(a->label);
61 g_object_unref(a->widget);
62 g_object_unref(a->htmlview);
63 g_free(orig);
64 }
65
G_DEFINE_BOXED_TYPE(tabInfo,tab_info,tab_info_copy,tab_info_free)66 G_DEFINE_BOXED_TYPE(tabInfo, tab_info, tab_info_copy, tab_info_free)
67
68 // gslist type for gproperty
69 //https://git.gnome.org/browse/gobject-introspection/tree/tests/gimarshallingtests.c
70 static GType
71 gi_marshalling_boxed_gslist_get_type (void)
72 {
73 static GType type = 0;
74
75 if (type == 0) {
76 type = g_boxed_type_register_static ("GIMarshallingBoxedGSList",
77 (GBoxedCopyFunc) g_slist_copy,
78 (GBoxedFreeFunc) g_slist_free);
79 }
80
81 return type;
82 }
83
84 /* tab callbacks */
85
86 static void browser_tabs_close_tab (tabInfo *tab);
87
88 static gboolean
on_tab_key_press(GtkWidget * widget,GdkEventKey * event,gpointer data)89 on_tab_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data)
90 {
91 guint modifiers;
92
93 modifiers = gtk_accelerator_get_default_mod_mask ();
94 if ((event->keyval == GDK_KEY_w)
95 && ((event->state & modifiers) == GDK_CONTROL_MASK)) {
96 browser_tabs_close_tab ((tabInfo *)data);
97 return TRUE;
98 }
99
100 return FALSE;
101 }
102
103 /* browser tabs object */
104
105 #define BROWSER_TABS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), BROWSER_TABS_TYPE, BrowserTabsPrivate))
106
107 enum {
108 PROP_NONE,
109 PROP_NOTEBOOK,
110 PROP_HEAD_LINES,
111 PROP_TAB_INFO_LIST
112
113 };
114
115 struct BrowserTabsPrivate {
116 GtkNotebook *notebook;
117
118 GtkWidget *headlines; /*<< widget of the headlines tab */
119
120 GSList *list; /*<< tabInfo structures for all tabs */
121 };
122
123 static GObjectClass *parent_class = NULL;
124 static BrowserTabs *tabs = NULL;
125
126 G_DEFINE_TYPE (BrowserTabs, browser_tabs, G_TYPE_OBJECT);
127
128 /* Removes tab info structure */
129 static void
browser_tabs_remove_tab(tabInfo * tab)130 browser_tabs_remove_tab (tabInfo *tab)
131 {
132 tabs->priv->list = g_slist_remove (tabs->priv->list, tab);
133 g_object_unref (tab->htmlview);
134 g_free (tab);
135 }
136
137 static void
browser_tabs_finalize(GObject * object)138 browser_tabs_finalize (GObject *object)
139 {
140 BrowserTabs *bt = BROWSER_TABS (object);
141 GSList *iter = bt->priv->list;
142
143 while (iter) {
144 browser_tabs_remove_tab (iter->data);
145 iter = g_slist_next (iter);
146 }
147
148 G_OBJECT_CLASS (parent_class)->finalize (object);
149 }
150
151 static void
browser_tabs_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)152 browser_tabs_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
153 {
154 BrowserTabs *bt = BROWSER_TABS (object);
155
156 switch (prop_id) {
157 case PROP_NOTEBOOK:
158 g_value_set_object (value, bt->priv->notebook);
159 break;
160 case PROP_HEAD_LINES:
161 g_value_set_object (value, bt->priv->headlines);
162 break;
163 case PROP_TAB_INFO_LIST:
164 g_value_set_boxed (value, bt->priv->list);
165 break;
166 default:
167 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
168 break;
169 }
170 }
171
172 static void
browser_tabs_class_init(BrowserTabsClass * klass)173 browser_tabs_class_init (BrowserTabsClass *klass)
174 {
175 GObjectClass *object_class = G_OBJECT_CLASS (klass);
176
177 parent_class = g_type_class_peek_parent (klass);
178
179 object_class->get_property = browser_tabs_get_property;
180
181 object_class->finalize = browser_tabs_finalize;
182
183 /* BrowserTabs:notebook: */
184 g_object_class_install_property (
185 object_class,
186 PROP_NOTEBOOK,
187 g_param_spec_object (
188 "notebook",
189 "GtkNotebook",
190 "GtkNotebook object",
191 GTK_TYPE_NOTEBOOK,
192 G_PARAM_READABLE));
193
194 /* BrowserTabs:headlines: */
195 g_object_class_install_property (
196 object_class,
197 PROP_HEAD_LINES,
198 g_param_spec_object (
199 "head-lines",
200 "GtkWidget",
201 "GtkWidget object",
202 GTK_TYPE_WIDGET,
203 G_PARAM_READABLE));
204
205 /**
206 * BrowserTabs:tab-info-list: (type GSList(tabInfo)) (transfer none):
207 */
208 g_object_class_install_property (
209 object_class,
210 PROP_TAB_INFO_LIST,
211 g_param_spec_boxed(
212 "tab-info-list",
213 "SList of browser tab info",
214 "A GList of tab info containing htmlviews",
215 gi_marshalling_boxed_gslist_get_type(),
216 G_PARAM_READABLE));
217
218 g_type_class_add_private (object_class, sizeof(BrowserTabsPrivate));
219 }
220
221 static void
browser_tabs_init(BrowserTabs * bt)222 browser_tabs_init (BrowserTabs *bt)
223 {
224 /* globally accessible singleton */
225 g_assert (NULL == tabs);
226 tabs = bt;
227
228 tabs->priv = BROWSER_TABS_GET_PRIVATE (tabs);
229 }
230
231 BrowserTabs *
browser_tabs_create(GtkNotebook * notebook)232 browser_tabs_create (GtkNotebook *notebook)
233 {
234 g_object_new (BROWSER_TABS_TYPE, NULL);
235
236 tabs->priv->notebook = notebook;
237 tabs->priv->headlines = gtk_notebook_get_nth_page (notebook, 0);
238
239 gtk_notebook_set_show_tabs (tabs->priv->notebook, FALSE);
240
241 return tabs;
242 }
243
244 /* HTML view signal handlers */
245
246 static const gchar *
remove_string_prefix(const gchar * string,const gchar * prefix)247 remove_string_prefix (const gchar *string, const gchar *prefix)
248 {
249 int len;
250
251 len = strlen (prefix);
252
253 if (!strncmp (string, prefix, len))
254 string += len;
255
256 return string;
257 }
258
259 static const gchar *
create_label_text(const gchar * title)260 create_label_text (const gchar *title)
261 {
262 const gchar *tmp;
263
264 tmp = (title && *title) ? title : _("Untitled");
265
266 tmp = remove_string_prefix (tmp, "http://");
267 tmp = remove_string_prefix (tmp, "https://");
268
269 return tmp;
270 }
271
272 static void
on_htmlview_title_changed(gpointer object,gchar * title,gpointer user_data)273 on_htmlview_title_changed (gpointer object, gchar *title, gpointer user_data)
274 {
275 tabInfo *tab = (tabInfo *)user_data;
276
277 gtk_label_set_text (GTK_LABEL(tab->label), create_label_text (title));
278 }
279
280 static void
on_htmlview_close_tab(gpointer object,gpointer user_data)281 on_htmlview_close_tab (gpointer object, gpointer user_data)
282 {
283 browser_tabs_close_tab((tabInfo *)user_data);
284 }
285
286 /* Close tab and removes tab info structure */
287 static void
browser_tabs_close_tab(tabInfo * tab)288 browser_tabs_close_tab (tabInfo *tab)
289 {
290 int n = 0;
291 GList *iter, *list;
292
293 /* Find the tab index that needs to be closed */
294 iter = list = gtk_container_get_children (GTK_CONTAINER (tabs->priv->notebook));
295 while (iter) {
296 if (tab->widget == GTK_WIDGET (iter->data))
297 break;
298 n++;
299 iter = g_list_next (iter);
300 }
301 g_list_free (list);
302
303 if (iter) {
304 gtk_notebook_remove_page (tabs->priv->notebook, n);
305 browser_tabs_remove_tab (tab);
306 }
307
308 /* check if all tabs are closed */
309 if (1 == gtk_notebook_get_n_pages (tabs->priv->notebook))
310 gtk_notebook_set_show_tabs (tabs->priv->notebook, FALSE);
311 }
312
313 static void
on_htmlview_status_message(gpointer obj,gchar * url)314 on_htmlview_status_message (gpointer obj, gchar *url)
315 {
316 liferea_shell_set_important_status_bar ("%s", url);
317 }
318
319 /* single tab creation */
320
321 LifereaHtmlView *
browser_tabs_add_new(const gchar * url,const gchar * title,gboolean activate)322 browser_tabs_add_new (const gchar *url, const gchar *title, gboolean activate)
323 {
324 GtkWidget *close_button, *labelBox;
325 tabInfo *tab;
326 int i;
327
328 tab = g_new0 (tabInfo, 1);
329 tab->htmlview = liferea_htmlview_new (TRUE /* internal browsing */);
330 tab->widget = liferea_htmlview_get_widget (tab->htmlview);
331 tabs->priv->list = g_slist_append (tabs->priv->list, tab);
332
333 g_object_set_data (G_OBJECT (tab->widget), "tabInfo", tab);
334
335 g_signal_connect (tab->htmlview, "title-changed", G_CALLBACK (on_htmlview_title_changed), tab);
336 g_signal_connect (tab->htmlview, "statusbar-changed", G_CALLBACK (on_htmlview_status_message), NULL);
337
338 /* create tab widgets */
339
340 tab->label = gtk_label_new (create_label_text (title));
341 gtk_label_set_ellipsize (GTK_LABEL (tab->label), PANGO_ELLIPSIZE_END);
342 gtk_label_set_width_chars (GTK_LABEL (tab->label), 17);
343
344 labelBox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
345 gtk_box_pack_start (GTK_BOX (labelBox), tab->label, FALSE, FALSE, 0);
346
347 close_button = gedit_close_button_new ();
348 gtk_box_pack_end (GTK_BOX (labelBox), close_button, FALSE, FALSE, 0);
349 g_signal_connect ((gpointer)close_button, "clicked", G_CALLBACK (on_htmlview_close_tab), (gpointer)tab);
350
351 gtk_widget_show_all (labelBox);
352
353 i = gtk_notebook_append_page (tabs->priv->notebook, tab->widget, labelBox);
354 g_signal_connect (gtk_notebook_get_nth_page (tabs->priv->notebook, i),
355 "key-press-event", G_CALLBACK (on_tab_key_press), (gpointer)tab);
356 gtk_notebook_set_show_tabs (tabs->priv->notebook, TRUE);
357 gtk_notebook_set_tab_reorderable (tabs->priv->notebook, tab->widget, TRUE);
358
359 if (activate && (i != -1))
360 gtk_notebook_set_current_page (tabs->priv->notebook, i);
361
362 if (url)
363 liferea_htmlview_launch_URL_internal (tab->htmlview, (gchar *)url);
364
365 return tab->htmlview;
366 }
367
368 void
browser_tabs_show_headlines(void)369 browser_tabs_show_headlines (void)
370 {
371 gtk_notebook_set_current_page (tabs->priv->notebook, gtk_notebook_page_num (tabs->priv->notebook, tabs->priv->headlines));
372 }
373
374 LifereaHtmlView *
browser_tabs_get_active_htmlview(void)375 browser_tabs_get_active_htmlview (void)
376 {
377 tabInfo *tab;
378 gint current;
379
380 current = gtk_notebook_get_current_page (tabs->priv->notebook);
381 if (0 == current)
382 return NULL; /* never return the first page widget (because it is the item view) */
383
384 tab = g_object_get_data (G_OBJECT (gtk_notebook_get_nth_page (tabs->priv->notebook, current)), "tabInfo");
385 return tab->htmlview;
386 }
387
388 void
browser_tabs_do_zoom(gboolean in)389 browser_tabs_do_zoom (gboolean in)
390 {
391 liferea_htmlview_do_zoom (browser_tabs_get_active_htmlview (), in);
392 }
393