1 /* SPDX-FileCopyrightText: 2017 - Sébastien Wilmet <swilmet@gnome.org>
2  * SPDX-License-Identifier: LGPL-3.0-or-later
3  */
4 
5 #include "tepl-notebook.h"
6 #include "tepl-abstract-factory.h"
7 #include "tepl-tab-group.h"
8 #include "tepl-tab.h"
9 #include "tepl-signal-group.h"
10 
11 /**
12  * SECTION:notebook
13  * @Short_description: Subclass of #GtkNotebook implementing the #TeplTabGroup
14  * interface
15  * @Title: TeplNotebook
16  *
17  * #TeplNotebook is a subclass of #GtkNotebook that implements the #TeplTabGroup
18  * interface.
19  */
20 
21 struct _TeplNotebookPrivate
22 {
23 	TeplSignalGroup *view_signal_group;
24 
25 	/* Not used for tepl_tab_group_get_active_tab(), used to avoid sending
26 	 * unnecessary notify signals.
27 	 * Unowned.
28 	 */
29 	TeplTab *active_tab;
30 };
31 
32 enum
33 {
34 	PROP_0,
35 	PROP_ACTIVE_TAB,
36 	PROP_ACTIVE_VIEW,
37 	PROP_ACTIVE_BUFFER,
38 };
39 
40 static void tepl_tab_group_interface_init (gpointer g_iface,
41 					   gpointer iface_data);
42 
G_DEFINE_TYPE_WITH_CODE(TeplNotebook,tepl_notebook,GTK_TYPE_NOTEBOOK,G_ADD_PRIVATE (TeplNotebook)G_IMPLEMENT_INTERFACE (TEPL_TYPE_TAB_GROUP,tepl_tab_group_interface_init))43 G_DEFINE_TYPE_WITH_CODE (TeplNotebook,
44 			 tepl_notebook,
45 			 GTK_TYPE_NOTEBOOK,
46 			 G_ADD_PRIVATE (TeplNotebook)
47 			 G_IMPLEMENT_INTERFACE (TEPL_TYPE_TAB_GROUP,
48 						tepl_tab_group_interface_init))
49 
50 static void
51 tepl_notebook_get_property (GObject    *object,
52 			    guint       prop_id,
53 			    GValue     *value,
54 			    GParamSpec *pspec)
55 {
56 	TeplTabGroup *tab_group = TEPL_TAB_GROUP (object);
57 
58 	switch (prop_id)
59 	{
60 		case PROP_ACTIVE_TAB:
61 			g_value_set_object (value, tepl_tab_group_get_active_tab (tab_group));
62 			break;
63 
64 		case PROP_ACTIVE_VIEW:
65 			g_value_set_object (value, tepl_tab_group_get_active_view (tab_group));
66 			break;
67 
68 		case PROP_ACTIVE_BUFFER:
69 			g_value_set_object (value, tepl_tab_group_get_active_buffer (tab_group));
70 			break;
71 
72 		default:
73 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
74 			break;
75 	}
76 }
77 
78 static void
tepl_notebook_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)79 tepl_notebook_set_property (GObject      *object,
80 			    guint         prop_id,
81 			    const GValue *value,
82 			    GParamSpec   *pspec)
83 {
84 	TeplTabGroup *tab_group = TEPL_TAB_GROUP (object);
85 
86 	switch (prop_id)
87 	{
88 		case PROP_ACTIVE_TAB:
89 			tepl_tab_group_set_active_tab (tab_group, g_value_get_object (value));
90 			break;
91 
92 		default:
93 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
94 			break;
95 	}
96 }
97 
98 static void
tepl_notebook_dispose(GObject * object)99 tepl_notebook_dispose (GObject *object)
100 {
101 	TeplNotebook *notebook = TEPL_NOTEBOOK (object);
102 
103 	_tepl_signal_group_clear (&notebook->priv->view_signal_group);
104 
105 	G_OBJECT_CLASS (tepl_notebook_parent_class)->dispose (object);
106 }
107 
108 static void
buffer_notify_cb(GtkTextView * view,GParamSpec * pspec,TeplNotebook * notebook)109 buffer_notify_cb (GtkTextView  *view,
110 		  GParamSpec   *pspec,
111 		  TeplNotebook *notebook)
112 {
113 	g_object_notify (G_OBJECT (notebook), "active-buffer");
114 }
115 
116 static void
check_active_tab_changed(TeplNotebook * notebook)117 check_active_tab_changed (TeplNotebook *notebook)
118 {
119 	TeplTab *active_tab;
120 	TeplView *active_view;
121 
122 	active_tab = tepl_tab_group_get_active_tab (TEPL_TAB_GROUP (notebook));
123 	if (notebook->priv->active_tab == active_tab)
124 	{
125 		return;
126 	}
127 
128 	notebook->priv->active_tab = active_tab;
129 
130 	_tepl_signal_group_clear (&notebook->priv->view_signal_group);
131 
132 	active_view = tepl_tab_group_get_active_view (TEPL_TAB_GROUP (notebook));
133 
134 	if (active_view != NULL)
135 	{
136 		notebook->priv->view_signal_group = _tepl_signal_group_new (G_OBJECT (active_view));
137 
138 		_tepl_signal_group_add (notebook->priv->view_signal_group,
139 					g_signal_connect (active_view,
140 							  "notify::buffer",
141 							  G_CALLBACK (buffer_notify_cb),
142 							  notebook));
143 	}
144 
145 	g_object_notify (G_OBJECT (notebook), "active-tab");
146 	g_object_notify (G_OBJECT (notebook), "active-view");
147 	g_object_notify (G_OBJECT (notebook), "active-buffer");
148 }
149 
150 static void
tepl_notebook_switch_page(GtkNotebook * notebook,GtkWidget * page,guint page_num)151 tepl_notebook_switch_page (GtkNotebook *notebook,
152 			   GtkWidget   *page,
153 			   guint        page_num)
154 {
155 	if (GTK_NOTEBOOK_CLASS (tepl_notebook_parent_class)->switch_page != NULL)
156 	{
157 		GTK_NOTEBOOK_CLASS (tepl_notebook_parent_class)->switch_page (notebook,
158 									      page,
159 									      page_num);
160 	}
161 
162 	check_active_tab_changed (TEPL_NOTEBOOK (notebook));
163 }
164 
165 static void
tepl_notebook_page_removed(GtkNotebook * notebook,GtkWidget * child,guint page_num)166 tepl_notebook_page_removed (GtkNotebook *notebook,
167 			    GtkWidget   *child,
168 			    guint        page_num)
169 {
170 	if (GTK_NOTEBOOK_CLASS (tepl_notebook_parent_class)->page_removed != NULL)
171 	{
172 		GTK_NOTEBOOK_CLASS (tepl_notebook_parent_class)->page_removed (notebook,
173 									       child,
174 									       page_num);
175 	}
176 
177 	check_active_tab_changed (TEPL_NOTEBOOK (notebook));
178 }
179 
180 static void
tepl_notebook_class_init(TeplNotebookClass * klass)181 tepl_notebook_class_init (TeplNotebookClass *klass)
182 {
183 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
184 	GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
185 
186 	object_class->get_property = tepl_notebook_get_property;
187 	object_class->set_property = tepl_notebook_set_property;
188 	object_class->dispose = tepl_notebook_dispose;
189 
190 	notebook_class->switch_page = tepl_notebook_switch_page;
191 	notebook_class->page_removed = tepl_notebook_page_removed;
192 
193 	g_object_class_override_property (object_class, PROP_ACTIVE_TAB, "active-tab");
194 	g_object_class_override_property (object_class, PROP_ACTIVE_VIEW, "active-view");
195 	g_object_class_override_property (object_class, PROP_ACTIVE_BUFFER, "active-buffer");
196 }
197 
198 static GList *
tepl_notebook_get_tabs(TeplTabGroup * tab_group)199 tepl_notebook_get_tabs (TeplTabGroup *tab_group)
200 {
201 	GtkNotebook *notebook = GTK_NOTEBOOK (tab_group);
202 	GList *tabs = NULL;
203 	gint n_pages;
204 	gint page_num;
205 
206 	n_pages = gtk_notebook_get_n_pages (notebook);
207 	for (page_num = n_pages - 1; page_num >= 0; page_num--)
208 	{
209 		GtkWidget *page_widget;
210 
211 		page_widget = gtk_notebook_get_nth_page (notebook, page_num);
212 		if (TEPL_IS_TAB (page_widget))
213 		{
214 			tabs = g_list_prepend (tabs, TEPL_TAB (page_widget));
215 		}
216 	}
217 
218 	return tabs;
219 }
220 
221 static TeplTab *
tepl_notebook_get_active_tab(TeplTabGroup * tab_group)222 tepl_notebook_get_active_tab (TeplTabGroup *tab_group)
223 {
224 	GtkNotebook *notebook = GTK_NOTEBOOK (tab_group);
225 	gint cur_page_num;
226 	GtkWidget *cur_page_widget;
227 
228 	cur_page_num = gtk_notebook_get_current_page (notebook);
229 	if (cur_page_num == -1)
230 	{
231 		return NULL;
232 	}
233 
234 	cur_page_widget = gtk_notebook_get_nth_page (notebook, cur_page_num);
235 	return TEPL_IS_TAB (cur_page_widget) ? TEPL_TAB (cur_page_widget) : NULL;
236 }
237 
238 static void
tepl_notebook_set_active_tab(TeplTabGroup * tab_group,TeplTab * tab)239 tepl_notebook_set_active_tab (TeplTabGroup *tab_group,
240 			      TeplTab      *tab)
241 {
242 	GtkNotebook *notebook = GTK_NOTEBOOK (tab_group);
243 	gint page_num;
244 
245 	page_num = gtk_notebook_page_num (notebook, GTK_WIDGET (tab));
246 	g_return_if_fail (page_num != -1);
247 
248 	if (!gtk_widget_get_visible (GTK_WIDGET (tab)))
249 	{
250 		g_warning ("Calling gtk_notebook_set_current_page() on an "
251 			   "invisible TeplTab. This won't work, make the "
252 			   "TeplTab visible first.");
253 	}
254 
255 	gtk_notebook_set_current_page (notebook, page_num);
256 }
257 
258 static void
tepl_notebook_append_tab_vfunc(TeplTabGroup * tab_group,TeplTab * tab)259 tepl_notebook_append_tab_vfunc (TeplTabGroup *tab_group,
260 				TeplTab      *tab)
261 {
262 	GtkNotebook *notebook = GTK_NOTEBOOK (tab_group);
263 	TeplAbstractFactory *factory;
264 	GtkWidget *tab_label;
265 
266 	factory = tepl_abstract_factory_get_singleton ();
267 	tab_label = tepl_abstract_factory_create_tab_label (factory, tab);
268 
269 	gtk_notebook_append_page (notebook, GTK_WIDGET (tab), tab_label);
270 }
271 
272 static void
tepl_tab_group_interface_init(gpointer g_iface,gpointer iface_data)273 tepl_tab_group_interface_init (gpointer g_iface,
274 			       gpointer iface_data)
275 {
276 	TeplTabGroupInterface *interface = g_iface;
277 
278 	interface->get_tabs = tepl_notebook_get_tabs;
279 	interface->get_active_tab = tepl_notebook_get_active_tab;
280 	interface->set_active_tab = tepl_notebook_set_active_tab;
281 	interface->append_tab_vfunc = tepl_notebook_append_tab_vfunc;
282 }
283 
284 static void
tepl_notebook_init(TeplNotebook * notebook)285 tepl_notebook_init (TeplNotebook *notebook)
286 {
287 	notebook->priv = tepl_notebook_get_instance_private (notebook);
288 
289 	/* The statusbar must always be at the bottom of the window (if there is
290 	 * a statusbar). More generally, the notebook is the main part of the
291 	 * window, so it needs to be expanded, to push other widgets on the
292 	 * sides, even if the notebook is empty.
293 	 */
294 	gtk_widget_set_hexpand (GTK_WIDGET (notebook), TRUE);
295 	gtk_widget_set_vexpand (GTK_WIDGET (notebook), TRUE);
296 
297 	gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
298 }
299 
300 /**
301  * tepl_notebook_new:
302  *
303  * Returns: (transfer floating): a new #TeplNotebook.
304  * Since: 3.0
305  */
306 GtkWidget *
tepl_notebook_new(void)307 tepl_notebook_new (void)
308 {
309 	return g_object_new (TEPL_TYPE_NOTEBOOK, NULL);
310 }
311