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