1.12.42
[gnumeric.git] / src / dialogs / dialog-cell-sort.c
blob443b702af610bbcd97012fff9b57f91d9f9a7eb3
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/dialogs.h>
28 #include <dialogs/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/gnm-cell-renderer-toggle.h>
45 #include <widgets/gnm-expr-entry.h>
46 #include <value.h>
48 #include <gsf/gsf-impl-utils.h>
49 #include <gdk/gdkkeysyms.h>
50 #include <goffice/goffice.h>
52 #define CELL_SORT_KEY "cell-sort-dialog"
56 typedef struct {
57 WBCGtk *wbcg;
58 Workbook *wb;
59 SheetView *sv;
60 Sheet *sheet;
62 GtkBuilder *gui;
63 GtkWidget *dialog;
64 GtkWidget *warning_dialog;
65 GtkWidget *cancel_button;
66 GtkWidget *ok_button;
67 GtkWidget *up_button;
68 GtkWidget *down_button;
69 GtkWidget *add_button;
70 GtkWidget *delete_button;
71 GtkWidget *clear_button;
72 GnmExprEntry *range_entry;
73 GnmExprEntry *add_entry;
74 GtkListStore *model;
75 GtkTreeView *treeview;
76 GtkTreeViewColumn *header_column;
77 GtkTreeSelection *selection;
78 GtkWidget *cell_sort_row_rb;
79 GtkWidget *cell_sort_col_rb;
80 GtkWidget *cell_sort_header_check;
81 GtkWidget *retain_format_check;
82 GdkPixbuf *image_ascending;
83 GdkPixbuf *image_descending;
84 GOLocaleSel *locale_selector;
86 GnmValue *sel;
87 gboolean header;
88 gboolean is_cols;
89 int sort_items;
91 } SortFlowState;
93 enum {
94 ITEM_HEADER,
95 ITEM_NAME,
96 ITEM_DESCENDING,
97 ITEM_DESCENDING_IMAGE,
98 ITEM_CASE_SENSITIVE,
99 ITEM_SORT_BY_VALUE,
100 ITEM_MOVE_FORMAT,
101 ITEM_NUMBER,
102 NUM_COLUMNS
105 static const gint MAX_MENU_SIZE = 20;
106 typedef struct {
107 gint index;
108 gint start;
109 gint end;
110 gboolean done_submenu;
111 SortFlowState *state;
112 } AddSortFieldMenuState;
114 static gchar *
115 header_name (Sheet *sheet, int col, int row)
117 GnmCell *cell;
118 gchar *str = NULL;
120 cell = sheet_cell_get (sheet, col, row);
121 if (cell)
122 str = value_get_as_string (cell->value);
124 return str;
128 static gchar *
129 col_row_name (Sheet *sheet, int col, int row, gboolean header, gboolean is_cols)
131 GnmCell *cell;
132 gchar *str = NULL;
134 if (is_cols)
135 str = g_strdup_printf (_("Column %s"), col_name (col));
136 else
137 str = g_strdup_printf (_("Row %s"), row_name (row));
139 if (header) {
140 cell = sheet_cell_get (sheet, col, row);
141 if (cell && !gnm_cell_is_blank (cell)) {
142 gchar *header_str, *generic_str = str;
143 header_str = value_get_as_string (cell->value);
144 str = g_strdup_printf (_("%s (%s)"), header_str, generic_str);
145 g_free (header_str);
146 g_free (generic_str);
150 return str;
154 static gboolean
155 already_in_sort_fields(int index, SortFlowState *state)
157 GtkTreeIter iter;
158 int item = 0;
159 gint number;
161 /* See if index is already in the sort fields */
162 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
163 &iter, NULL, item)) {
164 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
165 ITEM_NUMBER, &number,
166 -1);
167 item++;
169 if (number == index) {
170 return TRUE;
174 /* Here means not already in sort fields */
175 return FALSE;
178 static gboolean
179 range_already_in_sort_criteria(gint start, gint end, SortFlowState *state)
181 gint i;
182 for (i=start; i<=end; i++) {
183 if (!already_in_sort_fields(i, state))
184 return FALSE;
186 return TRUE;
190 static void
191 build_sort_field_menu (gint start, gint end, gint index, GtkWidget *menu, SortFlowState *state, int used);
193 static void
194 cb_sort_field_menu_activate(GtkWidget *item, AddSortFieldMenuState *menu_state)
196 GtkWidget *menu = GTK_WIDGET (gtk_menu_item_get_submenu(GTK_MENU_ITEM (item)));
198 if (menu_state->done_submenu == FALSE) {
199 build_sort_field_menu(menu_state->start,
200 menu_state->end,
201 menu_state->index,
202 menu,
203 menu_state->state, 0);
204 menu_state->done_submenu = TRUE;
208 static void
209 set_button_sensitivity(SortFlowState *state)
211 int items;
213 if (state->sel == NULL) {
214 gtk_widget_set_sensitive (state->ok_button, FALSE);
215 return;
218 items = state->is_cols ? (state->sel->v_range.cell.b.row -
219 state->sel->v_range.cell.a.row + 1) :
220 (state->sel->v_range.cell.b.col -
221 state->sel->v_range.cell.a.col + 1);
222 if (state->header)
223 items -= 1;
224 gtk_widget_set_sensitive (state->ok_button,
225 (state->sort_items != 0) &&
226 (items > 1));
227 gtk_widget_set_sensitive (state->clear_button, state->sort_items != 0);
230 static void
231 append_data (SortFlowState *state, int i, int index)
233 gchar *str, *header;
234 GtkTreeIter iter;
235 Sheet *sheet = state->sel->v_range.cell.a.sheet;
236 gboolean sort_asc = gnm_conf_get_core_sort_default_ascending ();
238 header = state->is_cols
239 ? header_name (sheet, i, index)
240 : header_name (sheet, index, i);
241 str = state->is_cols
242 ? col_row_name (sheet, i, index, FALSE, TRUE)
243 : col_row_name (sheet, index, i, FALSE, FALSE);
244 gtk_list_store_append (state->model, &iter);
245 gtk_list_store_set (state->model, &iter,
246 ITEM_HEADER, header,
247 ITEM_NAME, str,
248 ITEM_DESCENDING, !sort_asc,
249 ITEM_DESCENDING_IMAGE, sort_asc ? state->image_ascending
250 : state->image_descending,
251 ITEM_CASE_SENSITIVE, gnm_conf_get_core_sort_default_by_case (),
252 ITEM_SORT_BY_VALUE, TRUE,
253 ITEM_MOVE_FORMAT, TRUE,
254 ITEM_NUMBER, i,
255 -1);
256 state->sort_items++;
257 g_free (str);
258 g_free (header);
261 static void
262 cb_sort_field_selection(G_GNUC_UNUSED GtkWidget *item, AddSortFieldMenuState *menu_state)
264 append_data(menu_state->state,
265 menu_state->start,
266 menu_state->index);
267 /* Update sensitivity if this is the first sort item. */
268 if (menu_state->state->sort_items == 1)
269 set_button_sensitivity(menu_state->state);
272 static void
273 build_sort_field_menu (gint start, gint end, gint index, GtkWidget *menu, SortFlowState *state, int used)
275 Sheet *sheet = state->sel->v_range.cell.a.sheet;
276 GtkWidget *item;
277 GtkWidget *submenu;
278 int i;
279 int this_end;
280 char *str;
281 char *str_start;
282 char *str_end;
283 AddSortFieldMenuState *menu_state;
284 gint menu_size;
286 menu_size = 1 + end - start;
287 if (MAX_MENU_SIZE < menu_size - used) {
288 gint submenu_size;
289 gint balanced_submenu_size;
291 submenu_size = (menu_size + MAX_MENU_SIZE - 1) / MAX_MENU_SIZE;
292 balanced_submenu_size = sqrt((double)
293 (menu_size + MAX_MENU_SIZE - 1));
294 if (balanced_submenu_size > submenu_size)
295 submenu_size = balanced_submenu_size;
297 for (i = start; i <= end; i+=submenu_size) {
298 this_end = i + submenu_size - 1;
299 if (this_end > end)
300 this_end = end;
302 /* See if there are any fields in this range that aren't already
303 in the sort.
305 if (range_already_in_sort_criteria(i, this_end, state))
306 continue;
308 str_start = state->is_cols
309 ? col_row_name (sheet, i, index, state->header, TRUE)
310 : col_row_name (sheet, index, i, state->header, FALSE);
312 str_end = state->is_cols
313 ? col_row_name (sheet, this_end, index, state->header, TRUE)
314 : col_row_name (sheet, index, this_end, state->header, FALSE);
316 str = g_strdup_printf(_("%s to %s"), str_start, str_end);
317 g_free(str_start);
318 g_free(str_end);
320 item = (GtkWidget *) gtk_menu_item_new_with_label(str);
321 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
322 gtk_widget_show (item);
324 menu_state = g_new(AddSortFieldMenuState, 1);
325 menu_state->start = i;
326 menu_state->end = this_end;
327 menu_state->index = index;
328 menu_state->state = state;
329 menu_state->done_submenu = FALSE;
330 submenu = gtk_menu_new();
331 gtk_menu_item_set_submenu(GTK_MENU_ITEM (item), submenu);
332 g_signal_connect (item, "activate",
333 G_CALLBACK (cb_sort_field_menu_activate), menu_state);
335 } else {
336 for (i = start; i <= end; i++) {
337 if (FALSE == already_in_sort_fields(i, state)) {
339 str = state->is_cols
340 ? col_row_name (sheet, i, index, state->header, TRUE)
341 : col_row_name (sheet, index, i, state->header, FALSE);
342 item = (GtkWidget *) gtk_menu_item_new_with_label(str);
343 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
344 gtk_widget_show (item);
345 menu_state = g_new(AddSortFieldMenuState, 1);
346 menu_state->start = i;
347 menu_state->end = i;
348 menu_state->index = index;
349 menu_state->state = state;
350 menu_state->done_submenu = FALSE;
351 g_signal_connect (item, "activate",
352 G_CALLBACK (cb_sort_field_selection),
353 menu_state);
359 static void
360 load_model_data (SortFlowState *state)
362 int start;
363 int end;
364 int index;
365 int i;
366 int limit = gnm_conf_get_core_sort_dialog_max_initial_clauses ();
368 if (state->is_cols) {
369 start = state->sel->v_range.cell.a.col;
370 end = state->sel->v_range.cell.b.col;
371 index = state->sel->v_range.cell.a.row;
372 } else {
373 start = state->sel->v_range.cell.a.row;
374 end = state->sel->v_range.cell.b.row;
375 index = state->sel->v_range.cell.a.col;
378 gtk_list_store_clear (state->model);
379 state->sort_items = 0;
381 if (end >= start + limit)
382 end = start + limit - 1;
384 for (i = start; i <= end; i++)
385 append_data (state, i, index);
388 static void
389 translate_range (GnmValue *range, SortFlowState *state)
391 state->is_cols = !gtk_toggle_button_get_active (
392 GTK_TOGGLE_BUTTON (state->cell_sort_row_rb));
393 state->header = gtk_toggle_button_get_active (
394 GTK_TOGGLE_BUTTON (state->cell_sort_header_check));
396 value_release (state->sel);
397 state->sel = range;
398 load_model_data(state);
401 static void
402 cb_sort_header_check(SortFlowState *state)
404 state->header = gtk_toggle_button_get_active (
405 GTK_TOGGLE_BUTTON (state->cell_sort_header_check));
407 gtk_tree_view_column_set_visible (state->header_column, state->header);
408 set_button_sensitivity (state);
411 static void
412 cb_update_to_new_range (SortFlowState *state)
414 GnmValue *range;
416 range = gnm_expr_entry_parse_as_value
417 (GNM_EXPR_ENTRY (state->range_entry), state->sheet);
418 if (range == NULL) {
419 if (state->sel != NULL) {
420 value_release (state->sel);
421 state->sel = NULL;
422 gtk_list_store_clear (state->model);
423 state->sort_items = 0;
425 } else
426 translate_range (range, state);
427 set_button_sensitivity (state);
430 static void
431 cb_dialog_destroy (SortFlowState *state)
433 value_release (state->sel);
434 state->sel = NULL;
436 g_clear_object (&state->model);
437 g_clear_object (&state->gui);
439 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
441 state->dialog = NULL;
443 g_clear_object (&state->image_ascending);
444 g_clear_object (&state->image_descending);
446 g_free (state);
449 static void
450 cb_dialog_ok_clicked (SortFlowState *state)
452 GnmSortData *data, *data_copy;
453 GnmSortClause *array, *this_array_item;
454 int item = 0;
455 GtkTreeIter iter;
456 gboolean descending, case_sensitive, sort_by_value, move_format;
457 gint number;
458 gint base;
459 char const *text;
461 array = g_new (GnmSortClause, state->sort_items);
462 this_array_item = array;
463 base = (state->is_cols ? state->sel->v_range.cell.a.col : state->sel->v_range.cell.a.row);
465 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
466 &iter, NULL, item)) {
467 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
468 ITEM_DESCENDING,&descending,
469 ITEM_CASE_SENSITIVE, &case_sensitive,
470 ITEM_SORT_BY_VALUE, &sort_by_value,
471 ITEM_MOVE_FORMAT, &move_format,
472 ITEM_NUMBER, &number,
473 -1);
474 item++;
475 this_array_item->offset = number - base;
476 this_array_item->asc = !!descending;
477 this_array_item->cs = case_sensitive;
478 this_array_item->val = sort_by_value;
479 this_array_item++;
483 data = g_new (GnmSortData, 1);
484 data->sheet = state->sel->v_range.cell.a.sheet;
485 data->range = g_new (GnmRange, 1);
486 data->range = range_init (data->range, state->sel->v_range.cell.a.col
487 + ((state->header && !state->is_cols) ? 1 : 0),
488 state->sel->v_range.cell.a.row
489 + ((state->header && state->is_cols) ? 1 : 0),
490 state->sel->v_range.cell.b.col,
491 state->sel->v_range.cell.b.row);
492 data->num_clause = state->sort_items;
493 data->clauses = array;
494 data->top = state->is_cols;
495 data->retain_formats = gtk_toggle_button_get_active (
496 GTK_TOGGLE_BUTTON (state->retain_format_check));
497 data->locale = go_locale_sel_get_locale (state->locale_selector);
499 data_copy = gnm_sort_data_copy (data);
500 text = gnm_expr_entry_get_text (state->range_entry);
501 gnm_sheet_add_sort_setup
502 (data->sheet,
503 g_strdup((text != NULL && text[0] != '\0') ? text : "Other"),
504 data_copy);
506 cmd_sort (GNM_WBC (state->wbcg), data);
508 gtk_widget_destroy (state->dialog);
509 return;
512 static void
513 cb_dialog_cancel_clicked (G_GNUC_UNUSED GtkWidget *button,
514 SortFlowState *state)
516 gtk_widget_destroy (state->dialog);
519 static void
520 dialog_cell_sort_load_sort_setup (SortFlowState *state, GnmSortData const *data)
522 int i;
523 GnmSortClause *this = data->clauses;
524 gint base, max, index;
525 Sheet *sheet = state->sel->v_range.cell.a.sheet;
527 if (sheet == NULL)
528 sheet = state->sheet;
530 go_locale_sel_set_locale (state->locale_selector, data->locale);
532 gtk_toggle_button_set_active (
533 GTK_TOGGLE_BUTTON (state->retain_format_check), data->retain_formats);
535 gtk_toggle_button_set_active (
536 GTK_TOGGLE_BUTTON (state->cell_sort_row_rb), !data->top);
537 state->is_cols = data->top;
539 index = (data->top ? state->sel->v_range.cell.a.row : state->sel->v_range.cell.a.col);
540 base = (data->top ? state->sel->v_range.cell.a.col : state->sel->v_range.cell.a.row);
541 max = (data->top ? state->sel->v_range.cell.b.col : state->sel->v_range.cell.b.row);
542 gtk_list_store_clear (state->model);
543 state->sort_items = 0;
544 for (i = 0; i < data->num_clause; i++) {
545 if (data->clauses[i].offset <= max ) {
546 GtkTreeIter iter;
547 gchar *str, *header;
548 int id = data->clauses[i].offset + base;
550 header = state->is_cols
551 ? header_name (sheet, id, index)
552 : header_name (sheet, index, id);
553 str = col_row_name (sheet, id, id, FALSE, state->is_cols);
555 gtk_list_store_append (state->model, &iter);
556 gtk_list_store_set (state->model, &iter,
557 ITEM_HEADER, header,
558 ITEM_NAME, str,
559 ITEM_DESCENDING, data->clauses[i].asc,
560 ITEM_DESCENDING_IMAGE,
561 !data->clauses[i].asc
562 ? state->image_ascending
563 : state->image_descending,
564 ITEM_CASE_SENSITIVE, data->clauses[i].cs,
565 ITEM_SORT_BY_VALUE, data->clauses[i].val,
566 ITEM_MOVE_FORMAT, TRUE,
567 ITEM_NUMBER, id,
568 -1);
569 state->sort_items++;
571 this++;
573 set_button_sensitivity (state);
576 static GnmRange const *
577 dialog_load_selection (SortFlowState *state, gboolean *col_rb)
579 GnmRange const *first;
580 GnmSortData const *data;
582 first = selection_first_range (state->sv, NULL, NULL);
584 if (first != NULL) {
585 gtk_toggle_button_set_active (
586 GTK_TOGGLE_BUTTON (state->cell_sort_col_rb),
587 (*col_rb = (first->end.row - first->start.row > first->end.col - first->start.col)));
588 gnm_expr_entry_load_from_range (state->range_entry,
589 state->sheet, first);
590 } else
591 gtk_toggle_button_set_active (
592 GTK_TOGGLE_BUTTON (state->cell_sort_col_rb),
593 (*col_rb = TRUE));
595 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->cell_sort_header_check),
596 sheet_range_has_heading
597 (state->sheet, first, *col_rb, FALSE));
598 cb_sort_header_check (state);
600 data = gnm_sheet_find_sort_setup (state->sheet,
601 gnm_expr_entry_get_text (state->range_entry));
602 if (data != NULL)
603 dialog_cell_sort_load_sort_setup (state, data);
604 else
605 cb_update_to_new_range (state);
607 return first;
611 * cb_sort_selection_changed:
613 * 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 = gnm_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);