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