Compilation: don't compile dialogs separately.
[gnumeric.git] / src / dialogs / dialog-cell-sort.c
blob2af6f3888979b96639e785703b246b9fd1ff9ae5
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 * cb_sort_selection_changed:
614 * Refreshes the buttons on a row (un)selection
616 static void
617 cb_sort_selection_changed (SortFlowState *state)
619 GtkTreeIter iter, test;
621 if (!gtk_tree_selection_get_selected (state->selection, NULL, &iter)) {
622 gtk_widget_set_sensitive (state->up_button, FALSE);
623 gtk_widget_set_sensitive (state->down_button, FALSE);
624 gtk_widget_set_sensitive (state->delete_button, FALSE);
625 return;
628 test = iter;
629 gtk_widget_set_sensitive
630 (state->up_button,
631 gtk_tree_model_iter_previous (GTK_TREE_MODEL (state->model),
632 &test));
634 test = iter;
635 gtk_widget_set_sensitive
636 (state->down_button,
637 gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model),
638 &test));
640 gtk_widget_set_sensitive (state->delete_button, TRUE);
641 set_button_sensitivity (state);
644 static void
645 toggled (SortFlowState *state, const gchar *path_string, int column)
647 GtkTreeModel *model = GTK_TREE_MODEL (state->model);
648 GtkTreeIter iter;
649 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
650 gboolean value;
652 if (gtk_tree_model_get_iter (model, &iter, path)) {
653 gtk_tree_model_get (model, &iter, column, &value, -1);
654 value = !value;
655 gtk_list_store_set (GTK_LIST_STORE (model),
656 &iter, column, value, -1);
657 } else {
658 g_warning ("Did not get a valid iterator");
661 gtk_tree_path_free (path);
664 static void
665 move_cb (SortFlowState *state,
666 gboolean (*mover) (GtkTreeModel *, GtkTreeIter *))
668 GtkTreeIter iter, this_iter;
670 if (!gtk_tree_selection_get_selected (state->selection, NULL,
671 &this_iter))
672 return;
674 iter = this_iter;
675 if (!mover (GTK_TREE_MODEL(state->model), &iter))
676 return;
678 gtk_list_store_swap (state->model, &this_iter, &iter);
679 cb_sort_selection_changed (state);
682 static void
683 cb_up (SortFlowState *state)
685 move_cb (state, gtk_tree_model_iter_previous);
688 static void
689 cb_down (SortFlowState *state)
691 move_cb (state, gtk_tree_model_iter_next);
694 static void
695 cb_delete_clicked (G_GNUC_UNUSED GtkWidget *w, SortFlowState *state)
697 GtkTreeIter iter, iter2;
698 gboolean ok;
700 if (!gtk_tree_selection_get_selected (state->selection, NULL, &iter))
701 return;
703 iter2 = iter;
704 ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model), &iter2);
705 if (!ok) {
706 iter2 = iter;
707 ok = gtk_tree_model_iter_previous (GTK_TREE_MODEL (state->model), &iter2);
710 if (ok)
711 gtk_tree_selection_select_iter (state->selection, &iter2);
713 gtk_list_store_remove (state->model, &iter);
714 state->sort_items--;
715 set_button_sensitivity (state);
719 static void
720 cb_clear_clicked (SortFlowState *state)
722 state->sort_items = 0;
723 gtk_list_store_clear (state->model);
724 set_button_sensitivity (state);
727 static GtkMenu *
728 build_sort_field_base_menu (SortFlowState *state)
730 gint start;
731 gint end;
732 gint index;
734 GtkWidget *menu = gtk_menu_new ();
735 GList* items = NULL;
737 if (state->sel != NULL) {
738 if (state->is_cols) {
739 start = state->sel->v_range.cell.a.col;
740 end = state->sel->v_range.cell.b.col;
741 index = state->sel->v_range.cell.a.row;
742 } else {
743 start = state->sel->v_range.cell.a.row;
744 end = state->sel->v_range.cell.b.row;
745 index = state->sel->v_range.cell.a.col;
748 build_sort_field_menu (start, end, index, menu, state,
749 state->sort_items);
751 items = gtk_container_get_children (GTK_CONTAINER (menu));
754 if (items == NULL) {
755 GtkWidget *item;
756 item = (GtkWidget *) gtk_menu_item_new_with_label(state->is_cols ?
757 _("no available column"): _("no available row"));
758 gtk_widget_set_sensitive( GTK_WIDGET (item), FALSE);
759 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
760 gtk_widget_show (item);
763 g_list_free (items);
765 return GTK_MENU (menu);
768 static void
769 show_add_menu (SortFlowState *state)
771 gnumeric_popup_menu (build_sort_field_base_menu(state),
772 NULL);
775 static void
776 cb_add_clicked (SortFlowState *state)
778 GnmValue *range_add;
779 GnmSheetRange grange_sort, grange_add;
780 GnmRange intersection;
781 int start;
782 int end;
783 int index;
784 int i;
785 gboolean had_items = (state->sort_items > 0);
787 range_add = gnm_expr_entry_parse_as_value
788 (GNM_EXPR_ENTRY (state->add_entry), state->sheet);
790 if (range_add == NULL) {
791 show_add_menu (state);
792 return;
795 g_return_if_fail (range_add != NULL && state->sel != NULL);
797 gnm_sheet_range_from_value (&grange_sort, state->sel);
798 gnm_sheet_range_from_value (&grange_add, range_add);
800 value_release (range_add);
802 if (range_intersection (&intersection, &grange_sort.range, &grange_add.range)) {
804 if (state->is_cols) {
805 start = intersection.start.col;
806 end = intersection.end.col;
807 index = state->sel->v_range.cell.a.row;
808 } else {
809 start = intersection.start.row;
810 end = intersection.end.row;
811 index = state->sel->v_range.cell.a.col;
814 for (i = start; i <= end; i++) {
816 int item = 0;
817 GtkTreeIter iter;
818 gboolean found = FALSE;
819 gint number;
821 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
822 &iter, NULL, item)) {
823 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
824 ITEM_NUMBER, &number,
825 -1);
826 item++;
827 if (number == i) {
828 found = TRUE;
829 break;
833 if (!found) {
834 append_data (state, i, index);
837 if (!had_items && (state->sort_items > 0))
838 set_button_sensitivity(state);
839 } else
840 show_add_menu (state);
841 gnm_expr_entry_load_from_text (GNM_EXPR_ENTRY (state->add_entry), "");
844 static gint
845 cb_treeview_button_press(G_GNUC_UNUSED GtkWidget *w, GdkEvent *event, SortFlowState *state)
847 if ((event->type == GDK_BUTTON_PRESS) &&
848 (event->button.button == 3)) {
849 gnumeric_popup_menu (build_sort_field_base_menu(state),
850 event);
851 return TRUE;
854 return FALSE;
857 static gint
858 cb_treeview_keypress (G_GNUC_UNUSED GtkWidget *w, GdkEventKey *event,
859 SortFlowState *state)
861 gboolean ctrl = (event->state & GDK_CONTROL_MASK);
862 GtkTreeIter iter;
864 switch (event->keyval) {
865 case GDK_KEY_Delete:
866 case GDK_KEY_KP_Delete:
867 cb_delete_clicked (w, state);
868 return TRUE;
869 case GDK_KEY_KP_Up:
870 case GDK_KEY_Up:
871 if (ctrl) {
872 cb_up (state);
873 return TRUE;
876 if (gtk_tree_selection_get_selected (state->selection, NULL, &iter) &&
877 gtk_tree_model_iter_previous (GTK_TREE_MODEL (state->model), &iter))
878 gtk_tree_selection_select_iter (state->selection,
879 &iter);
880 return TRUE;
882 case GDK_KEY_KP_Down:
883 case GDK_KEY_Down:
884 if (ctrl) {
885 cb_down (state);
886 return TRUE;
889 if (gtk_tree_selection_get_selected (state->selection, NULL, &iter) &&
890 gtk_tree_model_iter_next (GTK_TREE_MODEL (state->model), &iter))
891 gtk_tree_selection_select_iter (state->selection,
892 &iter);
893 return TRUE;
895 return FALSE;
898 static void
899 cb_toggled_descending (G_GNUC_UNUSED GtkCellRendererToggle *cell,
900 const gchar *path_string,
901 SortFlowState *state)
903 GtkTreeModel *model = GTK_TREE_MODEL (state->model);
904 GtkTreeIter iter;
905 GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
906 gboolean value;
908 if (gtk_tree_model_get_iter (model, &iter, path)) {
909 gtk_tree_model_get (model, &iter, ITEM_DESCENDING, &value, -1);
910 if (value) {
911 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
912 ITEM_DESCENDING, FALSE,
913 ITEM_DESCENDING_IMAGE, state->image_ascending,
914 -1);
915 } else {
916 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
917 ITEM_DESCENDING, TRUE,
918 ITEM_DESCENDING_IMAGE, state->image_descending,
919 -1);
921 } else {
922 g_warning ("Did not get a valid iterator");
924 gtk_tree_path_free (path);
927 #if 0
928 /* We are currently not supporting `by-value' vs not. */
929 static void
930 cb_toggled_sort_by_value (G_GNUC_UNUSED GtkCellRendererToggle *cell,
931 const gchar *path_string,
932 SortFlowState *state)
934 toggled (state, path_string, ITEM_SORT_BY_VALUE);
936 #endif
938 static void
939 cb_toggled_case_sensitive (G_GNUC_UNUSED GtkCellRendererToggle *cell,
940 const gchar *path_string,
941 SortFlowState *state)
943 toggled (state, path_string, ITEM_CASE_SENSITIVE);
947 static void
948 dialog_init (SortFlowState *state)
950 GtkGrid *grid;
951 GtkWidget *scrolled;
952 GtkTreeViewColumn *column;
953 GtkCellRenderer *renderer;
954 gboolean col_rb;
956 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui, "cell-sort-grid"));
957 /* setup range entry */
958 state->range_entry = gnm_expr_entry_new (state->wbcg, TRUE);
959 gnm_expr_entry_set_flags (state->range_entry,
960 GNM_EE_SINGLE_RANGE,
961 GNM_EE_MASK);
962 gtk_widget_set_hexpand (GTK_WIDGET (state->range_entry), TRUE);
963 gtk_grid_attach (grid, GTK_WIDGET (state->range_entry),
964 1, 1, 2, 1);
965 gnm_editable_enters (GTK_WINDOW (state->dialog),
966 GTK_WIDGET (state->range_entry));
967 gnm_expr_entry_set_update_policy (state->range_entry, GNM_UPDATE_DISCONTINUOUS);
968 gtk_widget_show (GTK_WIDGET (state->range_entry));
969 g_signal_connect_swapped (G_OBJECT (state->range_entry),
970 "changed",
971 G_CALLBACK (cb_update_to_new_range), state);
973 state->locale_selector = GO_LOCALE_SEL (go_locale_sel_new ());
974 gtk_widget_set_hexpand (GTK_WIDGET (state->locale_selector), TRUE);
975 gtk_widget_show_all (GTK_WIDGET (state->locale_selector));
976 gtk_grid_attach (grid, GTK_WIDGET (state->locale_selector),
977 1, 5, 2, 1);
979 grid = GTK_GRID (go_gtk_builder_get_widget (state->gui, "cell-sort-spec-grid"));
980 /* setup add entry */
981 state->add_entry = gnm_expr_entry_new (state->wbcg, TRUE);
982 gnm_expr_entry_set_flags (state->add_entry,
983 GNM_EE_SINGLE_RANGE,
984 GNM_EE_MASK);
985 gtk_widget_set_hexpand (GTK_WIDGET (state->add_entry), TRUE);
986 gtk_grid_attach (grid, GTK_WIDGET (state->add_entry),
987 0, 5, 1, 1);
988 gnm_editable_enters (GTK_WINDOW (state->dialog),
989 GTK_WIDGET (state->add_entry));
990 gtk_widget_show (GTK_WIDGET (state->add_entry));
992 /* Set-up tree view */
993 scrolled = go_gtk_builder_get_widget (state->gui, "scrolled_cell_sort_list");
994 state->model = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING,
995 G_TYPE_STRING, G_TYPE_BOOLEAN,
996 GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN,
997 G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
998 G_TYPE_INT);
999 state->treeview = GTK_TREE_VIEW (
1000 gtk_tree_view_new_with_model (GTK_TREE_MODEL (state->model)));
1001 state->selection = gtk_tree_view_get_selection (state->treeview);
1002 gtk_tree_selection_set_mode (state->selection, GTK_SELECTION_BROWSE);
1003 g_signal_connect_swapped (state->selection,
1004 "changed",
1005 G_CALLBACK (cb_sort_selection_changed), state);
1007 state->header_column = gtk_tree_view_column_new_with_attributes
1008 (_("Header"),
1009 gtk_cell_renderer_text_new (),
1010 "text", ITEM_HEADER, NULL);
1011 gtk_tree_view_append_column (state->treeview, state->header_column);
1013 column = gtk_tree_view_column_new_with_attributes
1014 (_("Row/Column"),
1015 gtk_cell_renderer_text_new (),
1016 "text", ITEM_NAME, NULL);
1017 gtk_tree_view_append_column (state->treeview, column);
1019 renderer = gnumeric_cell_renderer_toggle_new ();
1020 g_signal_connect (G_OBJECT (renderer),
1021 "toggled",
1022 G_CALLBACK (cb_toggled_descending), state);
1023 column = gtk_tree_view_column_new_with_attributes ("",
1024 renderer,
1025 "active", ITEM_DESCENDING,
1026 "pixbuf", ITEM_DESCENDING_IMAGE,
1027 NULL);
1028 gtk_tree_view_append_column (state->treeview, column);
1030 renderer = gtk_cell_renderer_toggle_new ();
1031 g_signal_connect (G_OBJECT (renderer),
1032 "toggled",
1033 G_CALLBACK (cb_toggled_case_sensitive), state);
1034 column = gtk_tree_view_column_new_with_attributes (_("Case Sensitive"),
1035 renderer,
1036 "active", ITEM_CASE_SENSITIVE, NULL);
1037 gtk_tree_view_append_column (state->treeview, column);
1039 gtk_tree_view_columns_autosize (state->treeview);
1042 g_signal_connect (G_OBJECT (state->treeview),
1043 "key_press_event",
1044 G_CALLBACK (cb_treeview_keypress), state);
1045 g_signal_connect (G_OBJECT (state->treeview),
1046 "button_press_event",
1047 G_CALLBACK (cb_treeview_button_press), state);
1048 #if 0
1049 /* We are currently not supporting `by-value' vs not. */
1050 renderer = gtk_cell_renderer_toggle_new ();
1051 g_signal_connect (G_OBJECT (renderer),
1052 "toggled",
1053 G_CALLBACK (cb_toggled_sort_by_value), state);
1054 column = gtk_tree_view_column_new_with_attributes (_("By Value"),
1055 renderer,
1056 "active", ITEM_SORT_BY_VALUE, NULL);
1057 gtk_tree_view_append_column (state->treeview, column);
1058 #endif
1060 gtk_tree_view_set_reorderable (state->treeview,TRUE);
1062 gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (state->treeview));
1063 gtk_widget_show (GTK_WIDGET (state->treeview));
1065 /* Set-up other widgets */
1066 state->cell_sort_row_rb = go_gtk_builder_get_widget (state->gui, "cell_sort_row_rb");
1067 state->cell_sort_col_rb = go_gtk_builder_get_widget (state->gui, "cell_sort_col_rb");
1068 g_signal_connect_swapped (G_OBJECT (state->cell_sort_row_rb),
1069 "toggled",
1070 G_CALLBACK (cb_update_to_new_range), state);
1072 state->cell_sort_header_check = go_gtk_builder_get_widget (state->gui,
1073 "cell_sort_header_check");
1074 g_signal_connect_swapped (G_OBJECT (state->cell_sort_header_check),
1075 "toggled",
1076 G_CALLBACK (cb_sort_header_check), state);
1078 state->retain_format_check = go_gtk_builder_get_widget (state->gui,
1079 "retain_format_button");
1080 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->retain_format_check),
1081 gnm_conf_get_core_sort_default_retain_formats ());
1084 /* Set-up buttons */
1085 state->up_button = go_gtk_builder_get_widget (state->gui, "up_button");
1086 g_signal_connect_swapped (G_OBJECT (state->up_button),
1087 "clicked",
1088 G_CALLBACK (cb_up), state);
1089 state->down_button = go_gtk_builder_get_widget (state->gui, "down_button");
1090 g_signal_connect_swapped (G_OBJECT (state->down_button),
1091 "clicked",
1092 G_CALLBACK (cb_down), state);
1093 state->add_button = go_gtk_builder_get_widget (state->gui, "add_button");
1094 g_signal_connect_swapped (G_OBJECT (state->add_button),
1095 "clicked",
1096 G_CALLBACK (cb_add_clicked), state);
1097 gtk_widget_set_sensitive (state->add_button, TRUE);
1098 state->delete_button = go_gtk_builder_get_widget (state->gui, "delete_button");
1099 g_signal_connect (G_OBJECT (state->delete_button),
1100 "clicked",
1101 G_CALLBACK (cb_delete_clicked), state);
1102 gtk_widget_set_sensitive (state->delete_button, FALSE);
1104 state->clear_button = go_gtk_builder_get_widget (state->gui, "clear_button");
1105 g_signal_connect_swapped (G_OBJECT (state->clear_button),
1106 "clicked",
1107 G_CALLBACK (cb_clear_clicked), state);
1108 gtk_widget_set_sensitive (state->clear_button, FALSE);
1110 gtk_button_set_alignment (GTK_BUTTON (state->up_button), 0., .5);
1111 gtk_button_set_alignment (GTK_BUTTON (state->down_button), 0., .5);
1112 gtk_button_set_alignment (GTK_BUTTON (state->add_button), 0., .5);
1113 gtk_button_set_alignment (GTK_BUTTON (state->delete_button), 0., .5);
1114 gtk_button_set_alignment (GTK_BUTTON (state->clear_button), 0., .5);
1115 gnm_init_help_button (
1116 go_gtk_builder_get_widget (state->gui, "help_button"),
1117 GNUMERIC_HELP_LINK_CELL_SORT);
1119 state->ok_button = go_gtk_builder_get_widget (state->gui, "ok_button");
1120 g_signal_connect_swapped (G_OBJECT (state->ok_button),
1121 "clicked",
1122 G_CALLBACK (cb_dialog_ok_clicked), state);
1123 state->cancel_button = go_gtk_builder_get_widget (state->gui, "cancel_button");
1124 g_signal_connect (G_OBJECT (state->cancel_button),
1125 "clicked",
1126 G_CALLBACK (cb_dialog_cancel_clicked), state);
1128 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
1129 state->wbcg,
1130 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
1132 /* Finish dialog signals */
1133 wbc_gtk_attach_guru (state->wbcg, state->dialog);
1134 g_object_set_data_full (G_OBJECT (state->dialog),
1135 "state", state, (GDestroyNotify) cb_dialog_destroy);
1137 dialog_load_selection (state, &col_rb);
1139 cb_sort_selection_changed (state);
1140 gnm_expr_entry_grab_focus(GNM_EXPR_ENTRY (state->add_entry), TRUE);
1144 * Main entry point for the Cell Sort dialog box
1146 void
1147 dialog_cell_sort (WBCGtk *wbcg)
1149 SortFlowState *state;
1150 GtkBuilder *gui;
1152 g_return_if_fail (wbcg != NULL);
1154 if (gnm_dialog_raise_if_exists (wbcg, CELL_SORT_KEY))
1155 return;
1157 gui = gnm_gtk_builder_load ("res:ui/cell-sort.ui", NULL, GO_CMD_CONTEXT (wbcg));
1158 if (gui == NULL)
1159 return;
1161 state = g_new (SortFlowState, 1);
1162 state->wbcg = wbcg;
1163 state->wb = wb_control_get_workbook (GNM_WBC (wbcg));
1164 state->sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
1165 state->sheet = sv_sheet (state->sv);
1166 state->warning_dialog = NULL;
1167 state->sel = NULL;
1168 state->sort_items = 0;
1169 state->gui = gui;
1170 state->dialog = go_gtk_builder_get_widget (state->gui, "CellSort");
1172 state->image_ascending =
1173 go_gtk_widget_render_icon_pixbuf (state->dialog,
1174 "view-sort-ascending",
1175 GTK_ICON_SIZE_LARGE_TOOLBAR);
1176 state->image_descending =
1177 go_gtk_widget_render_icon_pixbuf (state->dialog,
1178 "view-sort-descending",
1179 GTK_ICON_SIZE_LARGE_TOOLBAR);
1180 dialog_init (state);
1182 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1183 CELL_SORT_KEY);
1185 gtk_widget_show (state->dialog);