1 /* SPDX-FileCopyrightText: 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-space-drawer-prefs.h"
7 #include <glib/gi18n-lib.h>
8
9 /**
10 * SECTION:space-drawer-prefs
11 * @Short_description: Preferences widget for #GtkSourceSpaceDrawer
12 * @Title: TeplSpaceDrawerPrefs
13 *
14 * #TeplSpaceDrawerPrefs is a #GtkWidget for configuring the preferences about
15 * white space drawing with #GtkSourceSpaceDrawer.
16 *
17 * The configuration is stored in the #GtkSourceSpaceDrawer:matrix property of
18 * the associated #GtkSourceSpaceDrawer object.
19 */
20
21 struct _TeplSpaceDrawerPrefsPrivate
22 {
23 /* Owned */
24 GtkSourceSpaceDrawer *space_drawer;
25
26 /* First column */
27 GtkCheckButton *check_button_leading_tabs;
28 GtkCheckButton *check_button_leading_spaces;
29 GtkCheckButton *check_button_inside_text_tabs;
30 GtkCheckButton *check_button_inside_text_spaces;
31 GtkCheckButton *check_button_trailing_tabs;
32 GtkCheckButton *check_button_trailing_spaces;
33 GtkCheckButton *check_button_newlines;
34
35 /* Second column */
36 GtkGrid *second_column_vgrid;
37 };
38
39 G_DEFINE_TYPE_WITH_PRIVATE (TeplSpaceDrawerPrefs, tepl_space_drawer_prefs, GTK_TYPE_GRID)
40
41 static void matrix_notify_cb (GtkSourceSpaceDrawer *space_drawer,
42 GParamSpec *pspec,
43 TeplSpaceDrawerPrefs *prefs);
44
45 static void
tepl_space_drawer_prefs_dispose(GObject * object)46 tepl_space_drawer_prefs_dispose (GObject *object)
47 {
48 TeplSpaceDrawerPrefs *prefs = TEPL_SPACE_DRAWER_PREFS (object);
49
50 g_clear_object (&prefs->priv->space_drawer);
51
52 G_OBJECT_CLASS (tepl_space_drawer_prefs_parent_class)->dispose (object);
53 }
54
55 static void
tepl_space_drawer_prefs_class_init(TeplSpaceDrawerPrefsClass * klass)56 tepl_space_drawer_prefs_class_init (TeplSpaceDrawerPrefsClass *klass)
57 {
58 GObjectClass *object_class = G_OBJECT_CLASS (klass);
59
60 object_class->dispose = tepl_space_drawer_prefs_dispose;
61 }
62
63 static void
set_matrix_state_according_to_check_buttons(TeplSpaceDrawerPrefs * prefs)64 set_matrix_state_according_to_check_buttons (TeplSpaceDrawerPrefs *prefs)
65 {
66 GtkSourceSpaceTypeFlags space_types;
67
68 g_signal_handlers_block_by_func (prefs->priv->space_drawer, matrix_notify_cb, prefs);
69
70 space_types = GTK_SOURCE_SPACE_TYPE_NBSP;
71 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_leading_tabs)))
72 {
73 space_types |= GTK_SOURCE_SPACE_TYPE_TAB;
74 }
75 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_leading_spaces)))
76 {
77 space_types |= GTK_SOURCE_SPACE_TYPE_SPACE;
78 }
79 gtk_source_space_drawer_set_types_for_locations (prefs->priv->space_drawer,
80 GTK_SOURCE_SPACE_LOCATION_LEADING,
81 space_types);
82
83 space_types = GTK_SOURCE_SPACE_TYPE_NBSP;
84 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_inside_text_tabs)))
85 {
86 space_types |= GTK_SOURCE_SPACE_TYPE_TAB;
87 }
88 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_inside_text_spaces)))
89 {
90 space_types |= GTK_SOURCE_SPACE_TYPE_SPACE;
91 }
92 gtk_source_space_drawer_set_types_for_locations (prefs->priv->space_drawer,
93 GTK_SOURCE_SPACE_LOCATION_INSIDE_TEXT,
94 space_types);
95
96 space_types = GTK_SOURCE_SPACE_TYPE_NBSP;
97 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_trailing_tabs)))
98 {
99 space_types |= GTK_SOURCE_SPACE_TYPE_TAB;
100 }
101 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_trailing_spaces)))
102 {
103 space_types |= GTK_SOURCE_SPACE_TYPE_SPACE;
104 }
105 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (prefs->priv->check_button_newlines)))
106 {
107 space_types |= GTK_SOURCE_SPACE_TYPE_NEWLINE;
108 }
109 gtk_source_space_drawer_set_types_for_locations (prefs->priv->space_drawer,
110 GTK_SOURCE_SPACE_LOCATION_TRAILING,
111 space_types);
112
113 g_signal_handlers_unblock_by_func (prefs->priv->space_drawer, matrix_notify_cb, prefs);
114 }
115
116 static void
check_button_toggled_cb(GtkCheckButton * check_button,TeplSpaceDrawerPrefs * prefs)117 check_button_toggled_cb (GtkCheckButton *check_button,
118 TeplSpaceDrawerPrefs *prefs)
119 {
120 set_matrix_state_according_to_check_buttons (prefs);
121 }
122
123 static void
set_check_button_state(TeplSpaceDrawerPrefs * prefs,GtkCheckButton * check_button,gboolean is_active)124 set_check_button_state (TeplSpaceDrawerPrefs *prefs,
125 GtkCheckButton *check_button,
126 gboolean is_active)
127 {
128 g_signal_handlers_block_by_func (check_button, check_button_toggled_cb, prefs);
129 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), is_active);
130 g_signal_handlers_unblock_by_func (check_button, check_button_toggled_cb, prefs);
131 }
132
133 static void
set_check_buttons_state_according_to_matrix(TeplSpaceDrawerPrefs * prefs)134 set_check_buttons_state_according_to_matrix (TeplSpaceDrawerPrefs *prefs)
135 {
136 GtkSourceSpaceTypeFlags space_types;
137
138 space_types = gtk_source_space_drawer_get_types_for_locations (prefs->priv->space_drawer,
139 GTK_SOURCE_SPACE_LOCATION_LEADING);
140 set_check_button_state (prefs,
141 prefs->priv->check_button_leading_tabs,
142 space_types & GTK_SOURCE_SPACE_TYPE_TAB);
143 set_check_button_state (prefs,
144 prefs->priv->check_button_leading_spaces,
145 space_types & GTK_SOURCE_SPACE_TYPE_SPACE);
146
147 space_types = gtk_source_space_drawer_get_types_for_locations (prefs->priv->space_drawer,
148 GTK_SOURCE_SPACE_LOCATION_INSIDE_TEXT);
149 set_check_button_state (prefs,
150 prefs->priv->check_button_inside_text_tabs,
151 space_types & GTK_SOURCE_SPACE_TYPE_TAB);
152 set_check_button_state (prefs,
153 prefs->priv->check_button_inside_text_spaces,
154 space_types & GTK_SOURCE_SPACE_TYPE_SPACE);
155
156 space_types = gtk_source_space_drawer_get_types_for_locations (prefs->priv->space_drawer,
157 GTK_SOURCE_SPACE_LOCATION_TRAILING);
158 set_check_button_state (prefs,
159 prefs->priv->check_button_trailing_tabs,
160 space_types & GTK_SOURCE_SPACE_TYPE_TAB);
161 set_check_button_state (prefs,
162 prefs->priv->check_button_trailing_spaces,
163 space_types & GTK_SOURCE_SPACE_TYPE_SPACE);
164 set_check_button_state (prefs,
165 prefs->priv->check_button_newlines,
166 space_types & GTK_SOURCE_SPACE_TYPE_NEWLINE);
167 }
168
169 static GtkCheckButton *
create_check_button(TeplSpaceDrawerPrefs * prefs,const gchar * label)170 create_check_button (TeplSpaceDrawerPrefs *prefs,
171 const gchar *label)
172 {
173 GtkCheckButton *check_button;
174
175 check_button = GTK_CHECK_BUTTON (gtk_check_button_new_with_label (label));
176 gtk_widget_set_margin_start (GTK_WIDGET (check_button), 12);
177
178 g_signal_connect (check_button,
179 "toggled",
180 G_CALLBACK (check_button_toggled_cb),
181 prefs);
182
183 return check_button;
184 }
185
186 static void
init_check_buttons(TeplSpaceDrawerPrefs * prefs)187 init_check_buttons (TeplSpaceDrawerPrefs *prefs)
188 {
189 prefs->priv->check_button_leading_tabs = create_check_button (prefs, _("Draw tabs"));
190 prefs->priv->check_button_leading_spaces = create_check_button (prefs, _("Draw spaces"));
191 prefs->priv->check_button_inside_text_tabs = create_check_button (prefs, _("Draw tabs"));
192 prefs->priv->check_button_inside_text_spaces = create_check_button (prefs, _("Draw spaces"));
193 prefs->priv->check_button_trailing_tabs = create_check_button (prefs, _("Draw tabs"));
194 prefs->priv->check_button_trailing_spaces = create_check_button (prefs, _("Draw spaces"));
195 prefs->priv->check_button_newlines = create_check_button (prefs, _("Draw new lines"));
196
197 set_check_buttons_state_according_to_matrix (prefs);
198 }
199
200 static GtkWidget *
create_subtitle_label(const gchar * str)201 create_subtitle_label (const gchar *str)
202 {
203 gchar *str_escaped;
204 gchar *str_in_bold;
205 GtkWidget *label;
206
207 str_escaped = g_markup_escape_text (str, -1);
208 str_in_bold = g_strdup_printf ("<b>%s</b>", str_escaped);
209
210 label = gtk_label_new (str_in_bold);
211 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
212 gtk_widget_set_halign (label, GTK_ALIGN_START);
213
214 g_free (str_escaped);
215 g_free (str_in_bold);
216
217 return label;
218 }
219
220 static void
add_check_buttons(TeplSpaceDrawerPrefs * prefs)221 add_check_buttons (TeplSpaceDrawerPrefs *prefs)
222 {
223 GtkContainer *vgrid;
224
225 init_check_buttons (prefs);
226
227 vgrid = GTK_CONTAINER (gtk_grid_new ());
228 gtk_orientable_set_orientation (GTK_ORIENTABLE (vgrid), GTK_ORIENTATION_VERTICAL);
229 gtk_grid_set_row_spacing (GTK_GRID (vgrid), 6);
230
231 gtk_container_add (vgrid, create_subtitle_label (_("Leading Spaces")));
232 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_leading_tabs));
233 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_leading_spaces));
234
235 gtk_container_add (vgrid, create_subtitle_label (_("Spaces Inside Text")));
236 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_inside_text_tabs));
237 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_inside_text_spaces));
238
239 gtk_container_add (vgrid, create_subtitle_label (_("Trailing Spaces")));
240 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_trailing_tabs));
241 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_trailing_spaces));
242 gtk_container_add (vgrid, GTK_WIDGET (prefs->priv->check_button_newlines));
243
244 gtk_widget_show_all (GTK_WIDGET (vgrid));
245 gtk_container_add (GTK_CONTAINER (prefs), GTK_WIDGET (vgrid));
246 }
247
248 static void
matrix_notify_cb(GtkSourceSpaceDrawer * space_drawer,GParamSpec * pspec,TeplSpaceDrawerPrefs * prefs)249 matrix_notify_cb (GtkSourceSpaceDrawer *space_drawer,
250 GParamSpec *pspec,
251 TeplSpaceDrawerPrefs *prefs)
252 {
253 set_check_buttons_state_according_to_matrix (prefs);
254 }
255
256 static gchar *
result_viewer_get_buffer_content(void)257 result_viewer_get_buffer_content (void)
258 {
259 const gchar *tab_desc = _("Tab");
260 const gchar *space_desc = _("Space");
261 const gchar *nbsp_desc = _("No-Break Space");
262 const gchar *narrow_nbsp_desc = _("Narrow No-Break Space");
263
264 return g_strconcat ("\t", tab_desc, "\t", tab_desc, "\t\n",
265 " ", space_desc, " ", space_desc, " \n",
266 "\xC2\xA0", nbsp_desc, "\xC2\xA0", nbsp_desc, "\xC2\xA0\n",
267 "\xE2\x80\xAF", narrow_nbsp_desc, "\xE2\x80\xAF", narrow_nbsp_desc, "\xE2\x80\xAF",
268 NULL);
269 }
270
271 static void
add_result_viewer(TeplSpaceDrawerPrefs * prefs)272 add_result_viewer (TeplSpaceDrawerPrefs *prefs)
273 {
274 GtkSourceView *view;
275 GtkTextBuffer *buffer;
276 gchar *buffer_content;
277 GtkSourceSpaceDrawer *space_drawer;
278 GtkWidget *scrolled_window;
279
280 gtk_container_add (GTK_CONTAINER (prefs->priv->second_column_vgrid),
281 create_subtitle_label (_("Result")));
282
283 view = GTK_SOURCE_VIEW (gtk_source_view_new ());
284 gtk_source_view_set_show_line_numbers (view, TRUE);
285 gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE);
286 gtk_text_view_set_monospace (GTK_TEXT_VIEW (view), TRUE);
287
288 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
289 buffer_content = result_viewer_get_buffer_content ();
290 gtk_text_buffer_set_text (buffer, buffer_content, -1);
291 g_free (buffer_content);
292
293 space_drawer = gtk_source_view_get_space_drawer (view);
294 gtk_source_space_drawer_set_enable_matrix (space_drawer, TRUE);
295 g_object_bind_property (prefs->priv->space_drawer, "matrix",
296 space_drawer, "matrix",
297 G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
298
299 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
300 gtk_widget_set_size_request (scrolled_window, 500, 120);
301 gtk_widget_set_margin_start (scrolled_window, 12);
302 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
303 gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (scrolled_window), FALSE);
304 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (view));
305 gtk_container_add (GTK_CONTAINER (prefs->priv->second_column_vgrid), scrolled_window);
306 }
307
308 static void
add_information(TeplSpaceDrawerPrefs * prefs)309 add_information (TeplSpaceDrawerPrefs *prefs)
310 {
311 GtkLabel *label;
312
313 gtk_container_add (GTK_CONTAINER (prefs->priv->second_column_vgrid),
314 create_subtitle_label (_("Information")));
315
316 label = GTK_LABEL (gtk_label_new (_("When white space drawing is enabled, then non-breaking "
317 "spaces are always drawn at all locations, to distinguish "
318 "them from normal spaces.")));
319 gtk_widget_set_margin_start (GTK_WIDGET (label), 12);
320 gtk_widget_set_halign (GTK_WIDGET (label), GTK_ALIGN_START);
321 gtk_label_set_xalign (label, 0.0);
322 gtk_label_set_line_wrap (label, TRUE);
323 gtk_label_set_selectable (label, TRUE);
324 gtk_label_set_max_width_chars (label, 60);
325 gtk_container_add (GTK_CONTAINER (prefs->priv->second_column_vgrid), GTK_WIDGET (label));
326 }
327
328 static void
tepl_space_drawer_prefs_init(TeplSpaceDrawerPrefs * prefs)329 tepl_space_drawer_prefs_init (TeplSpaceDrawerPrefs *prefs)
330 {
331 prefs->priv = tepl_space_drawer_prefs_get_instance_private (prefs);
332
333 gtk_orientable_set_orientation (GTK_ORIENTABLE (prefs), GTK_ORIENTATION_HORIZONTAL);
334 gtk_grid_set_column_spacing (GTK_GRID (prefs), 24);
335
336 g_object_set (prefs,
337 "margin", 6,
338 NULL);
339
340 prefs->priv->space_drawer = gtk_source_space_drawer_new ();
341 gtk_source_space_drawer_set_enable_matrix (prefs->priv->space_drawer, TRUE);
342 gtk_source_space_drawer_set_types_for_locations (prefs->priv->space_drawer,
343 GTK_SOURCE_SPACE_LOCATION_ALL,
344 GTK_SOURCE_SPACE_TYPE_ALL &
345 ~GTK_SOURCE_SPACE_TYPE_NEWLINE);
346
347 add_check_buttons (prefs);
348
349 g_signal_connect_object (prefs->priv->space_drawer,
350 "notify::matrix",
351 G_CALLBACK (matrix_notify_cb),
352 prefs,
353 0);
354
355 prefs->priv->second_column_vgrid = GTK_GRID (gtk_grid_new ());
356 gtk_orientable_set_orientation (GTK_ORIENTABLE (prefs->priv->second_column_vgrid),
357 GTK_ORIENTATION_VERTICAL);
358 gtk_grid_set_row_spacing (prefs->priv->second_column_vgrid, 6);
359 gtk_container_add (GTK_CONTAINER (prefs), GTK_WIDGET (prefs->priv->second_column_vgrid));
360 add_result_viewer (prefs);
361 add_information (prefs);
362 gtk_widget_show_all (GTK_WIDGET (prefs->priv->second_column_vgrid));
363 }
364
365 /**
366 * tepl_space_drawer_prefs_new:
367 *
368 * Returns: (transfer floating): a new #TeplSpaceDrawerPrefs.
369 * Since: 6.0
370 */
371 TeplSpaceDrawerPrefs *
tepl_space_drawer_prefs_new(void)372 tepl_space_drawer_prefs_new (void)
373 {
374 return g_object_new (TEPL_TYPE_SPACE_DRAWER_PREFS, NULL);
375 }
376
377 /**
378 * tepl_space_drawer_prefs_get_space_drawer:
379 * @prefs: a #TeplSpaceDrawerPrefs.
380 *
381 * Gets the #GtkSourceSpaceDrawer associated with @prefs. The returned object is
382 * guaranteed to be the same for the lifetime of @prefs. Each
383 * #TeplSpaceDrawerPrefs object has a different #GtkSourceSpaceDrawer.
384 *
385 * Returns: (transfer none): the #GtkSourceSpaceDrawer associated with @prefs.
386 * Since: 6.0
387 */
388 GtkSourceSpaceDrawer *
tepl_space_drawer_prefs_get_space_drawer(TeplSpaceDrawerPrefs * prefs)389 tepl_space_drawer_prefs_get_space_drawer (TeplSpaceDrawerPrefs *prefs)
390 {
391 g_return_val_if_fail (TEPL_IS_SPACE_DRAWER_PREFS (prefs), NULL);
392
393 return prefs->priv->space_drawer;
394 }
395