GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / dialogs / dialog-cell-sort.c
blob6c1eb705cde7c2eacc8fa52dac1e265320293895
1 /*
2 * dialog-cell-sort.c: Implements Cell Sort dialog boxes.
4 * Authors:
5 * JP Rosevear <jpr@arcavia.com>
6 * Michael Meeks <michael@ximian.com>
7 * Andreas J. Guelzow <aguelzow@taliesin.ca>
8 * Morten Welinder <terra@gnome.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <https://www.gnu.org/licenses/>.
24 #include <gnumeric-config.h>
25 #include <glib/gi18n-lib.h>
26 #include <gnumeric.h>
27 #include "dialogs.h"
28 #include "help.h"
30 #include <workbook-view.h>
31 #include <gui-util.h>
32 #include <cell.h>
33 #include <expr.h>
34 #include <selection.h>
35 #include <parse-util.h>
36 #include <ranges.h>
37 #include <commands.h>
38 #include <workbook.h>
39 #include <sort.h>
40 #include <sheet.h>
41 #include <sheet-view.h>
42 #include <wbc-gtk.h>
43 #include <gnumeric-conf.h>
44 #include <widgets/gnumeric-cell-renderer-toggle.h>
45 #include <widgets/gnumeric-expr-entry.h>
46 #include <value.h>
48 #include <gtk/gtk.h>
49 #include <gsf/gsf-impl-utils.h>
50 #include <gdk/gdkkeysyms.h>
51 #include <goffice/goffice.h>
53 #define CELL_SORT_KEY "cell-sort-dialog"
57 typedef struct {
58 WBCGtk *wbcg;
59 Workbook *wb;
60 SheetView *sv;
61 Sheet *sheet;
63 GtkBuilder *gui;
64 GtkWidget *dialog;
65 GtkWidget *warning_dialog;
66 GtkWidget *cancel_button;
67 GtkWidget *ok_button;
68 GtkWidget *up_button;
69 GtkWidget *down_button;
70 GtkWidget *add_button;
71 GtkWidget *delete_button;
72 GtkWidget *clear_button;
73 GnmExprEntry *range_entry;
74 GnmExprEntry *add_entry;
75 GtkListStore *model;
76 GtkTreeView *treeview;
77 GtkTreeViewColumn *header_column;
78 GtkTreeSelection *selection;
79 GtkWidget *cell_sort_row_rb;
80 GtkWidget *cell_sort_col_rb;
81 GtkWidget *cell_sort_header_check;
82 GtkWidget *retain_format_check;
83 GdkPixbuf *image_ascending;
84 GdkPixbuf *image_descending;
85 GOLocaleSel *locale_selector;
87 GnmValue *sel;
88 gboolean header;
89 gboolean is_cols;
90 int sort_items;
92 } SortFlowState;
94 enum {
95 ITEM_HEADER,
96 ITEM_NAME,
97 ITEM_DESCENDING,
98 ITEM_DESCENDING_IMAGE,
99 ITEM_CASE_SENSITIVE,
100 ITEM_SORT_BY_VALUE,
101 ITEM_MOVE_FORMAT,
102 ITEM_NUMBER,
103 NUM_COLUMNS
106 static const gint MAX_MENU_SIZE = 20;
107 typedef struct {
108 gint index;
109 gint start;
110 gint end;
111 gboolean done_submenu;
112 SortFlowState *state;
113 } AddSortFieldMenuState;
115 static gchar *
116 header_name (Sheet *sheet, int col, int row)
118 GnmCell *cell;
119 gchar *str = NULL;
121 cell = sheet_cell_get (sheet, col, row);
122 if (cell)
123 str = value_get_as_string (cell->value);
125 return str;
129 static gchar *
130 col_row_name (Sheet *sheet, int col, int row, gboolean header, gboolean is_cols)
132 GnmCell *cell;
133 gchar *str = NULL;
135 if (is_cols)
136 str = g_strdup_printf (_("Column %s"), col_name (col));
137 else
138 str = g_strdup_printf (_("Row %s"), row_name (row));
140 if (header) {
141 cell = sheet_cell_get (sheet, col, row);
142 if (cell && !gnm_cell_is_blank (cell)) {
143 gchar *header_str, *generic_str = str;
144 header_str = value_get_as_string (cell->value);
145 str = g_strdup_printf (_("%s (%s)"), header_str, generic_str);
146 g_free (header_str);
147 g_free (generic_str);
151 return str;
155 static gboolean
156 already_in_sort_fields(int index, SortFlowState *state)
158 GtkTreeIter iter;
159 int item = 0;
160 gint number;
162 /* See if index is already in the sort fields */
163 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
164 &iter, NULL, item)) {
165 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
166 ITEM_NUMBER, &number,
167 -1);
168 item++;
170 if (number == index) {
171 return TRUE;
175 /* Here means not already in sort fields */
176 return FALSE;
179 static gboolean
180 range_already_in_sort_criteria(gint start, gint end, SortFlowState *state)
182 gint i;
183 for (i=start; i<=end; i++) {
184 if (!already_in_sort_fields(i, state))
185 return FALSE;
187 return TRUE;
191 static void
192 build_sort_field_menu (gint start, gint end, gint index, GtkWidget *menu, SortFlowState *state, int used);
194 static void
195 cb_sort_field_menu_activate(GtkWidget *item, AddSortFieldMenuState *menu_state)
197 GtkWidget *menu = GTK_WIDGET (gtk_menu_item_get_submenu(GTK_MENU_ITEM (item)));
199 if (menu_state->done_submenu == FALSE) {
200 build_sort_field_menu(menu_state->start,
201 menu_state->end,
202 menu_state->index,
203 menu,
204 menu_state->state, 0);
205 menu_state->done_submenu = TRUE;
209 static void
210 set_button_sensitivity(SortFlowState *state)
212 int items;
214 if (state->sel == NULL) {
215 gtk_widget_set_sensitive (state->ok_button, FALSE);
216 return;
219 items = state->is_cols ? (state->sel->v_range.cell.b.row -
220 state->sel->v_range.cell.a.row + 1) :
221 (state->sel->v_range.cell.b.col -
222 state->sel->v_range.cell.a.col + 1);
223 if (state->header)
224 items -= 1;
225 gtk_widget_set_sensitive (state->ok_button,
226 (state->sort_items != 0) &&
227 (items > 1));
228 gtk_widget_set_sensitive (state->clear_button, state->sort_items != 0);
231 static void
232 append_data (SortFlowState *state, int i, int index)
234 gchar *str, *header;
235 GtkTreeIter iter;
236 Sheet *sheet = state->sel->v_range.cell.a.sheet;
237 gboolean sort_asc = gnm_conf_get_core_sort_default_ascending ();
239 header = state->is_cols
240 ? header_name (sheet, i, index)
241 : header_name (sheet, index, i);
242 str = state->is_cols
243 ? col_row_name (sheet, i, index, FALSE, TRUE)
244 : col_row_name (sheet, index, i, FALSE, FALSE);
245 gtk_list_store_append (state->model, &iter);
246 gtk_list_store_set (state->model, &iter,
247 ITEM_HEADER, header,
248 ITEM_NAME, str,
249 ITEM_DESCENDING, !sort_asc,
250 ITEM_DESCENDING_IMAGE, sort_asc ? state->image_ascending
251 : state->image_descending,
252 ITEM_CASE_SENSITIVE, gnm_conf_get_core_sort_default_by_case (),
253 ITEM_SORT_BY_VALUE, TRUE,
254 ITEM_MOVE_FORMAT, TRUE,
255 ITEM_NUMBER, i,
256 -1);
257 state->sort_items++;
258 g_free (str);
259 g_free (header);
262 static void
263 cb_sort_field_selection(G_GNUC_UNUSED GtkWidget *item, AddSortFieldMenuState *menu_state)
265 append_data(menu_state->state,
266 menu_state->start,
267 menu_state->index);
268 /* Update sensitivity if this is the first sort item. */
269 if (menu_state->state->sort_items == 1)
270 set_button_sensitivity(menu_state->state);
273 static void
274 build_sort_field_menu (gint start, gint end, gint index, GtkWidget *menu, SortFlowState *state, int used)
276 Sheet *sheet = state->sel->v_range.cell.a.sheet;
277 GtkWidget *item;
278 GtkWidget *submenu;
279 int i;
280 int this_end;
281 char *str;
282 char *str_start;
283 char *str_end;
284 AddSortFieldMenuState *menu_state;
285 gint menu_size;
287 menu_size = 1 + end - start;
288 if (MAX_MENU_SIZE < menu_size - used) {
289 gint submenu_size;
290 gint balanced_submenu_size;
292 submenu_size = (menu_size + MAX_MENU_SIZE - 1) / MAX_MENU_SIZE;
293 balanced_submenu_size = sqrt((double)
294 (menu_size + MAX_MENU_SIZE - 1));
295 if (balanced_submenu_size > submenu_size)
296 submenu_size = balanced_submenu_size;
298 for (i = start; i <= end; i+=submenu_size) {
299 this_end = i + submenu_size - 1;
300 if (this_end > end)
301 this_end = end;
303 /* See if there are any fields in this range that aren't already
304 in the sort.
306 if (range_already_in_sort_criteria(i, this_end, state))
307 continue;
309 str_start = state->is_cols
310 ? col_row_name (sheet, i, index, state->header, TRUE)
311 : col_row_name (sheet, index, i, state->header, FALSE);
313 str_end = state->is_cols
314 ? col_row_name (sheet, this_end, index, state->header, TRUE)
315 : col_row_name (sheet, index, this_end, state->header, FALSE);
317 str = g_strdup_printf(_("%s to %s"), str_start, str_end);
318 g_free(str_start);
319 g_free(str_end);
321 item = (GtkWidget *) gtk_menu_item_new_with_label(str);
322 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
323 gtk_widget_show (item);
325 menu_state = g_new(AddSortFieldMenuState, 1);
326 menu_state->start = i;
327 menu_state->end = this_end;
328 menu_state->index = index;
329 menu_state->state = state;
330 menu_state->done_submenu = FALSE;
331 submenu = gtk_menu_new();
332 gtk_menu_item_set_submenu(GTK_MENU_ITEM (item), submenu);
333 g_signal_connect (item, "activate",
334 G_CALLBACK (cb_sort_field_menu_activate), menu_state);
336 } else {
337 for (i = start; i <= end; i++) {
338 if (FALSE == already_in_sort_fields(i, state)) {
340 str = state->is_cols
341 ? col_row_name (sheet, i, index, state->header, TRUE)
342 : col_row_name (sheet, index, i, state->header, FALSE);
343 item = (GtkWidget *) gtk_menu_item_new_with_label(str);
344 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
345 gtk_widget_show (item);
346 menu_state = g_new(AddSortFieldMenuState, 1);
347 menu_state->start = i;
348 menu_state->end = i;
349 menu_state->index = index;
350 menu_state->state = state;
351 menu_state->done_submenu = FALSE;
352 g_signal_connect (item, "activate",
353 G_CALLBACK (cb_sort_field_selection),
354 menu_state);
360 static void
361 load_model_data (SortFlowState *state)
363 int start;
364 int end;
365 int index;
366 int i;
367 int limit = gnm_conf_get_core_sort_dialog_max_initial_clauses ();
369 if (state->is_cols) {
370 start = state->sel->v_range.cell.a.col;
371 end = state->sel->v_range.cell.b.col;
372 index = state->sel->v_range.cell.a.row;
373 } else {
374 start = state->sel->v_range.cell.a.row;
375 end = state->sel->v_range.cell.b.row;
376 index = state->sel->v_range.cell.a.col;
379 gtk_list_store_clear (state->model);
380 state->sort_items = 0;
382 if (end >= start + limit)
383 end = start + limit - 1;
385 for (i = start; i <= end; i++)
386 append_data (state, i, index);
389 static void
390 translate_range (GnmValue *range, SortFlowState *state)
392 state->is_cols = !gtk_toggle_button_get_active (
393 GTK_TOGGLE_BUTTON (state->cell_sort_row_rb));
394 state->header = gtk_toggle_button_get_active (
395 GTK_TOGGLE_BUTTON (state->cell_sort_header_check));
397 value_release (state->sel);
398 state->sel = range;
399 load_model_data(state);
402 static void
403 cb_sort_header_check(SortFlowState *state)
405 state->header = gtk_toggle_button_get_active (
406 GTK_TOGGLE_BUTTON (state->cell_sort_header_check));
408 gtk_tree_view_column_set_visible (state->header_column, state->header);
409 set_button_sensitivity (state);
412 static void
413 cb_update_to_new_range (SortFlowState *state)
415 GnmValue *range;
417 range = gnm_expr_entry_parse_as_value
418 (GNM_EXPR_ENTRY (state->range_entry), state->sheet);
419 if (range == NULL) {
420 if (state->sel != NULL) {
421 value_release (state->sel);
422 state->sel = NULL;
423 gtk_list_store_clear (state->model);
424 state->sort_items = 0;
426 } else
427 translate_range (range, state);
428 set_button_sensitivity (state);
431 static void
432 cb_dialog_destroy (SortFlowState *state)
434 value_release (state->sel);
435 state->sel = NULL;
437 g_clear_object (&state->model);
438 g_clear_object (&state->gui);
440 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
442 state->dialog = NULL;
444 g_clear_object (&state->image_ascending);
445 g_clear_object (&state->image_descending);
447 g_free (state);
450 static void
451 cb_dialog_ok_clicked (SortFlowState *state)
453 GnmSortData *data, *data_copy;
454 GnmSortClause *array, *this_array_item;
455 int item = 0;
456 GtkTreeIter iter;
457 gboolean descending, case_sensitive, sort_by_value, move_format;
458 gint number;
459 gint base;
460 char const *text;
462 array = g_new (GnmSortClause, state->sort_items);
463 this_array_item = array;
464 base = (state->is_cols ? state->sel->v_range.cell.a.col : state->sel->v_range.cell.a.row);
466 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
467 &iter, NULL, item)) {
468 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
469 ITEM_DESCENDING,&descending,
470 ITEM_CASE_SENSITIVE, &case_sensitive,
471 ITEM_SORT_BY_VALUE, &sort_by_value,
472 ITEM_MOVE_FORMAT, &move_format,
473 ITEM_NUMBER, &number,
474 -1);
475 item++;
476 this_array_item->offset = number - base;
477 this_array_item->asc = !!descending;
478 this_array_item->cs = case_sensitive;
479 this_array_item->val = sort_by_value;
480 this_array_item++;
484 data = g_new (GnmSortData, 1);
485 data->sheet = state->sel->v_range.cell.a.sheet;
486 data->range = g_new (GnmRange, 1);
487 data->range = range_init (data->range, state->sel->v_range.cell.a.col
488 + ((state->header && !state->is_cols) ? 1 : 0),
489 state->sel->v_range.cell.a.row
490 + ((state->header && state->is_cols) ? 1 : 0),
491 state->sel->v_range.cell.b.col,
492 state->sel->v_range.cell.b.row);
493 data->num_clause = state->sort_items;
494 data->clauses = array;
495 data->top = state->is_cols;
496 data->retain_formats = gtk_toggle_button_get_active (
497 GTK_TOGGLE_BUTTON (state->retain_format_check));
498 data->locale = go_locale_sel_get_locale (state->locale_selector);
500 data_copy = gnm_sort_data_copy (data);
501 text = gnm_expr_entry_get_text (state->range_entry);
502 gnm_sheet_add_sort_setup
503 (data->sheet,
504 g_strdup((text != NULL && text[0] != '\0') ? text : "Other"),
505 data_copy);
507 cmd_sort (GNM_WBC (state->wbcg), data);
509 gtk_widget_destroy (state->dialog);
510 return;
513 static void
514 cb_dialog_cancel_clicked (G_GNUC_UNUSED GtkWidget *button,
515 SortFlowState *state)
517 gtk_widget_destroy (state->dialog);
520 static void
521 dialog_cell_sort_load_sort_setup (SortFlowState *state, GnmSortData const *data)
523 int i;
524 GnmSortClause *this = data->clauses;
525 gint base, max, index;
526 Sheet *sheet = state->sel->v_range.cell.a.sheet;
528 if (sheet == NULL)
529 sheet = state->sheet;
531 go_locale_sel_set_locale (state->locale_selector, data->locale);
533 gtk_toggle_button_set_active (
534 GTK_TOGGLE_BUTTON (state->retain_format_check), data->retain_formats);
536 gtk_toggle_button_set_active (
537 GTK_TOGGLE_BUTTON (state->cell_sort_row_rb), !data->top);
538 state->is_cols = data->top;
540 index = (data->top ? state->sel->v_range.cell.a.row : state->sel->v_range.cell.a.col);
541 base = (data->top ? state->sel->v_range.cell.a.col : state->sel->v_range.cell.a.row);
542 max = (data->top ? state->sel->v_range.cell.b.col : state->sel->v_range.cell.b.row);
543 gtk_list_store_clear (state->model);
544 state->sort_items = 0;
545 for (i = 0; i < data->num_clause; i++) {
546 if (data->clauses[i].offset <= max ) {
547 GtkTreeIter iter;
548 gchar *str, *header;
549 int id = data->clauses[i].offset + base;
551 header = state->is_cols
552 ? header_name (sheet, id, index)
553 : header_name (sheet, index, id);
554 str = col_row_name (sheet, id, id, FALSE, state->is_cols);
556 gtk_list_store_append (state->model, &iter);
557 gtk_list_store_set (state->model, &iter,
558 ITEM_HEADER, header,
559 ITEM_NAME, str,
560 ITEM_DESCENDING, data->clauses[i].asc,
561 ITEM_DESCENDING_IMAGE,
562 !data->clauses[i].asc
563 ? state->image_ascending
564 : state->image_descending,
565 ITEM_CASE_SENSITIVE, data->clauses[i].cs,
566 ITEM_SORT_BY_VALUE, data->clauses[i].val,
567 ITEM_MOVE_FORMAT, TRUE,
568 ITEM_NUMBER, id,
569 -1);
570 state->sort_items++;
572 this++;
574 set_button_sensitivity (state);
577 static GnmRange const *
578 dialog_load_selection (SortFlowState *state, gboolean *col_rb)
580 GnmRange const *first;
581 GnmSortData const *data;
583 first = selection_first_range (state->sv, NULL, NULL);
585 if (first != NULL) {
586 gtk_toggle_button_set_active (
587 GTK_TOGGLE_BUTTON (state->cell_sort_col_rb),
588 (*col_rb = (first->end.row - first->start.row > first->end.col - first->start.col)));
589 gnm_expr_entry_load_from_range (state->range_entry,
590 state->sheet, first);
591 } else
592 gtk_toggle_button_set_active (
593 GTK_TOGGLE_BUTTON (state->cell_sort_col_rb),
594 (*col_rb = TRUE));
596 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->cell_sort_header_check),
597 sheet_range_has_heading
598 (state->sheet, first, *col_rb, FALSE));
599 cb_sort_header_check (state);
601 data = gnm_sheet_find_sort_setup (state->sheet,
602 gnm_expr_entry_get_text (state->range_entry));
603 if (data != NULL)
604 dialog_cell_sort_load_sort_setup (state, data);
605 else
606 cb_update_to_new_range (state);
608 return first;
612 * Refreshes the buttons on a row (un)selection
615 static void
616 cb_sort_selection_changed (SortFlowState *state)
618 GtkTreeIter iter, test;
620 if (!gtk_tree_selection_get_selected (state->selection, NULL, &iter)) {
621 gtk_widget_set_sensitive (state->up_button, FALSE);
622 gtk_widget_set_sensitive (state->down_button, FALSE);
623 gtk_widget_set_sensitive (state->delete_button, FALSE);
624 return;
627 test = iter;
628 gtk_widget_set_sensitive
629 (state->up_button,
630 gtk_tree_model_iter_previous (GTK_TREE_MODEL (state->model),
631 &test));
633 test = iter;
634 gtk_widget_set_sensitive
635 (state->down_button,
636 gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model),
637 &test));
639 gtk_widget_set_sensitive (state->delete_button, TRUE);
640 set_button_sensitivity (state);
643 static void
644 toggled (SortFlowState *state, const gchar *path_string, int column)
646 GtkTreeModel *model = GTK_TREE_MODEL (state->model);
647 GtkTreeIter iter;
648 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
649 gboolean value;
651 if (gtk_tree_model_get_iter (model, &iter, path)) {
652 gtk_tree_model_get (model, &iter, column, &value, -1);
653 value = !value;
654 gtk_list_store_set (GTK_LIST_STORE (model),
655 &iter, column, value, -1);
656 } else {
657 g_warning ("Did not get a valid iterator");
660 gtk_tree_path_free (path);
663 static void
664 move_cb (SortFlowState *state,
665 gboolean (*mover) (GtkTreeModel *, GtkTreeIter *))
667 GtkTreeIter iter, this_iter;
669 if (!gtk_tree_selection_get_selected (state->selection, NULL,
670 &this_iter))
671 return;
673 iter = this_iter;
674 if (!mover (GTK_TREE_MODEL(state->model), &iter))
675 return;
677 gtk_list_store_swap (state->model, &this_iter, &iter);
678 cb_sort_selection_changed (state);
681 static void
682 cb_up (SortFlowState *state)
684 move_cb (state, gtk_tree_model_iter_previous);
687 static void
688 cb_down (SortFlowState *state)
690 move_cb (state, gtk_tree_model_iter_next);
693 static void
694 cb_delete_clicked (G_GNUC_UNUSED GtkWidget *w, SortFlowState *state)
696 GtkTreeIter iter, iter2;
697 gboolean ok;
699 if (!gtk_tree_selection_get_selected (state->selection, NULL, &iter))
700 return;
702 iter2 = iter;
703 ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model), &iter2);
704 if (!ok) {
705 iter2 = iter;
706 ok = gtk_tree_model_iter_previous (GTK_TREE_MODEL (state->model), &iter2);
709 if (ok)
710 gtk_tree_selection_select_iter (state->selection, &iter2);
712 gtk_list_store_remove (state->model, &iter);
713 state->sort_items--;
714 set_button_sensitivity (state);
718 static void
719 cb_clear_clicked (SortFlowState *state)
721 state->sort_items = 0;
722 gtk_list_store_clear (state->model);
723 set_button_sensitivity (state);
726 static GtkMenu *
727 build_sort_field_base_menu (SortFlowState *state)
729 gint start;
730 gint end;
731 gint index;
733 GtkWidget *menu = gtk_menu_new ();
734 GList* items = NULL;
736 if (state->sel != NULL) {
737 if (state->is_cols) {
738 start = state->sel->v_range.cell.a.col;
739 end = state->sel->v_range.cell.b.col;
740 index = state->sel->v_range.cell.a.row;
741 } else {
742 start = state->sel->v_range.cell.a.row;
743 end = state->sel->v_range.cell.b.row;
744 index = state->sel->v_range.cell.a.col;
747 build_sort_field_menu (start, end, index, menu, state,
748 state->sort_items);
750 items = gtk_container_get_children (GTK_CONTAINER (menu));
753 if (items == NULL) {
754 GtkWidget *item;
755 item = (GtkWidget *) gtk_menu_item_new_with_label(state->is_cols ?
756 _("no available column"): _("no available row"));
757 gtk_widget_set_sensitive( GTK_WIDGET (item), FALSE);
758 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
759 gtk_widget_show (item);
762 g_list_free (items);
764 return GTK_MENU (menu);
767 static void
768 show_add_menu (SortFlowState *state)
770 gnumeric_popup_menu (build_sort_field_base_menu(state),
771 NULL);
774 static void
775 cb_add_clicked (SortFlowState *state)
777 GnmValue *range_add;
778 GnmSheetRange grange_sort, grange_add;
779 GnmRange intersection;
780 int start;
781 int end;
782 int index;
783 int i;
784 gboolean had_items = (state->sort_items > 0);
786 range_add = gnm_expr_entry_parse_as_value
787 (GNM_EXPR_ENTRY (state->add_entry), state->sheet);
789 if (range_add == NULL) {
790 show_add_menu (state);
791 return;
794 g_return_if_fail (range_add != NULL && state->sel != NULL);
796 gnm_sheet_range_from_value (&grange_sort, state->sel);
797 gnm_sheet_range_from_value (&grange_add, range_add);
799 value_release (range_add);
801 if (range_intersection (&intersection, &grange_sort.range, &grange_add.range)) {
803 if (state->is_cols) {
804 start = intersection.start.col;
805 end = intersection.end.col;
806 index = state->sel->v_range.cell.a.row;
807 } else {
808 start = intersection.start.row;
809 end = intersection.end.row;
810 index = state->sel->v_range.cell.a.col;
813 for (i = start; i <= end; i++) {
815 int item = 0;
816 GtkTreeIter iter;
817 gboolean found = FALSE;
818 gint number;
820 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
821 &iter, NULL, item)) {
822 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
823 ITEM_NUMBER, &number,
824 -1);
825 item++;
826 if (number == i) {
827 found = TRUE;
828 break;
832 if (!found) {
833 append_data (state, i, index);
836 if (!had_items && (state->sort_items > 0))
837 set_button_sensitivity(state);
838 } else
839 show_add_menu (state);
840 gnm_expr_entry_load_from_text (GNM_EXPR_ENTRY (state->add_entry), "");
843 static gint
844 cb_treeview_button_press(G_GNUC_UNUSED GtkWidget *w, GdkEvent *event, SortFlowState *state)
846 if ((event->type == GDK_BUTTON_PRESS) &&
847 (event->button.button == 3)) {
848 gnumeric_popup_menu (build_sort_field_base_menu(state),
849 event);
850 return TRUE;
853 return FALSE;
856 static gint
857 cb_treeview_keypress (G_GNUC_UNUSED GtkWidget *w, GdkEventKey *event,
858 SortFlowState *state)
860 gboolean ctrl = (event->state & GDK_CONTROL_MASK);
861 GtkTreeIter iter;
863 switch (event->keyval) {
864 case GDK_KEY_Delete:
865 case GDK_KEY_KP_Delete:
866 cb_delete_clicked (w, state);
867 return TRUE;
868 case GDK_KEY_KP_Up:
869 case GDK_KEY_Up:
870 if (ctrl) {
871 cb_up (state);
872 return TRUE;
875 if (gtk_tree_selection_get_selected (state->selection, NULL, &iter) &&
876 gtk_tree_model_iter_previous (GTK_TREE_MODEL (state->model), &iter))
877 gtk_tree_selection_select_iter (state->selection,
878 &iter);
879 return TRUE;
881 case GDK_KEY_KP_Down:
882 case GDK_KEY_Down:
883 if (ctrl) {
884 cb_down (state);
885 return TRUE;
888 if (gtk_tree_selection_get_selected (state->selection, NULL, &iter) &&
889 gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model), &iter))
890 gtk_tree_selection_select_iter (state->selection,
891 &iter);
892 return TRUE;
894 return FALSE;
897 static void
898 cb_toggled_descending (G_GNUC_UNUSED GtkCellRendererToggle *cell,
899 const gchar *path_string,
900 SortFlowState *state)
902 GtkTreeModel *model = GTK_TREE_MODEL (state->model);
903 GtkTreeIter iter;
904 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
905 gboolean value;
907 if (gtk_tree_model_get_iter (model, &iter, path)) {
908 gtk_tree_model_get (model, &iter, ITEM_DESCENDING, &value, -1);
909 if (value) {
910 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
911 ITEM_DESCENDING, FALSE,
912 ITEM_DESCENDING_IMAGE, state->image_ascending,
913 -1);
914 } else {
915 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
916 ITEM_DESCENDING, TRUE,
917 ITEM_DESCENDING_IMAGE, state->image_descending,
918 -1);
920 } else {
921 g_warning ("Did not get a valid iterator");
923 gtk_tree_path_free (path);
926 #if 0
927 /* We are currently not supporting `by-value' vs not. */
928 static void
929 cb_toggled_sort_by_value (G_GNUC_UNUSED GtkCellRendererToggle *cell,
930 const gchar *path_string,
931 SortFlowState *state)
933 toggled (state, path_string, ITEM_SORT_BY_VALUE);
935 #endif
937 static void
938 cb_toggled_case_sensitive (G_GNUC_UNUSED GtkCellRendererToggle *cell,
939 const gchar *path_string,
940 SortFlowState *state)
942 toggled (state, path_string, ITEM_CASE_SENSITIVE);
946 static void
947 dialog_init (SortFlowState *state)
949 GtkGrid *grid;
950 GtkWidget *scrolled;
951 GtkTreeViewColumn *column;
952 GtkCellRenderer *renderer;
953 gboolean col_rb;
955 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui, "cell-sort-grid"));
956 /* setup range entry */
957 state->range_entry = gnm_expr_entry_new (state->wbcg, TRUE);
958 gnm_expr_entry_set_flags (state->range_entry,
959 GNM_EE_SINGLE_RANGE,
960 GNM_EE_MASK);
961 gtk_widget_set_hexpand (GTK_WIDGET (state->range_entry), TRUE);
962 gtk_grid_attach (grid, GTK_WIDGET (state->range_entry),
963 1, 1, 2, 1);
964 gnm_editable_enters (GTK_WINDOW (state->dialog),
965 GTK_WIDGET (state->range_entry));
966 gnm_expr_entry_set_update_policy (state->range_entry, GNM_UPDATE_DISCONTINUOUS);
967 gtk_widget_show (GTK_WIDGET (state->range_entry));
968 g_signal_connect_swapped (G_OBJECT (state->range_entry),
969 "changed",
970 G_CALLBACK (cb_update_to_new_range), state);
972 state->locale_selector = GO_LOCALE_SEL (go_locale_sel_new ());
973 gtk_widget_set_hexpand (GTK_WIDGET (state->locale_selector), TRUE);
974 gtk_widget_show_all (GTK_WIDGET (state->locale_selector));
975 gtk_grid_attach (grid, GTK_WIDGET (state->locale_selector),
976 1, 5, 2, 1);
978 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui, "cell-sort-spec-grid"));
979 /* setup add entry */
980 state->add_entry = gnm_expr_entry_new (state->wbcg, TRUE);
981 gnm_expr_entry_set_flags (state->add_entry,
982 GNM_EE_SINGLE_RANGE,
983 GNM_EE_MASK);
984 gtk_widget_set_hexpand (GTK_WIDGET (state->add_entry), TRUE);
985 gtk_grid_attach (grid, GTK_WIDGET (state->add_entry),
986 0, 5, 1, 1);
987 gnm_editable_enters (GTK_WINDOW (state->dialog),
988 GTK_WIDGET (state->add_entry));
989 gtk_widget_show (GTK_WIDGET (state->add_entry));
991 /* Set-up tree view */
992 scrolled = go_gtk_builder_get_widget (state->gui, "scrolled_cell_sort_list");
993 state->model = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING,
994 G_TYPE_STRING, G_TYPE_BOOLEAN,
995 GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN,
996 G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
997 G_TYPE_INT);
998 state->treeview = GTK_TREE_VIEW (
999 gtk_tree_view_new_with_model (GTK_TREE_MODEL (state->model)));
1000 state->selection = gtk_tree_view_get_selection (state->treeview);
1001 gtk_tree_selection_set_mode (state->selection, GTK_SELECTION_BROWSE);
1002 g_signal_connect_swapped (state->selection,
1003 "changed",
1004 G_CALLBACK (cb_sort_selection_changed), state);
1006 state->header_column = gtk_tree_view_column_new_with_attributes
1007 (_("Header"),
1008 gtk_cell_renderer_text_new (),
1009 "text", ITEM_HEADER, NULL);
1010 gtk_tree_view_append_column (state->treeview, state->header_column);
1012 column = gtk_tree_view_column_new_with_attributes
1013 (_("Row/Column"),
1014 gtk_cell_renderer_text_new (),
1015 "text", ITEM_NAME, NULL);
1016 gtk_tree_view_append_column (state->treeview, column);
1018 renderer = gnumeric_cell_renderer_toggle_new ();
1019 g_signal_connect (G_OBJECT (renderer),
1020 "toggled",
1021 G_CALLBACK (cb_toggled_descending), state);
1022 column = gtk_tree_view_column_new_with_attributes ("",
1023 renderer,
1024 "active", ITEM_DESCENDING,
1025 "pixbuf", ITEM_DESCENDING_IMAGE,
1026 NULL);
1027 gtk_tree_view_append_column (state->treeview, column);
1029 renderer = gtk_cell_renderer_toggle_new ();
1030 g_signal_connect (G_OBJECT (renderer),
1031 "toggled",
1032 G_CALLBACK (cb_toggled_case_sensitive), state);
1033 column = gtk_tree_view_column_new_with_attributes (_("Case Sensitive"),
1034 renderer,
1035 "active", ITEM_CASE_SENSITIVE, NULL);
1036 gtk_tree_view_append_column (state->treeview, column);
1038 gtk_tree_view_columns_autosize (state->treeview);
1041 g_signal_connect (G_OBJECT (state->treeview),
1042 "key_press_event",
1043 G_CALLBACK (cb_treeview_keypress), state);
1044 g_signal_connect (G_OBJECT (state->treeview),
1045 "button_press_event",
1046 G_CALLBACK (cb_treeview_button_press), state);
1047 #if 0
1048 /* We are currently not supporting `by-value' vs not. */
1049 renderer = gtk_cell_renderer_toggle_new ();
1050 g_signal_connect (G_OBJECT (renderer),
1051 "toggled",
1052 G_CALLBACK (cb_toggled_sort_by_value), state);
1053 column = gtk_tree_view_column_new_with_attributes (_("By Value"),
1054 renderer,
1055 "active", ITEM_SORT_BY_VALUE, NULL);
1056 gtk_tree_view_append_column (state->treeview, column);
1057 #endif
1059 gtk_tree_view_set_reorderable (state->treeview,TRUE);
1061 gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (state->treeview));
1062 gtk_widget_show (GTK_WIDGET (state->treeview));
1064 /* Set-up other widgets */
1065 state->cell_sort_row_rb = go_gtk_builder_get_widget (state->gui, "cell_sort_row_rb");
1066 state->cell_sort_col_rb = go_gtk_builder_get_widget (state->gui, "cell_sort_col_rb");
1067 g_signal_connect_swapped (G_OBJECT (state->cell_sort_row_rb),
1068 "toggled",
1069 G_CALLBACK (cb_update_to_new_range), state);
1071 state->cell_sort_header_check = go_gtk_builder_get_widget (state->gui,
1072 "cell_sort_header_check");
1073 g_signal_connect_swapped (G_OBJECT (state->cell_sort_header_check),
1074 "toggled",
1075 G_CALLBACK (cb_sort_header_check), state);
1077 state->retain_format_check = go_gtk_builder_get_widget (state->gui,
1078 "retain_format_button");
1079 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->retain_format_check),
1080 gnm_conf_get_core_sort_default_retain_formats ());
1083 /* Set-up buttons */
1084 state->up_button = go_gtk_builder_get_widget (state->gui, "up_button");
1085 g_signal_connect_swapped (G_OBJECT (state->up_button),
1086 "clicked",
1087 G_CALLBACK (cb_up), state);
1088 state->down_button = go_gtk_builder_get_widget (state->gui, "down_button");
1089 g_signal_connect_swapped (G_OBJECT (state->down_button),
1090 "clicked",
1091 G_CALLBACK (cb_down), state);
1092 state->add_button = go_gtk_builder_get_widget (state->gui, "add_button");
1093 g_signal_connect_swapped (G_OBJECT (state->add_button),
1094 "clicked",
1095 G_CALLBACK (cb_add_clicked), state);
1096 gtk_widget_set_sensitive (state->add_button, TRUE);
1097 state->delete_button = go_gtk_builder_get_widget (state->gui, "delete_button");
1098 g_signal_connect (G_OBJECT (state->delete_button),
1099 "clicked",
1100 G_CALLBACK (cb_delete_clicked), state);
1101 gtk_widget_set_sensitive (state->delete_button, FALSE);
1103 state->clear_button = go_gtk_builder_get_widget (state->gui, "clear_button");
1104 g_signal_connect_swapped (G_OBJECT (state->clear_button),
1105 "clicked",
1106 G_CALLBACK (cb_clear_clicked), state);
1107 gtk_widget_set_sensitive (state->clear_button, FALSE);
1109 gtk_button_set_alignment (GTK_BUTTON (state->up_button), 0., .5);
1110 gtk_button_set_alignment (GTK_BUTTON (state->down_button), 0., .5);
1111 gtk_button_set_alignment (GTK_BUTTON (state->add_button), 0., .5);
1112 gtk_button_set_alignment (GTK_BUTTON (state->delete_button), 0., .5);
1113 gtk_button_set_alignment (GTK_BUTTON (state->clear_button), 0., .5);
1114 gnm_init_help_button (
1115 go_gtk_builder_get_widget (state->gui, "help_button"),
1116 GNUMERIC_HELP_LINK_CELL_SORT);
1118 state->ok_button = go_gtk_builder_get_widget (state->gui, "ok_button");
1119 g_signal_connect_swapped (G_OBJECT (state->ok_button),
1120 "clicked",
1121 G_CALLBACK (cb_dialog_ok_clicked), state);
1122 state->cancel_button = go_gtk_builder_get_widget (state->gui, "cancel_button");
1123 g_signal_connect (G_OBJECT (state->cancel_button),
1124 "clicked",
1125 G_CALLBACK (cb_dialog_cancel_clicked), state);
1127 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
1128 state->wbcg,
1129 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
1131 /* Finish dialog signals */
1132 wbc_gtk_attach_guru (state->wbcg, state->dialog);
1133 g_object_set_data_full (G_OBJECT (state->dialog),
1134 "state", state, (GDestroyNotify) cb_dialog_destroy);
1136 dialog_load_selection (state, &col_rb);
1138 cb_sort_selection_changed (state);
1139 gnm_expr_entry_grab_focus(GNM_EXPR_ENTRY (state->add_entry), TRUE);
1143 * Main entry point for the Cell Sort dialog box
1145 void
1146 dialog_cell_sort (WBCGtk *wbcg)
1148 SortFlowState *state;
1149 GtkBuilder *gui;
1151 g_return_if_fail (wbcg != NULL);
1153 if (gnm_dialog_raise_if_exists (wbcg, CELL_SORT_KEY))
1154 return;
1156 gui = gnm_gtk_builder_load ("res:ui/cell-sort.ui", NULL, GO_CMD_CONTEXT (wbcg));
1157 if (gui == NULL)
1158 return;
1160 state = g_new (SortFlowState, 1);
1161 state->wbcg = wbcg;
1162 state->wb = wb_control_get_workbook (GNM_WBC (wbcg));
1163 state->sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
1164 state->sheet = sv_sheet (state->sv);
1165 state->warning_dialog = NULL;
1166 state->sel = NULL;
1167 state->sort_items = 0;
1168 state->gui = gui;
1169 state->dialog = go_gtk_builder_get_widget (state->gui, "CellSort");
1171 state->image_ascending =
1172 go_gtk_widget_render_icon_pixbuf (state->dialog,
1173 "view-sort-ascending",
1174 GTK_ICON_SIZE_LARGE_TOOLBAR);
1175 state->image_descending =
1176 go_gtk_widget_render_icon_pixbuf (state->dialog,
1177 "view-sort-descending",
1178 GTK_ICON_SIZE_LARGE_TOOLBAR);
1179 dialog_init (state);
1181 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1182 CELL_SORT_KEY);
1184 gtk_widget_show (state->dialog);