ssdiff: move comparison engine into its own file.
[gnumeric.git] / src / dialogs / dialog-search.c
blob0022e838a200f107138e713b22472b1f7f1ba368
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * dialog-search.c:
4 * Dialog for entering a search query.
6 * Author:
7 * Morten Welinder (terra@gnome.org)
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
25 #include <gnumeric.h>
26 #include "dialogs.h"
27 #include "help.h"
29 #include <gui-util.h>
30 #include <gnumeric-conf.h>
31 #include <search.h>
32 #include <sheet.h>
33 #include <sheet-view.h>
34 #include <workbook.h>
35 #include <workbook-view.h>
36 #include <selection.h>
37 #include <cell.h>
38 #include <value.h>
39 #include <parse-util.h>
40 #include <wbc-gtk.h>
41 #include <sheet-object-cell-comment.h>
42 #include <selection.h>
44 #include <widgets/gnumeric-expr-entry.h>
45 #include <widgets/gnumeric-lazy-list.h>
46 #include <gtk/gtk.h>
47 #include <string.h>
49 #define SEARCH_KEY "search-dialog"
51 #undef USE_GURU
53 enum {
54 COL_SHEET = 0,
55 COL_CELL,
56 COL_TYPE,
57 COL_CONTENTS
60 typedef struct {
61 WBCGtk *wbcg;
63 GtkBuilder *gui;
64 GtkDialog *dialog;
65 GnmExprEntry *rangetext;
66 GtkEntry *gentry;
67 GtkWidget *prev_button, *next_button;
68 GtkNotebook *notebook;
69 int notebook_matches_page;
71 GtkTreeView *matches_table;
72 GPtrArray *matches;
73 } DialogState;
75 static const char * const search_type_group[] = {
76 "search_type_text",
77 "search_type_regexp",
78 "search_type_number",
79 NULL
82 static const char * const scope_group[] = {
83 "scope_workbook",
84 "scope_sheet",
85 "scope_range",
86 NULL
89 static const char * const direction_group[] = {
90 "row_major",
91 "column_major",
92 NULL
95 /* ------------------------------------------------------------------------- */
98 /* ------------------------------------------------------------------------- */
100 static void
101 search_get_value (gint row, gint column, gpointer _dd, GValue *value)
103 DialogState *dd = (DialogState *)_dd;
104 GnumericLazyList *ll = GNM_LAZY_LIST (gtk_tree_view_get_model (dd->matches_table));
105 GnmSearchFilterResult *item = g_ptr_array_index (dd->matches, row);
106 GnmCell *cell;
107 GnmComment *comment;
109 if (item->locus == GNM_SRL_COMMENT) {
110 cell = NULL;
111 comment = sheet_get_comment (item->ep.sheet, &item->ep.eval);
112 } else {
113 cell = sheet_cell_get (item->ep.sheet,
114 item->ep.eval.col,
115 item->ep.eval.row);
116 comment = NULL;
119 g_value_init (value, ll->column_headers[column]);
121 #if 0
122 g_print ("col=%d,row=%d\n", column, row);
123 #endif
125 switch (column) {
126 case COL_SHEET:
127 g_value_set_string (value, item->ep.sheet->name_unquoted);
128 return;
129 case COL_CELL:
130 g_value_set_string (value, cellpos_as_string (&item->ep.eval));
131 return;
132 case COL_TYPE:
133 switch (item->locus) {
134 case GNM_SRL_COMMENT:
135 g_value_set_static_string (value, _("Comment"));
136 return;
137 case GNM_SRL_VALUE:
138 g_value_set_static_string (value, _("Result"));
139 return;
140 case GNM_SRL_CONTENTS: {
141 GnmValue *v = cell ? cell->value : NULL;
142 char const *type;
144 gboolean is_expr = cell && gnm_cell_has_expr (cell);
145 gboolean is_value = !is_expr && !gnm_cell_is_empty (cell) && v;
147 if (!cell)
148 type = _("Deleted");
149 else if (is_expr)
150 type = _("Expression");
151 else if (is_value && VALUE_IS_STRING (v))
152 type = _("String");
153 else if (is_value && VALUE_IS_FLOAT (v))
154 type = _("Number");
155 else
156 type = _("Other value");
158 g_value_set_static_string (value, type);
159 return;
162 #ifndef DEBUG_SWITCH_ENUM
163 default:
164 g_assert_not_reached ();
165 #endif
168 case COL_CONTENTS:
169 switch (item->locus) {
170 case GNM_SRL_COMMENT:
171 if (comment)
172 g_value_set_string (value, cell_comment_text_get (comment));
173 else
174 g_value_set_static_string (value, _("Deleted"));
175 return;
176 case GNM_SRL_VALUE:
177 if (cell && cell->value)
178 g_value_take_string (value, value_get_as_string (cell->value));
179 else
180 g_value_set_static_string (value, _("Deleted"));
181 return;
182 case GNM_SRL_CONTENTS:
183 if (cell)
184 g_value_take_string (value, gnm_cell_get_entered_text (cell));
185 else
186 g_value_set_static_string (value, _("Deleted"));
187 return;
188 #ifndef DEBUG_SWITCH_ENUM
189 default:
190 g_assert_not_reached ();
191 #endif
194 #ifndef DEBUG_SWITCH_ENUM
195 default:
196 g_assert_not_reached ();
197 #endif
201 /* ------------------------------------------------------------------------- */
203 static GnumericLazyList *
204 make_matches_model (DialogState *dd, int rows)
206 return gnumeric_lazy_list_new (search_get_value,
208 rows,
210 G_TYPE_STRING,
211 G_TYPE_STRING,
212 G_TYPE_STRING,
213 G_TYPE_STRING);
216 static void
217 free_state (DialogState *dd)
219 gnm_search_filter_matching_free (dd->matches);
220 g_object_unref (dd->gui);
221 memset (dd, 0, sizeof (*dd));
222 g_free (dd);
225 static gboolean
226 range_focused (G_GNUC_UNUSED GtkWidget *widget,
227 G_GNUC_UNUSED GdkEventFocus *event,
228 DialogState *dd)
230 GtkWidget *scope_range = go_gtk_builder_get_widget (dd->gui, "scope_range");
231 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scope_range), TRUE);
232 return FALSE;
235 static gboolean
236 is_checked (GtkBuilder *gui, const char *name)
238 GtkWidget *w = go_gtk_builder_get_widget (gui, name);
239 return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w));
242 static void
243 dialog_search_save_in_prefs (DialogState *dd)
245 GtkBuilder *gui = dd->gui;
247 #define SETW(w,f) f (is_checked (gui, w));
248 SETW("search_expr", gnm_conf_set_searchreplace_change_cell_expressions);
249 SETW("search_other", gnm_conf_set_searchreplace_change_cell_other);
250 SETW("search_string", gnm_conf_set_searchreplace_change_cell_strings);
251 SETW("search_comments", gnm_conf_set_searchreplace_change_comments);
252 SETW("search_expr_results", gnm_conf_set_searchreplace_search_results);
253 SETW("ignore_case", gnm_conf_set_searchreplace_ignore_case);
254 SETW("match_words", gnm_conf_set_searchreplace_whole_words_only);
255 SETW("column_major", gnm_conf_set_searchreplace_columnmajor);
256 #undef SETW
258 gnm_conf_set_searchreplace_regex
259 (go_gtk_builder_group_value (gui, search_type_group));
260 gnm_conf_set_searchreplace_scope
261 (go_gtk_builder_group_value (gui, scope_group));
265 static void
266 cursor_change (GtkTreeView *tree_view, DialogState *dd)
268 int matchno;
269 int lastmatch = dd->matches->len - 1;
270 GtkTreePath *path;
272 gtk_tree_view_get_cursor (tree_view, &path, NULL);
273 if (path) {
274 matchno = gtk_tree_path_get_indices (path)[0];
275 gtk_tree_path_free (path);
276 } else {
277 matchno = -1;
280 gtk_widget_set_sensitive (dd->prev_button, matchno > 0);
281 gtk_widget_set_sensitive (dd->next_button,
282 matchno >= 0 && matchno < lastmatch);
284 if (matchno >= 0 && matchno <= lastmatch) {
285 GnmSearchFilterResult *item = g_ptr_array_index (dd->matches, matchno);
286 int col = item->ep.eval.col;
287 int row = item->ep.eval.row;
288 WorkbookControl *wbc = GNM_WBC (dd->wbcg);
289 WorkbookView *wbv = wb_control_view (wbc);
290 SheetView *sv;
292 if (!sheet_is_visible (item->ep.sheet))
293 return;
295 if (wb_control_cur_sheet (wbc) != item->ep.sheet)
296 wb_view_sheet_focus (wbv, item->ep.sheet);
297 sv = wb_view_cur_sheet_view (wbv);
298 sv_set_edit_pos (sv, &item->ep.eval);
299 sv_selection_set (sv, &item->ep.eval, col, row, col, row);
300 sv_make_cell_visible (sv, col, row, FALSE);
301 sv_update (sv);
306 static void
307 search_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
309 GtkBuilder *gui = dd->gui;
310 WBCGtk *wbcg = dd->wbcg;
311 WorkbookControl *wbc = GNM_WBC (wbcg);
312 GnmSearchReplace *sr;
313 char *err;
314 int i;
315 GnmSearchReplaceScope scope;
316 char *text;
317 gboolean is_regexp, is_number;
319 i = go_gtk_builder_group_value (gui, scope_group);
320 scope = (i == -1) ? GNM_SRS_SHEET : (GnmSearchReplaceScope)i;
322 i = go_gtk_builder_group_value (gui, search_type_group);
323 is_regexp = (i == 1);
324 is_number = (i == 2);
326 text = gnm_search_normalize (gtk_entry_get_text (dd->gentry));
328 sr = g_object_new (GNM_SEARCH_REPLACE_TYPE,
329 "sheet", wb_control_cur_sheet (wbc),
330 "scope", scope,
331 "range-text", gnm_expr_entry_get_text (dd->rangetext),
332 "search-text", text,
333 "is-regexp", is_regexp,
334 "is-number", is_number,
335 "ignore-case", is_checked (gui, "ignore_case"),
336 "match-words", is_checked (gui, "match_words"),
337 "search-strings", is_checked (gui, "search_string"),
338 "search-other-values", is_checked (gui, "search_other"),
339 "search-expressions", is_checked (gui, "search_expr"),
340 "search-expression-results", is_checked (gui, "search_expr_results"),
341 "search-comments", is_checked (gui, "search_comments"),
342 "by-row", go_gtk_builder_group_value (gui, direction_group) == 0,
343 NULL);
345 g_free (text);
347 err = gnm_search_replace_verify (sr, FALSE);
348 if (err) {
349 go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
350 GTK_MESSAGE_ERROR, "%s", err);
351 g_free (err);
352 g_object_unref (sr);
353 return;
354 } else if (!sr->search_strings &&
355 !sr->search_other_values &&
356 !sr->search_expressions &&
357 !sr->search_expression_results &&
358 !sr->search_comments) {
359 go_gtk_notice_dialog (GTK_WINDOW (dd->dialog), GTK_MESSAGE_ERROR,
360 _("You must select some cell types to search."));
361 g_object_unref (sr);
362 return;
365 if (is_checked (gui, "save-in-prefs"))
366 dialog_search_save_in_prefs (dd);
369 GnumericLazyList *ll;
370 GPtrArray *cells;
372 /* Clear current table. */
373 gtk_tree_view_set_model (dd->matches_table, NULL);
374 gnm_search_filter_matching_free (dd->matches);
376 cells = gnm_search_collect_cells (sr);
377 dd->matches = gnm_search_filter_matching (sr, cells);
378 gnm_search_collect_cells_free (cells);
380 ll = make_matches_model (dd, dd->matches->len);
381 gtk_tree_view_set_model (dd->matches_table, GTK_TREE_MODEL (ll));
382 g_object_unref (ll);
384 /* Set sensitivity of buttons. */
385 cursor_change (dd->matches_table, dd);
388 gtk_notebook_set_current_page (dd->notebook, dd->notebook_matches_page);
389 gtk_widget_grab_focus (GTK_WIDGET (dd->matches_table));
391 g_object_unref (sr);
394 static void
395 prev_next_clicked (DialogState *dd, int delta)
397 gboolean res;
398 GtkWidget *w = GTK_WIDGET (dd->matches_table);
400 gtk_widget_grab_focus (w);
401 g_signal_emit_by_name (w, "move_cursor",
402 GTK_MOVEMENT_DISPLAY_LINES, delta,
403 &res);
406 static void
407 prev_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
409 prev_next_clicked (dd, -1);
412 static void
413 next_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
415 prev_next_clicked (dd, +1);
418 static gboolean
419 cb_next (G_GNUC_UNUSED GtkWidget *widget,
420 G_GNUC_UNUSED gboolean start_editing,
421 DialogState *dd)
423 prev_next_clicked (dd, +1);
424 return TRUE;
427 static void
428 cb_focus_on_entry (GtkWidget *widget, GtkWidget *entry)
430 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
431 gtk_widget_grab_focus (GTK_WIDGET (gnm_expr_entry_get_entry
432 (GNM_EXPR_ENTRY (entry))));
435 static const struct {
436 const char *title;
437 const char *type;
438 } columns[] = {
439 { N_("Sheet"), "text" },
440 { N_("Cell"), "text" },
441 { N_("Type"), "text" },
442 { N_("Content"), "text" }
445 static GtkTreeView *
446 make_matches_table (DialogState *dd)
448 GtkTreeView *tree_view;
449 GtkTreeModel *model = GTK_TREE_MODEL (make_matches_model (dd, 0));
450 int i;
452 tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (model));
453 #ifdef NOT_YET
454 /* Gtk+ isn't ready yet -- 20031224. */
455 g_object_set (tree_view, "fixed-height-mode", TRUE, NULL);
456 #endif
458 for (i = 0; i < (int)G_N_ELEMENTS (columns); i++) {
459 GtkCellRenderer *cell = gtk_cell_renderer_text_new ();
460 GtkTreeViewColumn *column =
461 gtk_tree_view_column_new_with_attributes (_(columns[i].title), cell,
462 columns[i].type, i,
463 NULL);
464 /* Set single_paragraph_mode to ensure fixed height. */
465 g_object_set (cell, "single-paragraph-mode", TRUE, NULL);
466 if (i == COL_CONTENTS)
467 g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
468 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
469 gtk_tree_view_append_column (tree_view, column);
472 g_object_unref (model);
473 return tree_view;
476 void
477 dialog_search (WBCGtk *wbcg)
479 GtkBuilder *gui;
480 GtkDialog *dialog;
481 DialogState *dd;
482 GtkGrid *grid;
484 g_return_if_fail (wbcg != NULL);
486 #ifdef USE_GURU
487 /* Only one guru per workbook. */
488 if (wbc_gtk_get_guru (wbcg))
489 return;
490 #endif
492 gui = gnm_gtk_builder_load ("search.ui", NULL, GO_CMD_CONTEXT (wbcg));
493 if (gui == NULL)
494 return;
496 dialog = GTK_DIALOG (gtk_builder_get_object (gui, "search_dialog"));
498 dd = g_new (DialogState, 1);
499 dd->wbcg = wbcg;
500 dd->gui = gui;
501 dd->dialog = dialog;
502 dd->matches = g_ptr_array_new ();
504 dd->prev_button = go_gtk_builder_get_widget (gui, "prev_button");
505 dd->next_button = go_gtk_builder_get_widget (gui, "next_button");
507 dd->notebook = GTK_NOTEBOOK (gtk_builder_get_object (gui, "notebook"));
508 dd->notebook_matches_page =
509 gtk_notebook_page_num (dd->notebook,
510 go_gtk_builder_get_widget (gui, "matches_tab"));
512 dd->rangetext = gnm_expr_entry_new
513 (wbcg,
514 #ifdef USE_GURU
515 TRUE
516 #else
517 FALSE
518 #endif
520 gnm_expr_entry_set_flags (dd->rangetext, 0, GNM_EE_MASK);
521 grid = GTK_GRID (gtk_builder_get_object (gui, "normal-grid"));
522 gtk_widget_set_hexpand (GTK_WIDGET (dd->rangetext), TRUE);
523 gtk_grid_attach (grid, GTK_WIDGET (dd->rangetext), 1, 6, 1, 1);
525 char *selection_text =
526 selection_to_string (
527 wb_control_cur_sheet_view (GNM_WBC (wbcg)),
528 TRUE);
529 gnm_expr_entry_load_from_text (dd->rangetext, selection_text);
530 g_free (selection_text);
533 dd->gentry = GTK_ENTRY (gtk_entry_new ());
534 gtk_widget_set_hexpand (GTK_WIDGET (dd->gentry), TRUE);
535 gtk_grid_attach (grid, GTK_WIDGET (dd->gentry), 1, 0, 1, 1);
536 gtk_widget_grab_focus (GTK_WIDGET (dd->gentry));
537 gnm_editable_enters (GTK_WINDOW (dialog), GTK_WIDGET (dd->gentry));
539 dd->matches_table = make_matches_table (dd);
542 GtkWidget *scrolled_window =
543 gtk_scrolled_window_new (NULL, NULL);
544 gtk_container_add (GTK_CONTAINER (scrolled_window),
545 GTK_WIDGET (dd->matches_table));
546 gtk_box_pack_start (GTK_BOX (gtk_builder_get_object (gui, "matches_vbox")),
547 scrolled_window,
548 TRUE, TRUE, 0);
549 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
550 GTK_POLICY_NEVER,
551 GTK_POLICY_ALWAYS);
554 /* Set sensitivity of buttons. */
555 cursor_change (dd->matches_table, dd);
557 #define SETW(w,f) gtk_toggle_button_set_active \
558 (GTK_TOGGLE_BUTTON (gtk_builder_get_object (gui, w)), f())
559 SETW("search_expr", gnm_conf_get_searchreplace_change_cell_expressions);
560 SETW("search_other", gnm_conf_get_searchreplace_change_cell_other);
561 SETW("search_string", gnm_conf_get_searchreplace_change_cell_strings);
562 SETW("search_comments", gnm_conf_get_searchreplace_change_comments);
563 SETW("search_expr_results", gnm_conf_get_searchreplace_search_results);
564 SETW("ignore_case", gnm_conf_get_searchreplace_ignore_case);
565 SETW("match_words", gnm_conf_get_searchreplace_whole_words_only);
566 #undef SETW
568 gtk_toggle_button_set_active
569 (GTK_TOGGLE_BUTTON
570 (gtk_builder_get_object
571 (gui,
572 search_type_group[gnm_conf_get_searchreplace_regex ()])), TRUE);
573 gtk_toggle_button_set_active
574 (GTK_TOGGLE_BUTTON
575 (gtk_builder_get_object
576 (gui,
577 direction_group
578 [gnm_conf_get_searchreplace_columnmajor () ? 1 : 0])), TRUE);
579 gtk_toggle_button_set_active
580 (GTK_TOGGLE_BUTTON
581 (gtk_builder_get_object
582 (gui,
583 scope_group[gnm_conf_get_searchreplace_scope ()])), TRUE);
585 g_signal_connect (G_OBJECT (dd->matches_table), "cursor_changed",
586 G_CALLBACK (cursor_change), dd);
587 g_signal_connect (G_OBJECT (dd->matches_table), "select_cursor_row",
588 G_CALLBACK (cb_next), dd);
589 go_gtk_builder_signal_connect (gui, "search_button", "clicked",
590 G_CALLBACK (search_clicked), dd);
591 g_signal_connect (G_OBJECT (dd->prev_button), "clicked",
592 G_CALLBACK (prev_clicked), dd);
593 g_signal_connect (G_OBJECT (dd->next_button), "clicked",
594 G_CALLBACK (next_clicked), dd);
595 go_gtk_builder_signal_connect_swapped (gui, "close_button", "clicked",
596 G_CALLBACK (gtk_widget_destroy), dd->dialog);
597 g_signal_connect (G_OBJECT (gnm_expr_entry_get_entry (dd->rangetext)), "focus-in-event",
598 G_CALLBACK (range_focused), dd);
599 go_gtk_builder_signal_connect (gui, "scope_range", "toggled",
600 G_CALLBACK (cb_focus_on_entry), dd->rangetext);
602 #ifdef USE_GURU
603 wbc_gtk_attach_guru_with_unfocused_rs (wbcg, GTK_WIDGET (dialog), dd->rangetext);
604 #endif
605 g_object_set_data_full (G_OBJECT (dialog),
606 "state", dd, (GDestroyNotify) free_state);
607 gnm_dialog_setup_destroy_handlers (dialog, wbcg,
608 GNM_DIALOG_DESTROY_SHEET_REMOVED);
609 gnm_init_help_button (
610 go_gtk_builder_get_widget (gui, "help_button"),
611 GNUMERIC_HELP_LINK_SEARCH);
612 gnm_restore_window_geometry (GTK_WINDOW (dialog), SEARCH_KEY);
614 go_gtk_nonmodal_dialog (wbcg_toplevel (wbcg), GTK_WINDOW (dialog));
615 gtk_widget_show_all (GTK_WIDGET (dialog));
618 /* ------------------------------------------------------------------------- */