No issues found
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Chris Lahey <clahey@ximian.com>
18 * Miguel de Icaza <miguel@ximian.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 /*
25 * FIXME:
26 * Sort Dialog: when text is selected, the toggle button switches state.
27 * Make Clear all work.
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <gtk/gtk.h>
38
39 #include <glib/gi18n.h>
40 #include "e-util/e-util-private.h"
41 #include "e-util/e-util.h"
42 #include "e-util/e-unicode.h"
43
44 #include "e-table-config.h"
45 #include "e-table-memory-store.h"
46 #include "e-table-without.h"
47
48 G_DEFINE_TYPE (ETableConfig, e_table_config, G_TYPE_OBJECT)
49
50 enum {
51 CHANGED,
52 LAST_SIGNAL
53 };
54
55 enum {
56 PROP_0,
57 PROP_STATE
58 };
59
60 enum {
61 COLUMN_ITEM,
62 COLUMN_VALUE
63 };
64
65 static guint e_table_config_signals[LAST_SIGNAL] = { 0, };
66
67 static void
68 config_finalize (GObject *object)
69 {
70 ETableConfig *config = E_TABLE_CONFIG (object);
71
72 if (config->state)
73 g_object_unref (config->state);
74 config->state = NULL;
75
76 if (config->source_state)
77 g_object_unref (config->source_state);
78 config->source_state = NULL;
79
80 if (config->source_spec)
81 g_object_unref (config->source_spec);
82 config->source_spec = NULL;
83
84 g_free (config->header);
85 config->header = NULL;
86
87 g_slist_free (config->column_names);
88 config->column_names = NULL;
89
90 g_free (config->domain);
91 config->domain = NULL;
92
93 G_OBJECT_CLASS (e_table_config_parent_class)->finalize (object);
94 }
95
96 static void
97 e_table_config_changed (ETableConfig *config,
98 ETableState *state)
99 {
100 g_return_if_fail (E_IS_TABLE_CONFIG (config));
101
102 g_signal_emit (config, e_table_config_signals[CHANGED], 0, state);
103 }
104
105 static void
106 config_dialog_changed (ETableConfig *config)
107 {
108 /* enable the apply/ok buttons */
109 gtk_dialog_set_response_sensitive (
110 GTK_DIALOG (config->dialog_toplevel),
111 GTK_RESPONSE_APPLY, TRUE);
112 gtk_dialog_set_response_sensitive (
113 GTK_DIALOG (config->dialog_toplevel),
114 GTK_RESPONSE_OK, TRUE);
115 }
116
117 static void
118 config_get_property (GObject *object,
119 guint property_id,
120 GValue *value,
121 GParamSpec *pspec)
122 {
123 ETableConfig *config = E_TABLE_CONFIG (object);
124
125 switch (property_id) {
126 case PROP_STATE:
127 g_value_set_object (value, config->state);
128 break;
129 default:
130 break;
131 }
132 }
133
134 static void
135 e_table_config_class_init (ETableConfigClass *class)
136 {
137 GObjectClass *object_class = G_OBJECT_CLASS (class);
138
139 class->changed = NULL;
140
141 object_class->finalize = config_finalize;
142 object_class->get_property = config_get_property;
143
144 e_table_config_signals[CHANGED] = g_signal_new (
145 "changed",
146 G_TYPE_FROM_CLASS (object_class),
147 G_SIGNAL_RUN_LAST,
148 G_STRUCT_OFFSET (ETableConfigClass, changed),
149 (GSignalAccumulator) NULL, NULL,
150 g_cclosure_marshal_VOID__VOID,
151 G_TYPE_NONE, 0);
152
153 g_object_class_install_property (
154 object_class,
155 PROP_STATE,
156 g_param_spec_object (
157 "state",
158 "State",
159 NULL,
160 E_TYPE_TABLE_STATE,
161 G_PARAM_READABLE));
162 }
163
164 static void
165 configure_combo_box_add (GtkComboBox *combo_box,
166 const gchar *item,
167 const gchar *value)
168 {
169 GtkTreeRowReference *reference;
170 GtkTreeModel *model;
171 GtkTreePath *path;
172 GHashTable *index;
173 GtkTreeIter iter;
174
175 model = gtk_combo_box_get_model (combo_box);
176 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
177
178 gtk_list_store_set (
179 GTK_LIST_STORE (model), &iter,
180 COLUMN_ITEM, item, COLUMN_VALUE, value, -1);
181
182 index = g_object_get_data (G_OBJECT (combo_box), "index");
183 g_return_if_fail (index != NULL);
184
185 /* Add an entry to the tree model index. */
186 path = gtk_tree_model_get_path (model, &iter);
187 reference = gtk_tree_row_reference_new (model, path);
188 g_return_if_fail (reference != NULL);
189 g_hash_table_insert (index, g_strdup (value), reference);
190 gtk_tree_path_free (path);
191 }
192
193 static gchar *
194 configure_combo_box_get_active (GtkComboBox *combo_box)
195 {
196 GtkTreeIter iter;
197 gchar *value = NULL;
198
199 if (gtk_combo_box_get_active_iter (combo_box, &iter))
200 gtk_tree_model_get (
201 gtk_combo_box_get_model (combo_box), &iter,
202 COLUMN_VALUE, &value, -1);
203
204 if (value != NULL && *value == '\0') {
205 g_free (value);
206 value = NULL;
207 }
208
209 return value;
210 }
211
212 static void
213 configure_combo_box_set_active (GtkComboBox *combo_box,
214 const gchar *value)
215 {
216 GtkTreeRowReference *reference;
217 GHashTable *index;
218
219 index = g_object_get_data (G_OBJECT (combo_box), "index");
220 g_return_if_fail (index != NULL);
221
222 reference = g_hash_table_lookup (index, value);
223 if (reference != NULL) {
224 GtkTreeModel *model;
225 GtkTreePath *path;
226 GtkTreeIter iter;
227
228 model = gtk_tree_row_reference_get_model (reference);
229 path = gtk_tree_row_reference_get_path (reference);
230
231 if (path == NULL)
232 return;
233
234 if (gtk_tree_model_get_iter (model, &iter, path))
235 gtk_combo_box_set_active_iter (combo_box, &iter);
236
237 gtk_tree_path_free (path);
238 }
239 }
240
241 static ETableColumnSpecification *
242 find_column_in_spec (ETableSpecification *spec,
243 gint model_col)
244 {
245 ETableColumnSpecification **column;
246
247 for (column = spec->columns; *column; column++) {
248 if ((*column)->disabled)
249 continue;
250 if ((*column)->model_col != model_col)
251 continue;
252
253 return *column;
254 }
255
256 return NULL;
257 }
258
259 static gint
260 find_model_column_by_name (ETableSpecification *spec,
261 const gchar *s)
262 {
263 ETableColumnSpecification **column;
264
265 for (column = spec->columns; *column; column++) {
266
267 if ((*column)->disabled)
268 continue;
269 if (g_ascii_strcasecmp ((*column)->title, s) == 0)
270 return (*column)->model_col;
271 }
272 return -1;
273 }
274
275 static void
276 update_sort_and_group_config_dialog (ETableConfig *config,
277 gboolean is_sort)
278 {
279 ETableConfigSortWidgets *widgets;
280 gint count, i;
281
282 if (is_sort) {
283 count = e_table_sort_info_sorting_get_count (
284 config->temp_state->sort_info);
285 widgets = &config->sort[0];
286 } else {
287 count = e_table_sort_info_grouping_get_count (
288 config->temp_state->sort_info);
289 widgets = &config->group[0];
290 }
291
292 for (i = 0; i < 4; i++) {
293 gboolean sensitive = (i <= count);
294 const gchar *text = "";
295
296 gtk_widget_set_sensitive (widgets[i].frames, sensitive);
297
298 /*
299 * Sorting is set, auto select the text
300 */
301 g_signal_handler_block (
302 widgets[i].radio_ascending,
303 widgets[i].toggled_id);
304 g_signal_handler_block (
305 widgets[i].combo,
306 widgets[i].changed_id);
307
308 if (i < count) {
309 GtkToggleButton *a, *d;
310 ETableSortColumn col =
311 is_sort
312 ? e_table_sort_info_sorting_get_nth (
313 config->temp_state->sort_info,
314 i)
315 : e_table_sort_info_grouping_get_nth (
316 config->temp_state->sort_info,
317 i);
318
319 ETableColumnSpecification *column =
320 find_column_in_spec (config->source_spec, col.column);
321
322 if (!column) {
323 /*
324 * This is a bug in the programmer
325 * stuff, but by the time we arrive
326 * here, the user has been given a
327 * warning
328 */
329 continue;
330 }
331
332 text = column->title;
333
334 /*
335 * Update radio buttons
336 */
337 a = GTK_TOGGLE_BUTTON (
338 widgets[i].radio_ascending);
339 d = GTK_TOGGLE_BUTTON (
340 widgets[i].radio_descending);
341
342 gtk_toggle_button_set_active (col.ascending ? a : d, 1);
343 } else {
344 GtkToggleButton *t;
345
346 t = GTK_TOGGLE_BUTTON (
347 widgets[i].radio_ascending);
348
349 if (is_sort)
350 g_return_if_fail (
351 widgets[i].radio_ascending !=
352 config->group[i].radio_ascending);
353 else
354 g_return_if_fail (
355 widgets[i].radio_ascending !=
356 config->sort[i].radio_ascending);
357 gtk_toggle_button_set_active (t, 1);
358 }
359
360 /* Set the text */
361 configure_combo_box_set_active (
362 GTK_COMBO_BOX (widgets[i].combo), text);
363
364 g_signal_handler_unblock (
365 widgets[i].radio_ascending,
366 widgets[i].toggled_id);
367 g_signal_handler_unblock (
368 widgets[i].combo,
369 widgets[i].changed_id);
370 }
371 }
372
373 static void
374 config_sort_info_update (ETableConfig *config)
375 {
376 ETableSortInfo *info = config->state->sort_info;
377 GString *res;
378 gint count, i;
379
380 count = e_table_sort_info_sorting_get_count (info);
381 res = g_string_new ("");
382
383 for (i = 0; i < count; i++) {
384 ETableSortColumn col = e_table_sort_info_sorting_get_nth (info, i);
385 ETableColumnSpecification *column;
386
387 column = find_column_in_spec (config->source_spec, col.column);
388 if (!column) {
389 g_warning ("Could not find column model in specification");
390 continue;
391 }
392
393 g_string_append (res, dgettext (config->domain, (column)->title));
394 g_string_append_c (res, ' ');
395 g_string_append (
396 res,
397 col.ascending ?
398 _("(Ascending)") : _("(Descending)"));
399
400 if ((i + 1) != count)
401 g_string_append (res, ", ");
402 }
403
404 if (res->str[0] == 0)
405 g_string_append (res, _("Not sorted"));
406
407 gtk_label_set_text (GTK_LABEL (config->sort_label), res->str);
408
409 g_string_free (res, TRUE);
410 }
411
412 static void
413 config_group_info_update (ETableConfig *config)
414 {
415 ETableSortInfo *info = config->state->sort_info;
416 GString *res;
417 gint count, i;
418
419 if (!e_table_sort_info_get_can_group (info))
420 return;
421
422 count = e_table_sort_info_grouping_get_count (info);
423 res = g_string_new ("");
424
425 for (i = 0; i < count; i++) {
426 ETableSortColumn col = e_table_sort_info_grouping_get_nth (info, i);
427 ETableColumnSpecification *column;
428
429 column = find_column_in_spec (config->source_spec, col.column);
430 if (!column) {
431 g_warning ("Could not find model column in specification");
432 continue;
433 }
434
435 g_string_append (res, dgettext (config->domain, (column)->title));
436 g_string_append_c (res, ' ');
437 g_string_append (
438 res,
439 col.ascending ?
440 _("(Ascending)") : _("(Descending)"));
441
442 if ((i + 1) != count)
443 g_string_append (res, ", ");
444 }
445 if (res->str[0] == 0)
446 g_string_append (res, _("No grouping"));
447
448 gtk_label_set_text (GTK_LABEL (config->group_label), res->str);
449 g_string_free (res, TRUE);
450 }
451
452 static void
453 setup_fields (ETableConfig *config)
454 {
455 gint i;
456
457 e_table_model_freeze ((ETableModel *) config->available_model);
458 e_table_model_freeze ((ETableModel *) config->shown_model);
459 e_table_without_show_all (config->available_model);
460 e_table_subset_variable_clear (config->shown_model);
461
462 if (config->temp_state) {
463 for (i = 0; i < config->temp_state->col_count; i++) {
464 gint j, idx;
465 for (j = 0, idx = 0; j < config->temp_state->columns[i]; j++)
466 if (!config->source_spec->columns[j]->disabled)
467 idx++;
468
469 e_table_subset_variable_add (config->shown_model, idx);
470 e_table_without_hide (config->available_model, GINT_TO_POINTER (idx));
471 }
472 }
473 e_table_model_thaw ((ETableModel *) config->available_model);
474 e_table_model_thaw ((ETableModel *) config->shown_model);
475 }
476
477 static void
478 config_fields_info_update (ETableConfig *config)
479 {
480 ETableColumnSpecification **column;
481 GString *res = g_string_new ("");
482 gint i, j;
483
484 for (i = 0; i < config->state->col_count; i++) {
485 for (j = 0, column = config->source_spec->columns; *column; column++, j++) {
486
487 if ((*column)->disabled)
488 continue;
489
490 if (config->state->columns[i] != j)
491 continue;
492
493 g_string_append (res, dgettext (config->domain, (*column)->title));
494 if (i + 1 < config->state->col_count)
495 g_string_append (res, ", ");
496
497 break;
498 }
499 }
500
501 gtk_label_set_text (GTK_LABEL (config->fields_label), res->str);
502 g_string_free (res, TRUE);
503 }
504
505 static void
506 do_sort_and_group_config_dialog (ETableConfig *config,
507 gboolean is_sort)
508 {
509 GtkDialog *dialog;
510 gint response, running = 1;
511
512 config->temp_state = e_table_state_duplicate (config->state);
513
514 update_sort_and_group_config_dialog (config, is_sort);
515
516 gtk_widget_grab_focus (GTK_WIDGET (
517 is_sort
518 ? config->sort[0].combo
519 : config->group[0].combo));
520
521 if (is_sort)
522 dialog = GTK_DIALOG (config->dialog_sort);
523 else
524 dialog = GTK_DIALOG (config->dialog_group_by);
525
526 gtk_window_set_transient_for (
527 GTK_WINDOW (dialog), GTK_WINDOW (config->dialog_toplevel));
528
529 do {
530 response = gtk_dialog_run (dialog);
531 switch (response) {
532 case 0: /* clear fields */
533 if (is_sort) {
534 e_table_sort_info_sorting_truncate (
535 config->temp_state->sort_info, 0);
536 } else {
537 e_table_sort_info_grouping_truncate (
538 config->temp_state->sort_info, 0);
539 }
540 update_sort_and_group_config_dialog (config, is_sort);
541 break;
542
543 case GTK_RESPONSE_OK:
544 g_object_unref (config->state);
545 config->state = config->temp_state;
546 config->temp_state = NULL;
547 running = 0;
548 config_dialog_changed (config);
549 break;
550
551 case GTK_RESPONSE_DELETE_EVENT:
552 case GTK_RESPONSE_CANCEL:
553 g_object_unref (config->temp_state);
554 config->temp_state = NULL;
555 running = 0;
556 break;
557 }
558
559 } while (running);
560 gtk_widget_hide (GTK_WIDGET (dialog));
561
562 if (is_sort)
563 config_sort_info_update (config);
564 else
565 config_group_info_update (config);
566 }
567
568 static void
569 do_fields_config_dialog (ETableConfig *config)
570 {
571 GtkDialog *dialog;
572 GtkWidget *widget;
573 gint response, running = 1;
574
575 dialog = GTK_DIALOG (config->dialog_show_fields);
576
577 gtk_widget_ensure_style (config->dialog_show_fields);
578
579 widget = gtk_dialog_get_content_area (dialog);
580 gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
581
582 widget = gtk_dialog_get_action_area (dialog);
583 gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
584
585 config->temp_state = e_table_state_duplicate (config->state);
586
587 setup_fields (config);
588
589 gtk_window_set_transient_for (
590 GTK_WINDOW (config->dialog_show_fields),
591 GTK_WINDOW (config->dialog_toplevel));
592
593 do {
594 response = gtk_dialog_run (GTK_DIALOG (config->dialog_show_fields));
595 switch (response) {
596 case GTK_RESPONSE_OK:
597 g_object_unref (config->state);
598 config->state = config->temp_state;
599 config->temp_state = NULL;
600 running = 0;
601 config_dialog_changed (config);
602 break;
603
604 case GTK_RESPONSE_DELETE_EVENT:
605 case GTK_RESPONSE_CANCEL:
606 g_object_unref (config->temp_state);
607 config->temp_state = NULL;
608 running = 0;
609 break;
610 }
611
612 } while (running);
613 gtk_widget_hide (GTK_WIDGET (config->dialog_show_fields));
614
615 config_fields_info_update (config);
616 }
617
618 static ETableMemoryStoreColumnInfo store_columns[] = {
619 E_TABLE_MEMORY_STORE_STRING,
620 E_TABLE_MEMORY_STORE_INTEGER,
621 E_TABLE_MEMORY_STORE_TERMINATOR
622 };
623
624 static ETableModel *
625 create_store (ETableConfig *config)
626 {
627 gint i;
628 ETableModel *store;
629
630 store = e_table_memory_store_new (store_columns);
631 for (i = 0; config->source_spec->columns[i]; i++) {
632
633 gchar *text;
634
635 if (config->source_spec->columns[i]->disabled)
636 continue;
637
638 text = g_strdup (dgettext (
639 config->domain,
640 config->source_spec->columns[i]->title));
641 e_table_memory_store_insert_adopt (
642 E_TABLE_MEMORY_STORE (store), -1, NULL, text, i);
643 }
644
645 return store;
646 }
647
648 static const gchar *spec =
649 "<ETableSpecification gettext-domain=\"" GETTEXT_PACKAGE "\""
650 " no-headers=\"true\" cursor-mode=\"line\" draw-grid=\"false\" "
651 " draw-focus=\"true\" selection-mode=\"browse\">"
652 "<ETableColumn model_col= \"0\" _title=\"Name\" minimum_width=\"30\""
653 " resizable=\"true\" cell=\"string\" compare=\"string\"/>"
654 "<ETableState> <column source=\"0\"/>"
655 "<grouping/>"
656 "</ETableState>"
657 "</ETableSpecification>";
658
659 static GtkWidget *
660 e_table_proxy_etable_shown_new (ETableModel *store)
661 {
662 ETableModel *model = NULL;
663 GtkWidget *widget;
664
665 model = e_table_subset_variable_new (store);
666
667 widget = e_table_new (model, NULL, spec, NULL);
668
669 atk_object_set_name (
670 gtk_widget_get_accessible (widget),
671 _("Show Fields"));
672
673 return widget;
674 }
675
676 static GtkWidget *
677 e_table_proxy_etable_available_new (ETableModel *store)
678 {
679 ETableModel *model;
680 GtkWidget *widget;
681
682 model = e_table_without_new (
683 store, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
684
685 e_table_without_show_all (E_TABLE_WITHOUT (model));
686
687 widget = e_table_new (model, NULL, spec, NULL);
688
689 atk_object_set_name (
690 gtk_widget_get_accessible (widget),
691 _("Available Fields"));
692
693 return widget;
694 }
695
696 static void
697 config_button_fields (GtkWidget *widget,
698 ETableConfig *config)
699 {
700 do_fields_config_dialog (config);
701 }
702
703 static void
704 config_button_sort (GtkWidget *widget,
705 ETableConfig *config)
706 {
707 do_sort_and_group_config_dialog (config, TRUE);
708 }
709
710 static void
711 config_button_group (GtkWidget *widget,
712 ETableConfig *config)
713 {
714 do_sort_and_group_config_dialog (config, FALSE);
715 }
716
717 static void
718 dialog_destroyed (gpointer data,
719 GObject *where_object_was)
720 {
721 ETableConfig *config = data;
722 g_object_unref (config);
723 }
724
725 static void
726 dialog_response (GtkWidget *dialog,
727 gint response_id,
728 ETableConfig *config)
729 {
730 if (response_id == GTK_RESPONSE_APPLY
731 || response_id == GTK_RESPONSE_OK) {
732 e_table_config_changed (config, config->state);
733 }
734
735 if (response_id == GTK_RESPONSE_CANCEL
736 || response_id == GTK_RESPONSE_OK) {
737 gtk_widget_destroy (dialog);
738 }
739 }
740
741 /*
742 * Invoked by the GtkBuilder auto-connect code
743 */
744 static GtkWidget *
745 e_table_proxy_gtk_combo_text_new (void)
746 {
747 GtkCellRenderer *renderer;
748 GtkListStore *store;
749 GtkWidget *combo_box;
750 GHashTable *index;
751
752 store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
753 combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
754
755 renderer = gtk_cell_renderer_text_new ();
756 gtk_cell_layout_pack_start (
757 GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
758 gtk_cell_layout_add_attribute (
759 GTK_CELL_LAYOUT (combo_box), renderer, "text", COLUMN_ITEM);
760
761 /* Embed a reverse-lookup index into the widget. */
762 index = g_hash_table_new_full (
763 g_str_hash, g_str_equal,
764 (GDestroyNotify) g_free,
765 (GDestroyNotify) gtk_tree_row_reference_free);
766 g_object_set_data_full (
767 G_OBJECT (combo_box), "index", index,
768 (GDestroyNotify) g_hash_table_destroy);
769
770 return combo_box;
771 }
772
773 static void
774 connect_button (ETableConfig *config,
775 GtkBuilder *builder,
776 const gchar *widget_name,
777 GCallback cback)
778 {
779 GtkWidget *button = e_builder_get_widget (builder, widget_name);
780
781 if (button)
782 g_signal_connect (button, "clicked", cback, config);
783 }
784
785 static gint
786 get_source_model_col_index (ETableConfig *config,
787 gint idx)
788 {
789 gint visible_index;
790 ETableModel *src_model;
791
792 src_model = E_TABLE_SUBSET (config->available_model)->source;
793
794 visible_index = e_table_subset_view_to_model_row (
795 E_TABLE_SUBSET (config->available_model), idx);
796
797 return GPOINTER_TO_INT (e_table_model_value_at (src_model, 1, visible_index));
798 }
799
800 static void
801 sort_combo_changed (GtkComboBox *combo_box,
802 ETableConfigSortWidgets *sort)
803 {
804 ETableConfig *config = sort->e_table_config;
805 ETableSortInfo *sort_info = config->temp_state->sort_info;
806 ETableConfigSortWidgets *base = &config->sort[0];
807 GtkToggleButton *toggle_button;
808 gint idx = sort - base;
809 gchar *s;
810
811 s = configure_combo_box_get_active (combo_box);
812
813 if (s != NULL) {
814 ETableSortColumn c;
815 gint col;
816
817 col = find_model_column_by_name (config->source_spec, s);
818 if (col == -1) {
819 g_warning ("sort: This should not happen (%s)", s);
820 g_free (s);
821 return;
822 }
823
824 toggle_button = GTK_TOGGLE_BUTTON (
825 config->sort[idx].radio_ascending);
826 c.ascending = gtk_toggle_button_get_active (toggle_button);
827 c.column = col;
828 e_table_sort_info_sorting_set_nth (sort_info, idx, c);
829
830 update_sort_and_group_config_dialog (config, TRUE);
831 } else {
832 e_table_sort_info_sorting_truncate (sort_info, idx);
833 update_sort_and_group_config_dialog (config, TRUE);
834 }
835
836 g_free (s);
837 }
838
839 static void
840 sort_ascending_toggled (GtkToggleButton *t,
841 ETableConfigSortWidgets *sort)
842 {
843 ETableConfig *config = sort->e_table_config;
844 ETableSortInfo *si = config->temp_state->sort_info;
845 ETableConfigSortWidgets *base = &config->sort[0];
846 gint idx = sort - base;
847 ETableSortColumn c;
848
849 c = e_table_sort_info_sorting_get_nth (si, idx);
850 c.ascending = gtk_toggle_button_get_active (t);
851 e_table_sort_info_sorting_set_nth (si, idx, c);
852 }
853
854 static void
855 configure_sort_dialog (ETableConfig *config,
856 GtkBuilder *builder)
857 {
858 GSList *l;
859 gint i;
860
861 const gchar *algs[] = {
862 "alignment4",
863 "alignment3",
864 "alignment2",
865 "alignment1",
866 NULL
867 };
868
869 for (i = 0; i < 4; i++) {
870 gchar buffer[80];
871
872 snprintf (buffer, sizeof (buffer), "sort-combo-%d", i + 1);
873 config->sort[i].combo = e_table_proxy_gtk_combo_text_new ();
874 gtk_widget_show (GTK_WIDGET (config->sort[i].combo));
875 gtk_container_add (
876 GTK_CONTAINER (e_builder_get_widget (
877 builder, algs[i])), config->sort[i].combo);
878 configure_combo_box_add (
879 GTK_COMBO_BOX (config->sort[i].combo), "", "");
880
881 snprintf (buffer, sizeof (buffer), "frame-sort-%d", i + 1);
882 config->sort[i].frames =
883 e_builder_get_widget (builder, buffer);
884
885 snprintf (
886 buffer, sizeof (buffer),
887 "radiobutton-ascending-sort-%d", i + 1);
888 config->sort[i].radio_ascending = e_builder_get_widget (
889 builder, buffer);
890
891 snprintf (
892 buffer, sizeof (buffer),
893 "radiobutton-descending-sort-%d", i + 1);
894 config->sort[i].radio_descending = e_builder_get_widget (
895 builder, buffer);
896
897 config->sort[i].e_table_config = config;
898 }
899
900 for (l = config->column_names; l; l = l->next) {
901 gchar *label = l->data;
902
903 for (i = 0; i < 4; i++) {
904 configure_combo_box_add (
905 GTK_COMBO_BOX (config->sort[i].combo),
906 dgettext (config->domain, label), label);
907 }
908 }
909
910 /*
911 * After we have runtime modified things, signal connect
912 */
913 for (i = 0; i < 4; i++) {
914 config->sort[i].changed_id = g_signal_connect (
915 config->sort[i].combo,
916 "changed", G_CALLBACK (sort_combo_changed),
917 &config->sort[i]);
918
919 config->sort[i].toggled_id = g_signal_connect (
920 config->sort[i].radio_ascending,
921 "toggled", G_CALLBACK (sort_ascending_toggled),
922 &config->sort[i]);
923 }
924 }
925
926 static void
927 group_combo_changed (GtkComboBox *combo_box,
928 ETableConfigSortWidgets *group)
929 {
930 ETableConfig *config = group->e_table_config;
931 ETableSortInfo *sort_info = config->temp_state->sort_info;
932 ETableConfigSortWidgets *base = &config->group[0];
933 gint idx = group - base;
934 gchar *s;
935
936 s = configure_combo_box_get_active (combo_box);
937
938 if (s != NULL) {
939 GtkToggleButton *toggle_button;
940 ETableSortColumn c;
941 gint col;
942
943 col = find_model_column_by_name (config->source_spec, s);
944 if (col == -1) {
945 g_warning ("grouping: this should not happen, %s", s);
946 g_free (s);
947 return;
948 }
949
950 toggle_button = GTK_TOGGLE_BUTTON (
951 config->group[idx].radio_ascending);
952 c.ascending = gtk_toggle_button_get_active (toggle_button);
953 c.column = col;
954 e_table_sort_info_grouping_set_nth (sort_info, idx, c);
955
956 update_sort_and_group_config_dialog (config, FALSE);
957 } else {
958 e_table_sort_info_grouping_truncate (sort_info, idx);
959 update_sort_and_group_config_dialog (config, FALSE);
960 }
961
962 g_free (s);
963 }
964
965 static void
966 group_ascending_toggled (GtkToggleButton *t,
967 ETableConfigSortWidgets *group)
968 {
969 ETableConfig *config = group->e_table_config;
970 ETableSortInfo *si = config->temp_state->sort_info;
971 ETableConfigSortWidgets *base = &config->group[0];
972 gint idx = group - base;
973 ETableSortColumn c;
974
975 c = e_table_sort_info_grouping_get_nth (si, idx);
976 c.ascending = gtk_toggle_button_get_active (t);
977 e_table_sort_info_grouping_set_nth (si, idx, c);
978 }
979
980 static void
981 configure_group_dialog (ETableConfig *config,
982 GtkBuilder *builder)
983 {
984 GSList *l;
985 gint i;
986 const gchar *vboxes[] = {"vbox7", "vbox9", "vbox11", "vbox13", NULL};
987
988 for (i = 0; i < 4; i++) {
989 gchar buffer[80];
990
991 snprintf (buffer, sizeof (buffer), "group-combo-%d", i + 1);
992 config->group[i].combo = e_table_proxy_gtk_combo_text_new ();
993 gtk_widget_show (GTK_WIDGET (config->group[i].combo));
994 gtk_box_pack_start (
995 GTK_BOX (e_builder_get_widget (builder, vboxes[i])),
996 config->group[i].combo, FALSE, FALSE, 0);
997
998 configure_combo_box_add (
999 GTK_COMBO_BOX (config->group[i].combo), "", "");
1000
1001 snprintf (buffer, sizeof (buffer), "frame-group-%d", i + 1);
1002 config->group[i].frames =
1003 e_builder_get_widget (builder, buffer);
1004
1005 snprintf (
1006 buffer, sizeof (buffer),
1007 "radiobutton-ascending-group-%d", i + 1);
1008 config->group[i].radio_ascending = e_builder_get_widget (
1009 builder, buffer);
1010
1011 snprintf (
1012 buffer, sizeof (buffer),
1013 "radiobutton-descending-group-%d", i + 1);
1014 config->group[i].radio_descending = e_builder_get_widget (
1015 builder, buffer);
1016
1017 snprintf (
1018 buffer, sizeof (buffer),
1019 "checkbutton-group-%d", i + 1);
1020 config->group[i].view_check = e_builder_get_widget (
1021 builder, buffer);
1022
1023 config->group[i].e_table_config = config;
1024 }
1025
1026 for (l = config->column_names; l; l = l->next) {
1027 gchar *label = l->data;
1028
1029 for (i = 0; i < 4; i++) {
1030 configure_combo_box_add (
1031 GTK_COMBO_BOX (config->group[i].combo),
1032 dgettext (config->domain, label), label);
1033 }
1034 }
1035
1036 /*
1037 * After we have runtime modified things, signal connect
1038 */
1039 for (i = 0; i < 4; i++) {
1040 config->group[i].changed_id = g_signal_connect (
1041 config->group[i].combo,
1042 "changed", G_CALLBACK (group_combo_changed),
1043 &config->group[i]);
1044
1045 config->group[i].toggled_id = g_signal_connect (
1046 config->group[i].radio_ascending,
1047 "toggled", G_CALLBACK (group_ascending_toggled),
1048 &config->group[i]);
1049 }
1050 }
1051
1052 static void
1053 add_column (gint model_row,
1054 gpointer closure)
1055 {
1056 GList **columns = closure;
1057 *columns = g_list_prepend (*columns, GINT_TO_POINTER (model_row));
1058 }
1059
1060 static void
1061 config_button_add (GtkWidget *widget,
1062 ETableConfig *config)
1063 {
1064 GList *columns = NULL;
1065 GList *column;
1066 gint count;
1067 gint i;
1068
1069 e_table_selected_row_foreach (config->available, add_column, &columns);
1070 columns = g_list_reverse (columns);
1071
1072 count = g_list_length (columns);
1073
1074 config->temp_state->columns = g_renew (
1075 int, config->temp_state->columns,
1076 config->temp_state->col_count + count);
1077 config->temp_state->expansions = g_renew (
1078 gdouble, config->temp_state->expansions,
1079 config->temp_state->col_count + count);
1080 i = config->temp_state->col_count;
1081 for (column = columns; column; column = column->next) {
1082 config->temp_state->columns[i] =
1083 get_source_model_col_index (
1084 config, GPOINTER_TO_INT (column->data));
1085 config->temp_state->expansions[i] =
1086 config->source_spec->columns
1087 [config->temp_state->columns[i]]->expansion;
1088 i++;
1089 }
1090 config->temp_state->col_count += count;
1091
1092 g_list_free (columns);
1093
1094 setup_fields (config);
1095 }
1096
1097 static void
1098 config_button_remove (GtkWidget *widget,
1099 ETableConfig *config)
1100 {
1101 GList *columns = NULL;
1102 GList *column;
1103
1104 e_table_selected_row_foreach (config->shown, add_column, &columns);
1105
1106 for (column = columns; column; column = column->next) {
1107 gint row = GPOINTER_TO_INT (column->data);
1108
1109 memmove (
1110 config->temp_state->columns + row,
1111 config->temp_state->columns + row + 1,
1112 sizeof (gint) * (config->temp_state->col_count - row - 1));
1113 memmove (
1114 config->temp_state->expansions + row,
1115 config->temp_state->expansions + row + 1,
1116 sizeof (gdouble) * (config->temp_state->col_count - row - 1));
1117 config->temp_state->col_count--;
1118 }
1119 config->temp_state->columns = g_renew (
1120 int, config->temp_state->columns,
1121 config->temp_state->col_count);
1122 config->temp_state->expansions = g_renew (
1123 gdouble, config->temp_state->expansions,
1124 config->temp_state->col_count);
1125
1126 g_list_free (columns);
1127
1128 setup_fields (config);
1129 }
1130
1131 static void
1132 config_button_up (GtkWidget *widget,
1133 ETableConfig *config)
1134 {
1135 GList *columns = NULL;
1136 GList *column;
1137 gint *new_shown;
1138 gdouble *new_expansions;
1139 gint next_col;
1140 gdouble next_expansion;
1141 gint i;
1142
1143 e_table_selected_row_foreach (config->shown, add_column, &columns);
1144
1145 /* if no columns left, just return */
1146 if (columns == NULL)
1147 return;
1148
1149 columns = g_list_reverse (columns);
1150
1151 new_shown = g_new (int, config->temp_state->col_count);
1152 new_expansions = g_new (double, config->temp_state->col_count);
1153
1154 column = columns;
1155
1156 next_col = config->temp_state->columns[0];
1157 next_expansion = config->temp_state->expansions[0];
1158
1159 for (i = 1; i < config->temp_state->col_count; i++) {
1160 if (column && (GPOINTER_TO_INT (column->data) == i)) {
1161 new_expansions[i - 1] = config->temp_state->expansions[i];
1162 new_shown[i - 1] = config->temp_state->columns[i];
1163 column = column->next;
1164 } else {
1165 new_shown[i - 1] = next_col;
1166 next_col = config->temp_state->columns[i];
1167
1168 new_expansions[i - 1] = next_expansion;
1169 next_expansion = config->temp_state->expansions[i];
1170 }
1171 }
1172
1173 new_shown[i - 1] = next_col;
1174 new_expansions[i - 1] = next_expansion;
1175
1176 g_free (config->temp_state->columns);
1177 g_free (config->temp_state->expansions);
1178
1179 config->temp_state->columns = new_shown;
1180 config->temp_state->expansions = new_expansions;
1181
1182 g_list_free (columns);
1183
1184 setup_fields (config);
1185 }
1186
1187 static void
1188 config_button_down (GtkWidget *widget,
1189 ETableConfig *config)
1190 {
1191 GList *columns = NULL;
1192 GList *column;
1193 gint *new_shown;
1194 gdouble *new_expansions;
1195 gint next_col;
1196 gdouble next_expansion;
1197 gint i;
1198
1199 e_table_selected_row_foreach (config->shown, add_column, &columns);
1200
1201 /* if no columns left, just return */
1202 if (columns == NULL)
1203 return;
1204
1205 new_shown = g_new (gint, config->temp_state->col_count);
1206 new_expansions = g_new (gdouble, config->temp_state->col_count);
1207
1208 column = columns;
1209
1210 next_col =
1211 config->temp_state->columns[config->temp_state->col_count - 1];
1212 next_expansion =
1213 config->temp_state->expansions[config->temp_state->col_count - 1];
1214
1215 for (i = config->temp_state->col_count - 1; i > 0; i--) {
1216 if (column && (GPOINTER_TO_INT (column->data) == i - 1)) {
1217 new_expansions[i] = config->temp_state->expansions[i - 1];
1218 new_shown[i] = config->temp_state->columns[i - 1];
1219 column = column->next;
1220 } else {
1221 new_shown[i] = next_col;
1222 next_col = config->temp_state->columns[i - 1];
1223
1224 new_expansions[i] = next_expansion;
1225 next_expansion = config->temp_state->expansions[i - 1];
1226 }
1227 }
1228
1229 new_shown[0] = next_col;
1230 new_expansions[0] = next_expansion;
1231
1232 g_free (config->temp_state->columns);
1233 g_free (config->temp_state->expansions);
1234
1235 config->temp_state->columns = new_shown;
1236 config->temp_state->expansions = new_expansions;
1237
1238 g_list_free (columns);
1239
1240 setup_fields (config);
1241 }
1242
1243 static void
1244 configure_fields_dialog (ETableConfig *config,
1245 GtkBuilder *builder)
1246 {
1247 GtkWidget *scrolled;
1248 GtkWidget *etable;
1249 ETableModel *store = create_store (config);
1250
1251 /* "custom-available" widget */
1252 etable = e_table_proxy_etable_available_new (store);
1253 gtk_widget_show (etable);
1254 scrolled = e_builder_get_widget (builder, "available-scrolled");
1255 gtk_container_add (GTK_CONTAINER (scrolled), etable);
1256 config->available = E_TABLE (etable);
1257 g_object_get (
1258 config->available,
1259 "model", &config->available_model,
1260 NULL);
1261 gtk_widget_show_all (etable);
1262 gtk_label_set_mnemonic_widget (
1263 GTK_LABEL (e_builder_get_widget (
1264 builder, "label-available")), etable);
1265
1266 /* "custom-shown" widget */
1267 etable = e_table_proxy_etable_shown_new (store);
1268 gtk_widget_show (etable);
1269 scrolled = e_builder_get_widget (builder, "shown-scrolled");
1270 gtk_container_add (GTK_CONTAINER (scrolled), etable);
1271 config->shown = E_TABLE (etable);
1272 g_object_get (
1273 config->shown,
1274 "model", &config->shown_model,
1275 NULL);
1276 gtk_widget_show_all (etable);
1277 gtk_label_set_mnemonic_widget (
1278 GTK_LABEL (e_builder_get_widget (
1279 builder, "label-displayed")), etable);
1280
1281 connect_button (
1282 config, builder, "button-add",
1283 G_CALLBACK (config_button_add));
1284 connect_button (
1285 config, builder, "button-remove",
1286 G_CALLBACK (config_button_remove));
1287 connect_button (
1288 config, builder, "button-up",
1289 G_CALLBACK (config_button_up));
1290 connect_button (
1291 config, builder, "button-down",
1292 G_CALLBACK (config_button_down));
1293
1294 setup_fields (config);
1295
1296 g_object_unref (store);
1297 }
1298
1299 static void
1300 setup_gui (ETableConfig *config)
1301 {
1302 GtkBuilder *builder;
1303 gboolean can_group;
1304
1305 can_group = e_table_sort_info_get_can_group (config->state->sort_info);
1306 builder = gtk_builder_new ();
1307 e_load_ui_builder_definition (builder, "e-table-config.ui");
1308
1309 config->dialog_toplevel = e_builder_get_widget (
1310 builder, "e-table-config");
1311
1312 if (config->header)
1313 gtk_window_set_title (
1314 GTK_WINDOW (config->dialog_toplevel),
1315 config->header);
1316
1317 config->dialog_show_fields = e_builder_get_widget (
1318 builder, "dialog-show-fields");
1319 config->dialog_group_by = e_builder_get_widget (
1320 builder, "dialog-group-by");
1321 config->dialog_sort = e_builder_get_widget (
1322 builder, "dialog-sort");
1323
1324 config->sort_label = e_builder_get_widget (
1325 builder, "label-sort");
1326 config->group_label = e_builder_get_widget (
1327 builder, "label-group");
1328 config->fields_label = e_builder_get_widget (
1329 builder, "label-fields");
1330
1331 connect_button (
1332 config, builder, "button-sort",
1333 G_CALLBACK (config_button_sort));
1334 connect_button (
1335 config, builder, "button-group",
1336 G_CALLBACK (config_button_group));
1337 connect_button (
1338 config, builder, "button-fields",
1339 G_CALLBACK (config_button_fields));
1340
1341 if (!can_group) {
1342 GtkWidget *w;
1343
1344 w = e_builder_get_widget (builder, "button-group");
1345 if (w)
1346 gtk_widget_hide (w);
1347
1348 w = e_builder_get_widget (builder, "label3");
1349 if (w)
1350 gtk_widget_hide (w);
1351
1352 w = config->group_label;
1353 if (w)
1354 gtk_widget_hide (w);
1355 }
1356
1357 configure_sort_dialog (config, builder);
1358 configure_group_dialog (config, builder);
1359 configure_fields_dialog (config, builder);
1360
1361 g_object_weak_ref (
1362 G_OBJECT (config->dialog_toplevel),
1363 dialog_destroyed, config);
1364
1365 g_signal_connect (
1366 config->dialog_toplevel, "response",
1367 G_CALLBACK (dialog_response), config);
1368
1369 g_object_unref (builder);
1370 }
1371
1372 static void
1373 e_table_config_init (ETableConfig *config)
1374 {
1375 config->domain = NULL;
1376 }
1377
1378 ETableConfig *
1379 e_table_config_construct (ETableConfig *config,
1380 const gchar *header,
1381 ETableSpecification *spec,
1382 ETableState *state,
1383 GtkWindow *parent_window)
1384 {
1385 ETableColumnSpecification **column;
1386
1387 g_return_val_if_fail (config != NULL, NULL);
1388 g_return_val_if_fail (header != NULL, NULL);
1389 g_return_val_if_fail (spec != NULL, NULL);
1390 g_return_val_if_fail (state != NULL, NULL);
1391
1392 config->source_spec = spec;
1393 config->source_state = state;
1394 config->header = g_strdup (header);
1395
1396 g_object_ref (config->source_spec);
1397 g_object_ref (config->source_state);
1398
1399 config->state = e_table_state_duplicate (state);
1400
1401 config->domain = g_strdup (spec->domain);
1402
1403 for (column = config->source_spec->columns; *column; column++) {
1404 gchar *label = (*column)->title;
1405
1406 if ((*column)->disabled)
1407 continue;
1408
1409 config->column_names = g_slist_append (
1410 config->column_names, label);
1411 }
1412
1413 setup_gui (config);
1414
1415 gtk_window_set_transient_for (GTK_WINDOW (config->dialog_toplevel),
1416 parent_window);
1417
1418 config_sort_info_update (config);
1419 config_group_info_update (config);
1420 config_fields_info_update (config);
1421
1422 return E_TABLE_CONFIG (config);
1423 }
1424
1425 /**
1426 * e_table_config_new:
1427 * @header: The title of the dialog for the ETableConfig.
1428 * @spec: The specification for the columns to allow.
1429 * @state: The current state of the configuration.
1430 *
1431 * Creates a new ETable config object.
1432 *
1433 * Returns: The config object.
1434 */
1435 ETableConfig *
1436 e_table_config_new (const gchar *header,
1437 ETableSpecification *spec,
1438 ETableState *state,
1439 GtkWindow *parent_window)
1440 {
1441 ETableConfig *config;
1442 GtkDialog *dialog;
1443 GtkWidget *widget;
1444
1445 config = g_object_new (E_TYPE_TABLE_CONFIG, NULL);
1446
1447 e_table_config_construct (
1448 config, header, spec, state, parent_window);
1449
1450 dialog = GTK_DIALOG (config->dialog_toplevel);
1451
1452 gtk_widget_ensure_style (config->dialog_toplevel);
1453
1454 widget = gtk_dialog_get_content_area (dialog);
1455 gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
1456
1457 widget = gtk_dialog_get_action_area (dialog);
1458 gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
1459
1460 gtk_dialog_set_response_sensitive (
1461 GTK_DIALOG (config->dialog_toplevel),
1462 GTK_RESPONSE_APPLY, FALSE);
1463 gtk_widget_show (config->dialog_toplevel);
1464
1465 return E_TABLE_CONFIG (config);
1466 }
1467
1468 /**
1469 * e_table_config_raise:
1470 * @config: The ETableConfig object.
1471 *
1472 * Raises the dialog associated with this ETableConfig object.
1473 */
1474 void
1475 e_table_config_raise (ETableConfig *config)
1476 {
1477 GdkWindow *window;
1478
1479 window = gtk_widget_get_window (GTK_WIDGET (config->dialog_toplevel));
1480 gdk_window_raise (window);
1481 }