1 /* SPDX-FileCopyrightText: 2016-2020 - Sébastien Wilmet <swilmet@gnome.org>
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 */
4
5 #include "config.h"
6 #include "tepl-tab.h"
7 #include <glib/gi18n-lib.h>
8 #include "tepl-close-confirm-dialog-single.h"
9 #include "tepl-info-bar.h"
10 #include "tepl-tab-group.h"
11
12 /**
13 * SECTION:tab
14 * @Short_description: Contains a TeplView and GtkInfoBars
15 * @Title: TeplTab
16 * @See_also: #TeplInfoBar
17 *
18 * #TeplTab is meant to be the content of one tab in the text editor (if the
19 * text editor has a Tabbed Document Interface). It is a #GtkGrid container that
20 * contains the #TeplView and can contain one or several #GtkInfoBar's. Since it
21 * is a #GtkGrid, an application can of course add any other widget to it.
22 *
23 * To create a new #GtkInfoBar, it is recommended to use #TeplInfoBar (but
24 * #TeplTab doesn't enforce it).
25 *
26 * By default:
27 * - #TeplTab has a vertical #GtkOrientation.
28 * - The main child widget of #TeplTab is a #GtkScrolledWindow which contains
29 * the #TeplView.
30 * - #GtkInfoBar's are added on top of the #GtkScrolledWindow.
31 *
32 * The way that the #TeplView is packed into the #TeplTab is customizable with
33 * the ::pack_view virtual function. Similarly, the way that #GtkInfoBar's are
34 * added can be customized with ::pack_info_bar.
35 *
36 * # TeplTabGroup implementation
37 *
38 * #TeplTab implements the #TeplTabGroup interface, for a group of only one tab.
39 * It is useful for text editors that open each file in a separate window, or
40 * for applications that don't require to open more than one file. But the
41 * tepl_tab_group_append_tab() operation is not supported, so some higher-level
42 * features of Tepl don't work with #TeplTab as the #TeplTabGroup of the window.
43 * This will maybe be improved in the future by creating automatically a new
44 * window.
45 */
46
47 struct _TeplTabPrivate
48 {
49 GtkScrolledWindow *scrolled_window;
50 TeplView *view;
51 TeplGotoLineBar *goto_line_bar;
52 };
53
54 enum
55 {
56 PROP_0,
57 PROP_VIEW,
58 PROP_ACTIVE_TAB,
59 PROP_ACTIVE_VIEW,
60 PROP_ACTIVE_BUFFER,
61 };
62
63 enum
64 {
65 SIGNAL_CLOSE_REQUEST,
66 N_SIGNALS
67 };
68
69 static guint signals[N_SIGNALS];
70
71 static void tepl_tab_group_interface_init (gpointer g_iface,
72 gpointer iface_data);
73
G_DEFINE_TYPE_WITH_CODE(TeplTab,tepl_tab,GTK_TYPE_GRID,G_ADD_PRIVATE (TeplTab)G_IMPLEMENT_INTERFACE (TEPL_TYPE_TAB_GROUP,tepl_tab_group_interface_init))74 G_DEFINE_TYPE_WITH_CODE (TeplTab,
75 tepl_tab,
76 GTK_TYPE_GRID,
77 G_ADD_PRIVATE (TeplTab)
78 G_IMPLEMENT_INTERFACE (TEPL_TYPE_TAB_GROUP,
79 tepl_tab_group_interface_init))
80
81 static GtkScrolledWindow *
82 create_scrolled_window (void)
83 {
84 GtkScrolledWindow *scrolled_window;
85
86 scrolled_window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
87
88 /* Disable overlay scrolling, it doesn't work well with GtkTextView. For
89 * example to place the cursor with the mouse on the last character of a
90 * line.
91 */
92 gtk_scrolled_window_set_overlay_scrolling (scrolled_window, FALSE);
93
94 g_object_set (scrolled_window,
95 "expand", TRUE,
96 NULL);
97
98 gtk_widget_show (GTK_WIDGET (scrolled_window));
99
100 return scrolled_window;
101 }
102
103 static void
tepl_tab_pack_view_default(TeplTab * tab,TeplView * view)104 tepl_tab_pack_view_default (TeplTab *tab,
105 TeplView *view)
106 {
107 if (tab->priv->scrolled_window != NULL)
108 {
109 g_warning ("The TeplTab::pack_view virtual function can be called only once.");
110 return;
111 }
112
113 tab->priv->scrolled_window = create_scrolled_window ();
114 g_object_ref_sink (tab->priv->scrolled_window);
115
116 gtk_container_add (GTK_CONTAINER (tab->priv->scrolled_window),
117 GTK_WIDGET (view));
118
119 gtk_container_add (GTK_CONTAINER (tab),
120 GTK_WIDGET (tab->priv->scrolled_window));
121 }
122
123 static void
tepl_tab_pack_info_bar_default(TeplTab * tab,GtkInfoBar * info_bar)124 tepl_tab_pack_info_bar_default (TeplTab *tab,
125 GtkInfoBar *info_bar)
126 {
127 GtkWidget *sibling = NULL;
128
129 if (tab->priv->scrolled_window != NULL)
130 {
131 sibling = GTK_WIDGET (tab->priv->scrolled_window);
132 }
133
134 if (sibling != NULL)
135 {
136 gtk_grid_insert_next_to (GTK_GRID (tab), sibling, GTK_POS_TOP);
137
138 gtk_grid_attach_next_to (GTK_GRID (tab),
139 GTK_WIDGET (info_bar),
140 sibling,
141 GTK_POS_TOP,
142 1, 1);
143 }
144 else
145 {
146 gtk_container_add (GTK_CONTAINER (tab), GTK_WIDGET (info_bar));
147 }
148 }
149
150 static void
tepl_tab_pack_goto_line_bar_default(TeplTab * tab,TeplGotoLineBar * goto_line_bar)151 tepl_tab_pack_goto_line_bar_default (TeplTab *tab,
152 TeplGotoLineBar *goto_line_bar)
153 {
154 gtk_container_add (GTK_CONTAINER (tab), GTK_WIDGET (goto_line_bar));
155 }
156
157 static void
close_confirm_dialog_single_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)158 close_confirm_dialog_single_cb (GObject *source_object,
159 GAsyncResult *result,
160 gpointer user_data)
161 {
162 TeplTab *tab = TEPL_TAB (source_object);
163
164 if (_tepl_close_confirm_dialog_single_finish (tab, result))
165 {
166 gtk_widget_destroy (GTK_WIDGET (tab));
167 }
168 }
169
170 static void
tepl_tab_close_request_default(TeplTab * tab)171 tepl_tab_close_request_default (TeplTab *tab)
172 {
173 _tepl_close_confirm_dialog_single_async (tab, close_confirm_dialog_single_cb, NULL);
174 }
175
176 static void
buffer_notify_cb(GtkTextView * view,GParamSpec * pspec,TeplTab * tab)177 buffer_notify_cb (GtkTextView *view,
178 GParamSpec *pspec,
179 TeplTab *tab)
180 {
181 g_object_notify (G_OBJECT (tab), "active-buffer");
182 }
183
184 static void
set_view(TeplTab * tab,TeplView * view)185 set_view (TeplTab *tab,
186 TeplView *view)
187 {
188 if (view == NULL)
189 {
190 /* For tepl_tab_new(). */
191 view = TEPL_VIEW (tepl_view_new ());
192 gtk_widget_show (GTK_WIDGET (view));
193 }
194
195 g_return_if_fail (TEPL_IS_VIEW (view));
196
197 g_assert (tab->priv->view == NULL);
198 tab->priv->view = g_object_ref_sink (view);
199
200 TEPL_TAB_GET_CLASS (tab)->pack_view (tab, view);
201
202 g_signal_connect_object (view,
203 "notify::buffer",
204 G_CALLBACK (buffer_notify_cb),
205 tab,
206 0);
207
208 g_object_notify (G_OBJECT (tab), "view");
209 }
210
211 static void
tepl_tab_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)212 tepl_tab_get_property (GObject *object,
213 guint prop_id,
214 GValue *value,
215 GParamSpec *pspec)
216 {
217 TeplTab *tab = TEPL_TAB (object);
218 TeplTabGroup *tab_group = TEPL_TAB_GROUP (object);
219
220 switch (prop_id)
221 {
222 case PROP_VIEW:
223 g_value_set_object (value, tepl_tab_get_view (tab));
224 break;
225
226 case PROP_ACTIVE_TAB:
227 g_value_set_object (value, tepl_tab_group_get_active_tab (tab_group));
228 break;
229
230 case PROP_ACTIVE_VIEW:
231 g_value_set_object (value, tepl_tab_group_get_active_view (tab_group));
232 break;
233
234 case PROP_ACTIVE_BUFFER:
235 g_value_set_object (value, tepl_tab_group_get_active_buffer (tab_group));
236 break;
237
238 default:
239 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
240 break;
241 }
242 }
243
244 static void
tepl_tab_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)245 tepl_tab_set_property (GObject *object,
246 guint prop_id,
247 const GValue *value,
248 GParamSpec *pspec)
249 {
250 TeplTab *tab = TEPL_TAB (object);
251 TeplTabGroup *tab_group = TEPL_TAB_GROUP (object);
252
253 switch (prop_id)
254 {
255 case PROP_VIEW:
256 set_view (tab, g_value_get_object (value));
257 break;
258
259 case PROP_ACTIVE_TAB:
260 tepl_tab_group_set_active_tab (tab_group, g_value_get_object (value));
261 break;
262
263 default:
264 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
265 break;
266 }
267 }
268
269 static void
tepl_tab_dispose(GObject * object)270 tepl_tab_dispose (GObject *object)
271 {
272 TeplTab *tab = TEPL_TAB (object);
273
274 g_clear_object (&tab->priv->scrolled_window);
275 g_clear_object (&tab->priv->view);
276 g_clear_object (&tab->priv->goto_line_bar);
277
278 G_OBJECT_CLASS (tepl_tab_parent_class)->dispose (object);
279 }
280
281 static void
tepl_tab_class_init(TeplTabClass * klass)282 tepl_tab_class_init (TeplTabClass *klass)
283 {
284 GObjectClass *object_class = G_OBJECT_CLASS (klass);
285
286 object_class->get_property = tepl_tab_get_property;
287 object_class->set_property = tepl_tab_set_property;
288 object_class->dispose = tepl_tab_dispose;
289
290 klass->pack_view = tepl_tab_pack_view_default;
291 klass->pack_info_bar = tepl_tab_pack_info_bar_default;
292 klass->pack_goto_line_bar = tepl_tab_pack_goto_line_bar_default;
293 klass->close_request = tepl_tab_close_request_default;
294
295 /**
296 * TeplTab:view:
297 *
298 * The #TeplView contained in the tab. When this property is set, the
299 * ::pack_view virtual function is called.
300 *
301 * Since: 3.0
302 */
303 g_object_class_install_property (object_class,
304 PROP_VIEW,
305 g_param_spec_object ("view",
306 "View",
307 "",
308 TEPL_TYPE_VIEW,
309 G_PARAM_READWRITE |
310 G_PARAM_CONSTRUCT_ONLY |
311 G_PARAM_STATIC_STRINGS));
312
313 g_object_class_override_property (object_class, PROP_ACTIVE_TAB, "active-tab");
314 g_object_class_override_property (object_class, PROP_ACTIVE_VIEW, "active-view");
315 g_object_class_override_property (object_class, PROP_ACTIVE_BUFFER, "active-buffer");
316
317 /**
318 * TeplTab::close-request:
319 * @tab: the #TeplTab emitting the signal.
320 *
321 * The ::close-request signal is emitted when there is a request to
322 * close the #TeplTab, for example if the user clicks on a close button.
323 *
324 * The default object method handler does the following:
325 * - If the buffer is not modified (according to
326 * gtk_text_buffer_get_modified()), close the tab.
327 * - Else, show a message dialog to propose to save the file before
328 * closing.
329 *
330 * To override the default object method handler, either override the
331 * virtual function in a #TeplTab subclass or connect to the signal and
332 * call g_signal_stop_emission_by_name().
333 *
334 * Since: 3.0
335 */
336 signals[SIGNAL_CLOSE_REQUEST] =
337 g_signal_new ("close-request",
338 G_TYPE_FROM_CLASS (klass),
339 G_SIGNAL_RUN_LAST,
340 G_STRUCT_OFFSET (TeplTabClass, close_request),
341 NULL, NULL, NULL,
342 G_TYPE_NONE, 0);
343 }
344
345 static GList *
tepl_tab_get_tabs(TeplTabGroup * tab_group)346 tepl_tab_get_tabs (TeplTabGroup *tab_group)
347 {
348 return g_list_append (NULL, TEPL_TAB (tab_group));
349 }
350
351 static TeplTab *
tepl_tab_get_active_tab(TeplTabGroup * tab_group)352 tepl_tab_get_active_tab (TeplTabGroup *tab_group)
353 {
354 return TEPL_TAB (tab_group);
355 }
356
357 static void
tepl_tab_group_interface_init(gpointer g_iface,gpointer iface_data)358 tepl_tab_group_interface_init (gpointer g_iface,
359 gpointer iface_data)
360 {
361 TeplTabGroupInterface *interface = g_iface;
362
363 interface->get_tabs = tepl_tab_get_tabs;
364 interface->get_active_tab = tepl_tab_get_active_tab;
365 }
366
367 static void
tepl_tab_init(TeplTab * tab)368 tepl_tab_init (TeplTab *tab)
369 {
370 tab->priv = tepl_tab_get_instance_private (tab);
371
372 gtk_orientable_set_orientation (GTK_ORIENTABLE (tab), GTK_ORIENTATION_VERTICAL);
373 }
374
375 /**
376 * tepl_tab_new:
377 *
378 * Creates a new #TeplTab with a new #TeplView. The new #TeplView can be
379 * retrieved afterwards with tepl_tab_get_view().
380 *
381 * Returns: a new #TeplTab.
382 * Since: 3.0
383 */
384 TeplTab *
tepl_tab_new(void)385 tepl_tab_new (void)
386 {
387 return g_object_new (TEPL_TYPE_TAB, NULL);
388 }
389
390 /**
391 * tepl_tab_new_with_view:
392 * @view: the #TeplView that will be contained in the tab.
393 *
394 * Returns: a new #TeplTab.
395 * Since: 3.0
396 */
397 TeplTab *
tepl_tab_new_with_view(TeplView * view)398 tepl_tab_new_with_view (TeplView *view)
399 {
400 g_return_val_if_fail (TEPL_IS_VIEW (view), NULL);
401
402 return g_object_new (TEPL_TYPE_TAB,
403 "view", view,
404 NULL);
405 }
406
407 /**
408 * tepl_tab_get_view:
409 * @tab: a #TeplTab.
410 *
411 * Returns: (transfer none): the #TeplView contained in @tab.
412 * Since: 3.0
413 */
414 TeplView *
tepl_tab_get_view(TeplTab * tab)415 tepl_tab_get_view (TeplTab *tab)
416 {
417 g_return_val_if_fail (TEPL_IS_TAB (tab), NULL);
418
419 return tab->priv->view;
420 }
421
422 /**
423 * tepl_tab_get_buffer:
424 * @tab: a #TeplTab.
425 *
426 * A convenience function that calls gtk_text_view_get_buffer() on the
427 * #TeplTab:view associated with the @tab.
428 *
429 * Returns: (transfer none): the #TeplBuffer of the #TeplTab:view.
430 * Since: 3.0
431 */
432 TeplBuffer *
tepl_tab_get_buffer(TeplTab * tab)433 tepl_tab_get_buffer (TeplTab *tab)
434 {
435 g_return_val_if_fail (TEPL_IS_TAB (tab), NULL);
436
437 if (tab->priv->view == NULL)
438 {
439 return NULL;
440 }
441
442 return TEPL_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (tab->priv->view)));
443 }
444
445 /**
446 * tepl_tab_get_goto_line_bar:
447 * @tab: a #TeplTab.
448 *
449 * Gets the #TeplGotoLineBar widget belonging to @tab. The #TeplGotoLineBar must
450 * not be destroyed by the application, the purpose of this function is to
451 * show/hide the widget.
452 *
453 * Returns: (transfer none): the #TeplGotoLineBar widget belonging to @tab.
454 * Since: 5.0
455 */
456 TeplGotoLineBar *
tepl_tab_get_goto_line_bar(TeplTab * tab)457 tepl_tab_get_goto_line_bar (TeplTab *tab)
458 {
459 g_return_val_if_fail (TEPL_IS_TAB (tab), NULL);
460
461 if (tab->priv->goto_line_bar == NULL)
462 {
463 tab->priv->goto_line_bar = tepl_goto_line_bar_new ();
464 g_object_ref_sink (tab->priv->goto_line_bar);
465
466 /* The TeplGotoLineBar needs to be explicitly shown/hidden. */
467 gtk_widget_set_no_show_all (GTK_WIDGET (tab->priv->goto_line_bar), TRUE);
468
469 tepl_goto_line_bar_set_view (tab->priv->goto_line_bar,
470 tab->priv->view);
471
472 TEPL_TAB_GET_CLASS (tab)->pack_goto_line_bar (tab, tab->priv->goto_line_bar);
473 }
474
475 return tab->priv->goto_line_bar;
476 }
477
478 /**
479 * tepl_tab_add_info_bar:
480 * @tab: a #TeplTab.
481 * @info_bar: a #GtkInfoBar.
482 *
483 * Attaches @info_bar to @tab.
484 *
485 * This function calls the ::pack_info_bar virtual function.
486 *
487 * Since: 1.0
488 */
489 void
tepl_tab_add_info_bar(TeplTab * tab,GtkInfoBar * info_bar)490 tepl_tab_add_info_bar (TeplTab *tab,
491 GtkInfoBar *info_bar)
492 {
493 g_return_if_fail (TEPL_IS_TAB (tab));
494 g_return_if_fail (GTK_IS_INFO_BAR (info_bar));
495
496 _tepl_info_bar_set_size_request (info_bar);
497
498 TEPL_TAB_GET_CLASS (tab)->pack_info_bar (tab, info_bar);
499 }
500