1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
4 * Dialog for entering a search query.
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>
30 #include <gnumeric-conf.h>
33 #include <sheet-view.h>
35 #include <workbook-view.h>
36 #include <selection.h>
39 #include <parse-util.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>
49 #define SEARCH_KEY "search-dialog"
65 GnmExprEntry
*rangetext
;
67 GtkWidget
*prev_button
, *next_button
;
68 GtkNotebook
*notebook
;
69 int notebook_matches_page
;
71 GtkTreeView
*matches_table
;
75 static const char * const search_type_group
[] = {
82 static const char * const scope_group
[] = {
89 static const char * const direction_group
[] = {
95 /* ------------------------------------------------------------------------- */
98 /* ------------------------------------------------------------------------- */
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
);
109 if (item
->locus
== GNM_SRL_COMMENT
) {
111 comment
= sheet_get_comment (item
->ep
.sheet
, &item
->ep
.eval
);
113 cell
= sheet_cell_get (item
->ep
.sheet
,
119 g_value_init (value
, ll
->column_headers
[column
]);
122 g_print ("col=%d,row=%d\n", column
, row
);
127 g_value_set_string (value
, item
->ep
.sheet
->name_unquoted
);
130 g_value_set_string (value
, cellpos_as_string (&item
->ep
.eval
));
133 switch (item
->locus
) {
134 case GNM_SRL_COMMENT
:
135 g_value_set_static_string (value
, _("Comment"));
138 g_value_set_static_string (value
, _("Result"));
140 case GNM_SRL_CONTENTS
: {
141 GnmValue
*v
= cell
? cell
->value
: NULL
;
144 gboolean is_expr
= cell
&& gnm_cell_has_expr (cell
);
145 gboolean is_value
= !is_expr
&& !gnm_cell_is_empty (cell
) && v
;
150 type
= _("Expression");
151 else if (is_value
&& VALUE_IS_STRING (v
))
153 else if (is_value
&& VALUE_IS_FLOAT (v
))
156 type
= _("Other value");
158 g_value_set_static_string (value
, type
);
162 #ifndef DEBUG_SWITCH_ENUM
164 g_assert_not_reached ();
169 switch (item
->locus
) {
170 case GNM_SRL_COMMENT
:
172 g_value_set_string (value
, cell_comment_text_get (comment
));
174 g_value_set_static_string (value
, _("Deleted"));
177 if (cell
&& cell
->value
)
178 g_value_take_string (value
, value_get_as_string (cell
->value
));
180 g_value_set_static_string (value
, _("Deleted"));
182 case GNM_SRL_CONTENTS
:
184 g_value_take_string (value
, gnm_cell_get_entered_text (cell
));
186 g_value_set_static_string (value
, _("Deleted"));
188 #ifndef DEBUG_SWITCH_ENUM
190 g_assert_not_reached ();
194 #ifndef DEBUG_SWITCH_ENUM
196 g_assert_not_reached ();
201 /* ------------------------------------------------------------------------- */
203 static GnumericLazyList
*
204 make_matches_model (DialogState
*dd
, int rows
)
206 return gnumeric_lazy_list_new (search_get_value
,
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
));
226 range_focused (G_GNUC_UNUSED GtkWidget
*widget
,
227 G_GNUC_UNUSED GdkEventFocus
*event
,
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
);
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
));
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
);
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
));
266 cursor_change (GtkTreeView
*tree_view
, DialogState
*dd
)
269 int lastmatch
= dd
->matches
->len
- 1;
272 gtk_tree_view_get_cursor (tree_view
, &path
, NULL
);
274 matchno
= gtk_tree_path_get_indices (path
)[0];
275 gtk_tree_path_free (path
);
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
);
292 if (!sheet_is_visible (item
->ep
.sheet
))
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
);
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
;
315 GnmSearchReplaceScope scope
;
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
),
331 "range-text", gnm_expr_entry_get_text (dd
->rangetext
),
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,
347 err
= gnm_search_replace_verify (sr
, FALSE
);
349 go_gtk_notice_dialog (GTK_WINDOW (dd
->dialog
),
350 GTK_MESSAGE_ERROR
, "%s", err
);
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."));
365 if (is_checked (gui
, "save-in-prefs"))
366 dialog_search_save_in_prefs (dd
);
369 GnumericLazyList
*ll
;
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
));
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
));
395 prev_next_clicked (DialogState
*dd
, int delta
)
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
,
407 prev_clicked (G_GNUC_UNUSED GtkWidget
*widget
, DialogState
*dd
)
409 prev_next_clicked (dd
, -1);
413 next_clicked (G_GNUC_UNUSED GtkWidget
*widget
, DialogState
*dd
)
415 prev_next_clicked (dd
, +1);
419 cb_next (G_GNUC_UNUSED GtkWidget
*widget
,
420 G_GNUC_UNUSED gboolean start_editing
,
423 prev_next_clicked (dd
, +1);
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 {
439 { N_("Sheet"), "text" },
440 { N_("Cell"), "text" },
441 { N_("Type"), "text" },
442 { N_("Content"), "text" }
446 make_matches_table (DialogState
*dd
)
448 GtkTreeView
*tree_view
;
449 GtkTreeModel
*model
= GTK_TREE_MODEL (make_matches_model (dd
, 0));
452 tree_view
= GTK_TREE_VIEW (gtk_tree_view_new_with_model (model
));
454 /* Gtk+ isn't ready yet -- 20031224. */
455 g_object_set (tree_view
, "fixed-height-mode", TRUE
, NULL
);
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
,
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
);
477 dialog_search (WBCGtk
*wbcg
)
484 g_return_if_fail (wbcg
!= NULL
);
487 /* Only one guru per workbook. */
488 if (wbc_gtk_get_guru (wbcg
))
492 gui
= gnm_gtk_builder_load ("search.ui", NULL
, GO_CMD_CONTEXT (wbcg
));
496 dialog
= GTK_DIALOG (gtk_builder_get_object (gui
, "search_dialog"));
498 dd
= g_new (DialogState
, 1);
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
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
)),
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")),
549 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window
),
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
);
568 gtk_toggle_button_set_active
570 (gtk_builder_get_object
572 search_type_group
[gnm_conf_get_searchreplace_regex ()])), TRUE
);
573 gtk_toggle_button_set_active
575 (gtk_builder_get_object
578 [gnm_conf_get_searchreplace_columnmajor () ? 1 : 0])), TRUE
);
579 gtk_toggle_button_set_active
581 (gtk_builder_get_object
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
);
603 wbc_gtk_attach_guru_with_unfocused_rs (wbcg
, GTK_WIDGET (dialog
), dd
->rangetext
);
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 /* ------------------------------------------------------------------------- */