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