1 /*
2  * e-table-specification.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "e-table-specification.h"
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include <glib/gstdio.h>
24 
25 #include <libedataserver/libedataserver.h>
26 
27 #define E_TABLE_SPECIFICATION_GET_PRIVATE(obj) \
28 	(G_TYPE_INSTANCE_GET_PRIVATE \
29 	((obj), E_TYPE_TABLE_SPECIFICATION, ETableSpecificationPrivate))
30 
31 struct _ETableSpecificationPrivate {
32 	GPtrArray *columns;
33 	gchar *filename;
34 };
35 
36 enum {
37 	PROP_0,
38 	PROP_FILENAME
39 };
40 
41 /* Forward Declarations */
42 static void	e_table_specification_initable_init
43 						(GInitableIface *iface);
44 
G_DEFINE_TYPE_WITH_CODE(ETableSpecification,e_table_specification,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,e_table_specification_initable_init))45 G_DEFINE_TYPE_WITH_CODE (
46 	ETableSpecification,
47 	e_table_specification,
48 	G_TYPE_OBJECT,
49 	G_IMPLEMENT_INTERFACE (
50 		G_TYPE_INITABLE,
51 		e_table_specification_initable_init))
52 
53 static void
54 table_specification_start_specification (GMarkupParseContext *context,
55                                          const gchar *element_name,
56                                          const gchar **attribute_names,
57                                          const gchar **attribute_values,
58                                          ETableSpecification *specification,
59                                          GError **error)
60 {
61 	const gchar *cursor_mode = NULL;
62 	const gchar *selection_mode = NULL;
63 	gboolean fallback_draw_grid = FALSE;
64 	gboolean missing;
65 
66 	g_free (specification->click_to_add_message);
67 	specification->click_to_add_message = NULL;
68 
69 	g_free (specification->domain);
70 	specification->domain = NULL;
71 
72 	/* Use G_MARKUP_COLLECT_TRISTATE to identify
73 	 * missing attributes that default to TRUE. */
74 	g_markup_collect_attributes (
75 		element_name,
76 		attribute_names,
77 		attribute_values,
78 		error,
79 
80 		G_MARKUP_COLLECT_TRISTATE,
81 		"alternating-row-colors",
82 		&specification->alternating_row_colors,
83 
84 		G_MARKUP_COLLECT_BOOLEAN |
85 		G_MARKUP_COLLECT_OPTIONAL,
86 		"no-headers",
87 		&specification->no_headers,
88 
89 		G_MARKUP_COLLECT_BOOLEAN |
90 		G_MARKUP_COLLECT_OPTIONAL,
91 		"click-to-add",
92 		&specification->click_to_add,
93 
94 		G_MARKUP_COLLECT_BOOLEAN |
95 		G_MARKUP_COLLECT_OPTIONAL,
96 		"click-to-add-end",
97 		&specification->click_to_add_end,
98 
99 		G_MARKUP_COLLECT_TRISTATE,
100 		"horizontal-draw-grid",
101 		&specification->horizontal_draw_grid,
102 
103 		G_MARKUP_COLLECT_TRISTATE,
104 		"vertical-draw-grid",
105 		&specification->vertical_draw_grid,
106 
107 		G_MARKUP_COLLECT_BOOLEAN |
108 		G_MARKUP_COLLECT_OPTIONAL,
109 		"draw-grid",
110 		&fallback_draw_grid,
111 
112 		G_MARKUP_COLLECT_TRISTATE,
113 		"draw-focus",
114 		&specification->draw_focus,
115 
116 		G_MARKUP_COLLECT_BOOLEAN |
117 		G_MARKUP_COLLECT_OPTIONAL,
118 		"horizontal-scrolling",
119 		&specification->horizontal_scrolling,
120 
121 		G_MARKUP_COLLECT_BOOLEAN |
122 		G_MARKUP_COLLECT_OPTIONAL,
123 		"horizontal-resize",
124 		&specification->horizontal_resize,
125 
126 		G_MARKUP_COLLECT_TRISTATE,
127 		"allow-grouping",
128 		&specification->allow_grouping,
129 
130 		G_MARKUP_COLLECT_STRING |
131 		G_MARKUP_COLLECT_OPTIONAL,
132 		"selection-mode",
133 		&selection_mode,
134 
135 		G_MARKUP_COLLECT_STRING |
136 		G_MARKUP_COLLECT_OPTIONAL,
137 		"cursor-mode",
138 		&cursor_mode,
139 
140 		G_MARKUP_COLLECT_STRDUP |
141 		G_MARKUP_COLLECT_OPTIONAL,
142 		"_click-to-add-message",
143 		&specification->click_to_add_message,
144 
145 		G_MARKUP_COLLECT_STRDUP |
146 		G_MARKUP_COLLECT_OPTIONAL,
147 		"gettext-domain",
148 		&specification->domain,
149 
150 		G_MARKUP_COLLECT_INVALID);
151 
152 	/* Additional tweaks. */
153 
154 	missing =
155 		(specification->alternating_row_colors != TRUE) &&
156 		(specification->alternating_row_colors != FALSE);
157 	if (missing)
158 		specification->alternating_row_colors = TRUE;
159 
160 	if (!specification->click_to_add)
161 		specification->click_to_add_end = FALSE;
162 
163 	missing =
164 		(specification->horizontal_draw_grid != TRUE) &&
165 		(specification->horizontal_draw_grid != FALSE);
166 	if (missing)
167 		specification->horizontal_draw_grid = fallback_draw_grid;
168 
169 	missing =
170 		(specification->vertical_draw_grid != TRUE) &&
171 		(specification->vertical_draw_grid != FALSE);
172 	if (missing)
173 		specification->vertical_draw_grid = fallback_draw_grid;
174 
175 	missing =
176 		(specification->draw_focus != TRUE) &&
177 		(specification->draw_focus != FALSE);
178 	if (missing)
179 		specification->draw_focus = TRUE;
180 
181 	missing =
182 		(specification->allow_grouping != TRUE) &&
183 		(specification->allow_grouping != FALSE);
184 	if (missing)
185 		specification->allow_grouping = TRUE;
186 
187 	if (selection_mode == NULL)  /* attribute missing */
188 		specification->selection_mode = GTK_SELECTION_MULTIPLE;
189 	else if (g_ascii_strcasecmp (selection_mode, "single") == 0)
190 		specification->selection_mode = GTK_SELECTION_SINGLE;
191 	else if (g_ascii_strcasecmp (selection_mode, "browse") == 0)
192 		specification->selection_mode = GTK_SELECTION_BROWSE;
193 	else if (g_ascii_strcasecmp (selection_mode, "extended") == 0)
194 		specification->selection_mode = GTK_SELECTION_MULTIPLE;
195 	else  /* unrecognized attribute value */
196 		specification->selection_mode = GTK_SELECTION_MULTIPLE;
197 
198 	if (cursor_mode == NULL)  /* attribute missing */
199 		specification->cursor_mode = E_CURSOR_SIMPLE;
200 	else if (g_ascii_strcasecmp (cursor_mode, "line") == 0)
201 		specification->cursor_mode = E_CURSOR_LINE;
202 	else if (g_ascii_strcasecmp (cursor_mode, "spreadsheet") == 0)
203 		specification->cursor_mode = E_CURSOR_SPREADSHEET;
204 	else  /* unrecognized attribute value */
205 		specification->cursor_mode = E_CURSOR_SIMPLE;
206 
207 	if (specification->domain != NULL && *specification->domain == '\0') {
208 		g_free (specification->domain);
209 		specification->domain = NULL;
210 	}
211 }
212 
213 static void
table_specification_start_column(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,GPtrArray * columns,GError ** error)214 table_specification_start_column (GMarkupParseContext *context,
215                                   const gchar *element_name,
216                                   const gchar **attribute_names,
217                                   const gchar **attribute_values,
218                                   GPtrArray *columns,
219                                   GError **error)
220 {
221 	ETableColumnSpecification *column_spec;
222 	const gchar *model_col_str = NULL;
223 	const gchar *compare_col_str = NULL;
224 	const gchar *expansion_str = NULL;
225 	const gchar *minimum_width_str = NULL;
226 	const gchar *priority_str = NULL;
227 	gint64 int_value;
228 	gboolean missing;
229 
230 	column_spec = e_table_column_specification_new ();
231 
232 	/* Use G_MARKUP_COLLECT_TRISTATE to identify
233 	 * missing attributes that default to TRUE. */
234 	g_markup_collect_attributes (
235 		element_name,
236 		attribute_names,
237 		attribute_values,
238 		error,
239 
240 		G_MARKUP_COLLECT_STRING |
241 		G_MARKUP_COLLECT_OPTIONAL,
242 		"model_col",
243 		&model_col_str,
244 
245 		G_MARKUP_COLLECT_STRING |
246 		G_MARKUP_COLLECT_OPTIONAL,
247 		"compare_col",
248 		&compare_col_str,
249 
250 		G_MARKUP_COLLECT_STRDUP |
251 		G_MARKUP_COLLECT_OPTIONAL,
252 		"_title",
253 		&column_spec->title,
254 
255 		G_MARKUP_COLLECT_STRDUP |
256 		G_MARKUP_COLLECT_OPTIONAL,
257 		"pixbuf",
258 		&column_spec->pixbuf,
259 
260 		G_MARKUP_COLLECT_STRING |
261 		G_MARKUP_COLLECT_OPTIONAL,
262 		"expansion",
263 		&expansion_str,
264 
265 		G_MARKUP_COLLECT_STRING |
266 		G_MARKUP_COLLECT_OPTIONAL,
267 		"minimum_width",
268 		&minimum_width_str,
269 
270 		G_MARKUP_COLLECT_BOOLEAN |
271 		G_MARKUP_COLLECT_OPTIONAL,
272 		"resizable",
273 		&column_spec->resizable,
274 
275 		G_MARKUP_COLLECT_BOOLEAN |
276 		G_MARKUP_COLLECT_OPTIONAL,
277 		"disabled",
278 		&column_spec->disabled,
279 
280 		G_MARKUP_COLLECT_STRDUP |
281 		G_MARKUP_COLLECT_OPTIONAL,
282 		"cell",
283 		&column_spec->cell,
284 
285 		G_MARKUP_COLLECT_STRDUP |
286 		G_MARKUP_COLLECT_OPTIONAL,
287 		"compare",
288 		&column_spec->compare,
289 
290 		G_MARKUP_COLLECT_STRDUP |
291 		G_MARKUP_COLLECT_OPTIONAL,
292 		"search",
293 		&column_spec->search,
294 
295 		G_MARKUP_COLLECT_TRISTATE,
296 		"sortable",
297 		&column_spec->sortable,
298 
299 		G_MARKUP_COLLECT_STRING |
300 		G_MARKUP_COLLECT_OPTIONAL,
301 		"priority",
302 		&priority_str,
303 
304 		G_MARKUP_COLLECT_INVALID);
305 
306 	/* Additional tweaks. */
307 
308 	if (model_col_str != NULL) {
309 		int_value = g_ascii_strtoll (model_col_str, NULL, 10);
310 		column_spec->model_col = (gint) int_value;
311 		column_spec->compare_col = (gint) int_value;
312 	}
313 
314 	if (compare_col_str != NULL) {
315 		int_value = g_ascii_strtoll (compare_col_str, NULL, 10);
316 		column_spec->compare_col = (gint) int_value;
317 	}
318 
319 	if (column_spec->title == NULL)
320 		column_spec->title = g_strdup ("");
321 
322 	if (expansion_str != NULL)
323 		column_spec->expansion = g_ascii_strtod (expansion_str, NULL);
324 
325 	if (minimum_width_str != NULL) {
326 		int_value = g_ascii_strtoll (minimum_width_str, NULL, 10);
327 		column_spec->minimum_width = (gint) int_value;
328 	}
329 
330 	if (priority_str != NULL) {
331 		int_value = g_ascii_strtoll (priority_str, NULL, 10);
332 		column_spec->priority = (gint) int_value;
333 	}
334 
335 	missing =
336 		(column_spec->sortable != TRUE) &&
337 		(column_spec->sortable != FALSE);
338 	if (missing)
339 		column_spec->sortable = TRUE;
340 
341 	g_ptr_array_add (columns, g_object_ref (column_spec));
342 
343 	g_object_unref (column_spec);
344 }
345 
346 static void
table_specification_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)347 table_specification_start_element (GMarkupParseContext *context,
348                                    const gchar *element_name,
349                                    const gchar **attribute_names,
350                                    const gchar **attribute_values,
351                                    gpointer user_data,
352                                    GError **error)
353 {
354 	ETableSpecification *specification;
355 	GPtrArray *columns;
356 
357 	specification = E_TABLE_SPECIFICATION (user_data);
358 	columns = e_table_specification_ref_columns (specification);
359 
360 	if (g_str_equal (element_name, "ETableSpecification"))
361 		table_specification_start_specification (
362 			context,
363 			element_name,
364 			attribute_names,
365 			attribute_values,
366 			specification,
367 			error);
368 
369 	if (g_str_equal (element_name, "ETableColumn"))
370 		table_specification_start_column (
371 			context,
372 			element_name,
373 			attribute_names,
374 			attribute_values,
375 			columns,
376 			error);
377 
378 	if (g_str_equal (element_name, "ETableState"))
379 		e_table_state_parse_context_push (context, specification);
380 
381 	g_ptr_array_unref (columns);
382 }
383 
384 static void
table_specification_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)385 table_specification_end_element (GMarkupParseContext *context,
386                                  const gchar *element_name,
387                                  gpointer user_data,
388                                  GError **error)
389 {
390 	ETableSpecification *specification;
391 
392 	specification = E_TABLE_SPECIFICATION (user_data);
393 
394 	if (g_str_equal (element_name, "ETableState")) {
395 		ETableState *state;
396 
397 		state = e_table_state_parse_context_pop (context);
398 		g_return_if_fail (E_IS_TABLE_STATE (state));
399 
400 		g_clear_object (&specification->state);
401 		specification->state = g_object_ref (state);
402 
403 		g_object_unref (state);
404 	}
405 }
406 
407 static const GMarkupParser table_specification_parser = {
408 	table_specification_start_element,
409 	table_specification_end_element,
410 	NULL,
411 	NULL,
412 	NULL
413 };
414 
415 static void
table_specification_set_filename(ETableSpecification * specification,const gchar * filename)416 table_specification_set_filename (ETableSpecification *specification,
417                                   const gchar *filename)
418 {
419 	g_return_if_fail (filename != NULL);
420 	g_return_if_fail (specification->priv->filename == NULL);
421 
422 	specification->priv->filename = g_strdup (filename);
423 }
424 
425 static void
table_specification_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)426 table_specification_set_property (GObject *object,
427                                   guint property_id,
428                                   const GValue *value,
429                                   GParamSpec *pspec)
430 {
431 	switch (property_id) {
432 		case PROP_FILENAME:
433 			table_specification_set_filename (
434 				E_TABLE_SPECIFICATION (object),
435 				g_value_get_string (value));
436 			return;
437 	}
438 
439 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
440 }
441 
442 static void
table_specification_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)443 table_specification_get_property (GObject *object,
444                                   guint property_id,
445                                   GValue *value,
446                                   GParamSpec *pspec)
447 {
448 	switch (property_id) {
449 		case PROP_FILENAME:
450 			g_value_set_string (
451 				value,
452 				e_table_specification_get_filename (
453 				E_TABLE_SPECIFICATION (object)));
454 			return;
455 	}
456 
457 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
458 }
459 
460 static void
table_specification_dispose(GObject * object)461 table_specification_dispose (GObject *object)
462 {
463 	ETableSpecification *specification;
464 
465 	specification = E_TABLE_SPECIFICATION (object);
466 
467 	g_clear_object (&specification->state);
468 
469 	g_ptr_array_set_size (specification->priv->columns, 0);
470 
471 	/* Chain up to parent's dispose() method. */
472 	G_OBJECT_CLASS (e_table_specification_parent_class)->dispose (object);
473 }
474 
475 static void
table_specification_finalize(GObject * object)476 table_specification_finalize (GObject *object)
477 {
478 	ETableSpecification *specification;
479 
480 	specification = E_TABLE_SPECIFICATION (object);
481 
482 	g_free (specification->click_to_add_message);
483 	g_free (specification->domain);
484 
485 	g_ptr_array_unref (specification->priv->columns);
486 	g_free (specification->priv->filename);
487 
488 	/* Chain up to parent's finalize() method. */
489 	G_OBJECT_CLASS (e_table_specification_parent_class)->finalize (object);
490 }
491 
492 static gboolean
table_specification_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)493 table_specification_initable_init (GInitable *initable,
494                                    GCancellable *cancellable,
495                                    GError **error)
496 {
497 	ETableSpecification *specification;
498 	GMarkupParseContext *context;
499 	const gchar *filename;
500 	gchar *contents = NULL;
501 	gboolean success = FALSE;
502 
503 	specification = E_TABLE_SPECIFICATION (initable);
504 	filename = e_table_specification_get_filename (specification);
505 	g_return_val_if_fail (filename != NULL, FALSE);
506 
507 	if (!g_file_get_contents (filename, &contents, NULL, error)) {
508 		g_warn_if_fail (contents == NULL);
509 		return FALSE;
510 	}
511 
512 	context = g_markup_parse_context_new (
513 		&table_specification_parser,
514 		0,  /* no flags */
515 		g_object_ref (specification),
516 		(GDestroyNotify) g_object_unref);
517 
518 	if (g_markup_parse_context_parse (context, contents, -1, error))
519 		success = g_markup_parse_context_end_parse (context, error);
520 
521 	g_markup_parse_context_free (context);
522 
523 	if (specification->state == NULL)
524 		specification->state = e_table_state_vanilla (specification);
525 
526 	e_table_sort_info_set_can_group (
527 		specification->state->sort_info,
528 		specification->allow_grouping);
529 
530 	g_free (contents);
531 
532 	return success;
533 }
534 
535 static void
e_table_specification_class_init(ETableSpecificationClass * class)536 e_table_specification_class_init (ETableSpecificationClass *class)
537 {
538 	GObjectClass *object_class;
539 
540 	g_type_class_add_private (class, sizeof (ETableSpecificationPrivate));
541 
542 	object_class = G_OBJECT_CLASS (class);
543 	object_class->set_property = table_specification_set_property;
544 	object_class->get_property = table_specification_get_property;
545 	object_class->dispose = table_specification_dispose;
546 	object_class->finalize = table_specification_finalize;
547 
548 	g_object_class_install_property (
549 		object_class,
550 		PROP_FILENAME,
551 		g_param_spec_string (
552 			"filename",
553 			"Filename",
554 			"Name of the table specification file",
555 			NULL,
556 			G_PARAM_READWRITE |
557 			G_PARAM_CONSTRUCT_ONLY |
558 			G_PARAM_STATIC_STRINGS));
559 }
560 
561 static void
e_table_specification_initable_init(GInitableIface * iface)562 e_table_specification_initable_init (GInitableIface *iface)
563 {
564 	iface->init = table_specification_initable_init;
565 }
566 
567 static void
e_table_specification_init(ETableSpecification * specification)568 e_table_specification_init (ETableSpecification *specification)
569 {
570 	specification->priv =
571 		E_TABLE_SPECIFICATION_GET_PRIVATE (specification);
572 	specification->priv->columns =
573 		g_ptr_array_new_with_free_func (
574 		(GDestroyNotify) g_object_unref);
575 
576 	specification->alternating_row_colors = TRUE;
577 	specification->no_headers = FALSE;
578 	specification->click_to_add = FALSE;
579 	specification->click_to_add_end = FALSE;
580 	specification->horizontal_draw_grid = FALSE;
581 	specification->vertical_draw_grid = FALSE;
582 	specification->draw_focus = TRUE;
583 	specification->horizontal_scrolling = FALSE;
584 	specification->horizontal_resize = FALSE;
585 	specification->allow_grouping = TRUE;
586 
587 	specification->cursor_mode = E_CURSOR_SIMPLE;
588 	specification->selection_mode = GTK_SELECTION_MULTIPLE;
589 }
590 
591 /**
592  * e_table_specification_new:
593  * @filename: a table specification file
594  * @error: return location for a #GError, or %NULL
595  *
596  * Creates a new #ETableSpecification from @filename.  If a file or parse
597  * error occurs, the function sets @error and returns %NULL.
598  *
599  * Returns: an #ETableSpecification, or %NULL
600  */
601 ETableSpecification *
e_table_specification_new(const gchar * filename,GError ** error)602 e_table_specification_new (const gchar *filename,
603                            GError **error)
604 {
605 	return g_initable_new (
606 		E_TYPE_TABLE_SPECIFICATION, NULL, error,
607 		"filename", filename, NULL);
608 }
609 
610 /**
611  * e_table_specification_get_filename:
612  * @specification: an #ETableSpecification
613  *
614  * Returns the filename from which @specification was loaded.
615  *
616  * Returns: the table specification filename
617  **/
618 const gchar *
e_table_specification_get_filename(ETableSpecification * specification)619 e_table_specification_get_filename (ETableSpecification *specification)
620 {
621 	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
622 
623 	return specification->priv->filename;
624 }
625 
626 /**
627  * e_table_specification_ref_columns:
628  * @specification: an #ETableSpecification
629  *
630  * Returns a #GPtrArray containing #ETableColumnSpecification instances for
631  * all columns defined by @specification.  The array contents are owned by
632  * the @specification and should not be modified.  Unreference the array
633  * with g_ptr_array_unref() when finished with it.
634  *
635  * Returns: a #GPtrArray of #ETableColumnSpecification instances
636  **/
637 GPtrArray *
e_table_specification_ref_columns(ETableSpecification * specification)638 e_table_specification_ref_columns (ETableSpecification *specification)
639 {
640 	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
641 
642 	return g_ptr_array_ref (specification->priv->columns);
643 }
644 
645 /**
646  * e_table_specification_get_column_index:
647  * @specification: an #ETableSpecification
648  * @column_spec: an #ETableColumnSpecification
649  *
650  * Returns the zero-based index of @column_spec within @specification,
651  * or a negative value if @column_spec is not defined by @specification.
652  *
653  * Returns: the column index of @column_spec, or a negative value
654  **/
655 gint
e_table_specification_get_column_index(ETableSpecification * specification,ETableColumnSpecification * column_spec)656 e_table_specification_get_column_index (ETableSpecification *specification,
657                                         ETableColumnSpecification *column_spec)
658 {
659 	GPtrArray *columns;
660 	gint column_index = -1;
661 	guint ii;
662 
663 	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), -1);
664 	g_return_val_if_fail (E_IS_TABLE_COLUMN_SPECIFICATION (column_spec), -1);
665 
666 	columns = e_table_specification_ref_columns (specification);
667 
668 	for (ii = 0; ii < columns->len; ii++) {
669 		gboolean column_specs_equal;
670 
671 		column_specs_equal =
672 			e_table_column_specification_equal (
673 			column_spec, columns->pdata[ii]);
674 
675 		if (column_specs_equal) {
676 			column_index = (gint) ii;
677 			break;
678 		}
679 	}
680 
681 	g_ptr_array_unref (columns);
682 
683 	return column_index;
684 }
685 
686 /**
687  * e_table_specification_get_column_by_model_col:
688  * @specification: an #ETableSpecification
689  * @model_col: a model column index to get
690  *
691  * Get an #ETableColumnSpecification for the given @model_col.
692  *
693  * Returns: (transfer none) (nullable): an #ETableColumnSpecification for the given @model_col
694  *    or %NULL, when not found.
695  *
696  * Since: 3.42
697  **/
698 ETableColumnSpecification *
e_table_specification_get_column_by_model_col(ETableSpecification * specification,gint model_col)699 e_table_specification_get_column_by_model_col (ETableSpecification *specification,
700 					       gint model_col)
701 {
702 	GPtrArray *columns;
703 	ETableColumnSpecification *col_spec = NULL;
704 	guint ii;
705 
706 	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
707 
708 	columns = e_table_specification_ref_columns (specification);
709 
710 	for (ii = 0; ii < columns->len; ii++) {
711 		ETableColumnSpecification *adept = columns->pdata[ii];
712 
713 		if (adept && adept->model_col == model_col) {
714 			col_spec = adept;
715 			break;
716 		}
717 	}
718 
719 	g_ptr_array_unref (columns);
720 
721 	return col_spec;
722 }
723