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 (¬ebook->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 (¬ebook->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