1 /*
2  * dialog-search.c:
3  *   Dialog for entering a search query.
4  *
5  * Author:
6  *   Morten Welinder (terra@gnome.org)
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include <dialogs/dialogs.h>
26 #include <dialogs/help.h>
27 
28 #include <gui-util.h>
29 #include <gnumeric-conf.h>
30 #include <search.h>
31 #include <sheet.h>
32 #include <sheet-view.h>
33 #include <workbook.h>
34 #include <workbook-view.h>
35 #include <selection.h>
36 #include <cell.h>
37 #include <value.h>
38 #include <parse-util.h>
39 #include <wbc-gtk.h>
40 #include <sheet-object-cell-comment.h>
41 #include <selection.h>
42 
43 #include <widgets/gnm-expr-entry.h>
44 #include <string.h>
45 
46 #define SEARCH_KEY "search-dialog"
47 
48 #undef USE_GURU
49 
50 enum {
51 	COL_SHEET = 0,
52 	COL_CELL,
53 	COL_TYPE,
54 	COL_CONTENTS
55 };
56 
57 enum {
58 	ITEM_MATCH
59 };
60 
61 typedef struct {
62 	WBCGtk *wbcg;
63 
64 	GtkBuilder *gui;
65 	GtkDialog *dialog;
66 	GnmExprEntry *rangetext;
67 	GtkEntry *gentry;
68 	GtkWidget *prev_button, *next_button;
69 	GtkNotebook *notebook;
70 	int notebook_matches_page;
71 
72 	GtkTreeView *matches_table;
73 	GPtrArray *matches;
74 } DialogState;
75 
76 static const char * const search_type_group[] = {
77 	"search_type_text",
78 	"search_type_regexp",
79 	"search_type_number",
80 	NULL
81 };
82 
83 static const char * const scope_group[] = {
84 	"scope_workbook",
85 	"scope_sheet",
86 	"scope_range",
87 	NULL
88 };
89 
90 static const char * const direction_group[] = {
91 	"row_major",
92 	"column_major",
93 	NULL
94 };
95 
96 /* ------------------------------------------------------------------------- */
97 
98 static GtkTreeModel *
make_matches_model(DialogState * dd)99 make_matches_model (DialogState *dd)
100 {
101 	GtkListStore *list_store = gtk_list_store_new (1, G_TYPE_POINTER);
102 	unsigned ui;
103 	GPtrArray *matches = dd->matches;
104 
105 	for (ui = 0; ui < matches->len; ui++) {
106 		GtkTreeIter iter;
107 
108 		gtk_list_store_append (list_store, &iter);
109 		gtk_list_store_set (list_store, &iter,
110 				    ITEM_MATCH, g_ptr_array_index (matches, ui),
111 				    -1);
112 	}
113 
114 	return GTK_TREE_MODEL (list_store);
115 }
116 
117 static void
free_state(DialogState * dd)118 free_state (DialogState *dd)
119 {
120 	gnm_search_filter_matching_free (dd->matches);
121 	g_object_unref (dd->gui);
122 	memset (dd, 0, sizeof (*dd));
123 	g_free (dd);
124 }
125 
126 static gboolean
range_focused(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED GdkEventFocus * event,DialogState * dd)127 range_focused (G_GNUC_UNUSED GtkWidget *widget,
128 	       G_GNUC_UNUSED GdkEventFocus *event,
129 	       DialogState *dd)
130 {
131 	GtkWidget *scope_range = go_gtk_builder_get_widget (dd->gui, "scope_range");
132 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scope_range), TRUE);
133 	return FALSE;
134 }
135 
136 static gboolean
is_checked(GtkBuilder * gui,const char * name)137 is_checked (GtkBuilder *gui, const char *name)
138 {
139 	GtkWidget *w = go_gtk_builder_get_widget (gui, name);
140 	return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w));
141 }
142 
143 static void
dialog_search_save_in_prefs(DialogState * dd)144 dialog_search_save_in_prefs (DialogState *dd)
145 {
146 	GtkBuilder *gui = dd->gui;
147 
148 #define SETW(w,f) f (is_checked (gui, w));
149 	SETW("search_expr", gnm_conf_set_searchreplace_change_cell_expressions);
150 	SETW("search_other", gnm_conf_set_searchreplace_change_cell_other);
151 	SETW("search_string", gnm_conf_set_searchreplace_change_cell_strings);
152 	SETW("search_comments", gnm_conf_set_searchreplace_change_comments);
153 	SETW("search_expr_results", gnm_conf_set_searchreplace_search_results);
154 	SETW("ignore_case", gnm_conf_set_searchreplace_ignore_case);
155 	SETW("match_words", gnm_conf_set_searchreplace_whole_words_only);
156 	SETW("column_major", gnm_conf_set_searchreplace_columnmajor);
157 #undef SETW
158 
159 	gnm_conf_set_searchreplace_regex
160 		(go_gtk_builder_group_value (gui, search_type_group));
161 	gnm_conf_set_searchreplace_scope
162 		(go_gtk_builder_group_value (gui, scope_group));
163 }
164 
165 
166 static void
cursor_change(GtkTreeView * tree_view,DialogState * dd)167 cursor_change (GtkTreeView *tree_view, DialogState *dd)
168 {
169 	int matchno;
170 	int lastmatch = dd->matches->len - 1;
171 	GtkTreePath *path;
172 
173 	gtk_tree_view_get_cursor (tree_view, &path, NULL);
174 	if (path) {
175 		matchno = gtk_tree_path_get_indices (path)[0];
176 		gtk_tree_path_free (path);
177 	} else {
178 		matchno = -1;
179 	}
180 
181 	gtk_widget_set_sensitive (dd->prev_button, matchno > 0);
182 	gtk_widget_set_sensitive (dd->next_button,
183 				  matchno >= 0 && matchno < lastmatch);
184 
185 	if (matchno >= 0 && matchno <= lastmatch) {
186 		GnmSearchFilterResult *item = g_ptr_array_index (dd->matches, matchno);
187 		int col = item->ep.eval.col;
188 		int row = item->ep.eval.row;
189 		WorkbookControl *wbc = GNM_WBC (dd->wbcg);
190 		WorkbookView *wbv = wb_control_view (wbc);
191 		SheetView *sv;
192 
193 		if (!sheet_is_visible (item->ep.sheet))
194 			return;
195 
196 		if (wb_control_cur_sheet (wbc) != item->ep.sheet)
197 			wb_view_sheet_focus (wbv, item->ep.sheet);
198 		sv = wb_view_cur_sheet_view (wbv);
199 		gnm_sheet_view_set_edit_pos (sv, &item->ep.eval);
200 		sv_selection_set (sv, &item->ep.eval, col, row, col, row);
201 		gnm_sheet_view_make_cell_visible (sv, col, row, FALSE);
202 		gnm_sheet_view_update (sv);
203 	}
204 }
205 
206 
207 static void
search_clicked(G_GNUC_UNUSED GtkWidget * widget,DialogState * dd)208 search_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
209 {
210 	GtkBuilder *gui = dd->gui;
211 	WBCGtk *wbcg = dd->wbcg;
212 	WorkbookControl *wbc = GNM_WBC (wbcg);
213 	GnmSearchReplace *sr;
214 	char *err;
215 	int i;
216 	GnmSearchReplaceScope scope;
217 	char *text;
218 	gboolean is_regexp, is_number;
219 
220 	i = go_gtk_builder_group_value (gui, scope_group);
221 	scope = (i == -1) ? GNM_SRS_SHEET : (GnmSearchReplaceScope)i;
222 
223 	i = go_gtk_builder_group_value (gui, search_type_group);
224 	is_regexp = (i == 1);
225 	is_number = (i == 2);
226 
227 	text = gnm_search_normalize (gtk_entry_get_text (dd->gentry));
228 
229 	sr = g_object_new (GNM_SEARCH_REPLACE_TYPE,
230 			   "sheet", wb_control_cur_sheet (wbc),
231 			   "scope", scope,
232 			   "range-text", gnm_expr_entry_get_text (dd->rangetext),
233 			   "search-text", text,
234 			   "is-regexp", is_regexp,
235 			   "is-number", is_number,
236 			   "ignore-case", is_checked (gui, "ignore_case"),
237 			   "match-words", is_checked (gui, "match_words"),
238 			   "search-strings", is_checked (gui, "search_string"),
239 			   "search-other-values", is_checked (gui, "search_other"),
240 			   "search-expressions", is_checked (gui, "search_expr"),
241 			   "search-expression-results", is_checked (gui, "search_expr_results"),
242 			   "search-comments", is_checked (gui, "search_comments"),
243 			   "by-row", go_gtk_builder_group_value (gui, direction_group) == 0,
244 			   NULL);
245 
246 	g_free (text);
247 
248 	err = gnm_search_replace_verify (sr, FALSE);
249 	if (err) {
250 		go_gtk_notice_dialog (GTK_WINDOW (dd->dialog),
251 				      GTK_MESSAGE_ERROR, "%s", err);
252 		g_free (err);
253 		g_object_unref (sr);
254 		return;
255 	} else if (!sr->search_strings &&
256 		   !sr->search_other_values &&
257 		   !sr->search_expressions &&
258 		   !sr->search_expression_results &&
259 		   !sr->search_comments) {
260 		go_gtk_notice_dialog (GTK_WINDOW (dd->dialog), GTK_MESSAGE_ERROR,
261 				      _("You must select some cell types to search."));
262 		g_object_unref (sr);
263 		return;
264 	}
265 
266 	if  (is_checked (gui, "save-in-prefs"))
267 		dialog_search_save_in_prefs (dd);
268 
269 	{
270 		GtkTreeModel *model;
271 		GPtrArray *cells;
272 
273 		/* Clear current table.  */
274 		gtk_tree_view_set_model (dd->matches_table, NULL);
275 		gnm_search_filter_matching_free (dd->matches);
276 
277 		cells = gnm_search_collect_cells (sr);
278 		dd->matches = gnm_search_filter_matching (sr, cells);
279 		gnm_search_collect_cells_free (cells);
280 
281 		model = make_matches_model (dd);
282 		gtk_tree_view_set_model (dd->matches_table, model);
283 		g_object_unref (model);
284 
285 		/* Set sensitivity of buttons.  */
286 		cursor_change (dd->matches_table, dd);
287 	}
288 
289 	gtk_notebook_set_current_page (dd->notebook, dd->notebook_matches_page);
290 	gtk_widget_grab_focus (GTK_WIDGET (dd->matches_table));
291 
292 	g_object_unref (sr);
293 }
294 
295 static void
prev_next_clicked(DialogState * dd,int delta)296 prev_next_clicked (DialogState *dd, int delta)
297 {
298 	gboolean res;
299 	GtkWidget *w = GTK_WIDGET (dd->matches_table);
300 
301 	gtk_widget_grab_focus (w);
302 	g_signal_emit_by_name (w, "move_cursor",
303 			       GTK_MOVEMENT_DISPLAY_LINES, delta,
304 			       &res);
305 }
306 
307 static void
prev_clicked(G_GNUC_UNUSED GtkWidget * widget,DialogState * dd)308 prev_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
309 {
310 	prev_next_clicked (dd, -1);
311 }
312 
313 static void
next_clicked(G_GNUC_UNUSED GtkWidget * widget,DialogState * dd)314 next_clicked (G_GNUC_UNUSED GtkWidget *widget, DialogState *dd)
315 {
316 	prev_next_clicked (dd, +1);
317 }
318 
319 static gboolean
cb_next(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED gboolean start_editing,DialogState * dd)320 cb_next (G_GNUC_UNUSED GtkWidget *widget,
321 	 G_GNUC_UNUSED gboolean start_editing,
322 	 DialogState *dd)
323 {
324 	prev_next_clicked (dd, +1);
325 	return TRUE;
326 }
327 
328 static void
cb_focus_on_entry(GtkWidget * widget,GtkWidget * entry)329 cb_focus_on_entry (GtkWidget *widget, GtkWidget *entry)
330 {
331         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
332 		gtk_widget_grab_focus (GTK_WIDGET (gnm_expr_entry_get_entry
333 						   (GNM_EXPR_ENTRY (entry))));
334 }
335 
336 static void
match_renderer_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cr,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)337 match_renderer_func (GtkTreeViewColumn *tree_column,
338 		     GtkCellRenderer   *cr,
339 		     GtkTreeModel      *model,
340 		     GtkTreeIter       *iter,
341 		     gpointer           user_data)
342 {
343 	int column = GPOINTER_TO_INT (user_data);
344 	GnmSearchFilterResult *m;
345 	GnmCell *cell;
346 	GnmComment *comment;
347 	const char *text = NULL;
348 	char *free_text = NULL;
349 
350 	gtk_tree_model_get (model, iter, ITEM_MATCH, &m, -1);
351 
352 	if (m->locus == GNM_SRL_COMMENT) {
353 		cell = NULL;
354 		comment = sheet_get_comment (m->ep.sheet, &m->ep.eval);
355 	} else {
356 		cell = sheet_cell_get (m->ep.sheet,
357 				       m->ep.eval.col,
358 				       m->ep.eval.row);
359 		comment = NULL;
360 	}
361 
362 	switch (column) {
363 	case COL_SHEET:
364 		text = m->ep.sheet->name_unquoted;
365 		break;
366 	case COL_CELL:
367 		text = cellpos_as_string (&m->ep.eval);
368 		break;
369 	case COL_TYPE:
370 		switch (m->locus) {
371 		case GNM_SRL_COMMENT:
372 			text = _("Comment");
373 			break;
374 		case GNM_SRL_VALUE:
375 			text = _("Result");
376 			break;
377 		case GNM_SRL_CONTENTS: {
378 			GnmValue *v = cell ? cell->value : NULL;
379 			gboolean is_expr = cell && gnm_cell_has_expr (cell);
380 			gboolean is_value = !is_expr && !gnm_cell_is_empty (cell) && v;
381 
382 			if (!cell)
383 				text = _("Deleted");
384 			else if (is_expr)
385 				text = _("Expression");
386 			else if (is_value && VALUE_IS_STRING (v))
387 				text = _("String");
388 			else if (is_value && VALUE_IS_FLOAT (v))
389 				text = _("Number");
390 			else
391 				text = _("Other value");
392 			break;
393 		}
394 		default:
395 			g_assert_not_reached ();
396 		}
397 		break;
398 
399 	case COL_CONTENTS:
400 		switch (m->locus) {
401 		case GNM_SRL_COMMENT:
402 			text = comment
403 				? cell_comment_text_get (comment)
404 				: _("Deleted");
405 			break;
406 		case GNM_SRL_VALUE:
407 			text = cell && cell->value
408 				? value_peek_string (cell->value)
409 				: _("Deleted");
410 			break;
411 		case GNM_SRL_CONTENTS:
412 			text = cell
413 				? (free_text = gnm_cell_get_entered_text (cell))
414 				: _("Deleted");
415 			break;
416 		default:
417 			g_assert_not_reached ();
418 		}
419 		break;
420 
421 	default:
422 		g_assert_not_reached ();
423 	}
424 
425 	g_object_set (cr, "text", text, NULL);
426 	g_free (free_text);
427 }
428 
429 
430 static GtkTreeView *
make_matches_table(DialogState * dd)431 make_matches_table (DialogState *dd)
432 {
433 	GtkTreeView *tree_view;
434 	GtkTreeModel *model = GTK_TREE_MODEL (make_matches_model (dd));
435 	int i;
436 	static const char *const columns[4] = {
437 		N_("Sheet"), N_("Cell"), N_("Type"), N_("Content")
438 	};
439 
440 	tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (model));
441 
442 	for (i = 0; i < (int)G_N_ELEMENTS (columns); i++) {
443 		GtkTreeViewColumn *tvc = gtk_tree_view_column_new ();
444 		GtkCellRenderer *cr = gtk_cell_renderer_text_new ();
445 
446 		g_object_set (cr, "single-paragraph-mode", TRUE, NULL);
447 		if (i == COL_CONTENTS)
448 			g_object_set (cr, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
449 
450 		gtk_tree_view_column_set_title (tvc, _(columns[i]));
451 		gtk_tree_view_column_set_cell_data_func
452 			(tvc, cr,
453 			 match_renderer_func,
454 			 GINT_TO_POINTER (i), NULL);
455 		gtk_tree_view_column_pack_start (tvc, cr, TRUE);
456 
457 		gtk_tree_view_column_set_sizing (tvc, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
458 		gtk_tree_view_append_column (tree_view, tvc);
459 	}
460 
461 	g_object_unref (model);
462 	return tree_view;
463 }
464 
465 void
dialog_search(WBCGtk * wbcg)466 dialog_search (WBCGtk *wbcg)
467 {
468 	GtkBuilder *gui;
469 	GtkDialog *dialog;
470 	DialogState *dd;
471 	GtkGrid *grid;
472 
473 	g_return_if_fail (wbcg != NULL);
474 
475 #ifdef USE_GURU
476 	/* Only one guru per workbook. */
477 	if (wbc_gtk_get_guru (wbcg))
478 		return;
479 #endif
480 
481 	gui = gnm_gtk_builder_load ("res:ui/search.ui", NULL, GO_CMD_CONTEXT (wbcg));
482         if (gui == NULL)
483                 return;
484 
485 	dialog = GTK_DIALOG (gtk_builder_get_object (gui, "search_dialog"));
486 
487 	dd = g_new (DialogState, 1);
488 	dd->wbcg = wbcg;
489 	dd->gui = gui;
490 	dd->dialog = dialog;
491 	dd->matches = g_ptr_array_new ();
492 
493 	dd->prev_button = go_gtk_builder_get_widget (gui, "prev_button");
494 	dd->next_button = go_gtk_builder_get_widget (gui, "next_button");
495 
496 	dd->notebook = GTK_NOTEBOOK (gtk_builder_get_object (gui, "notebook"));
497 	dd->notebook_matches_page =
498 		gtk_notebook_page_num (dd->notebook,
499 				       go_gtk_builder_get_widget (gui, "matches_tab"));
500 
501 	dd->rangetext = gnm_expr_entry_new
502 		(wbcg,
503 #ifdef USE_GURU
504 		 TRUE
505 #else
506 		 FALSE
507 #endif
508 			);
509 	gnm_expr_entry_set_flags (dd->rangetext, 0, GNM_EE_MASK);
510 	grid = GTK_GRID (gtk_builder_get_object (gui, "normal-grid"));
511 	gtk_widget_set_hexpand (GTK_WIDGET (dd->rangetext), TRUE);
512 	gtk_grid_attach (grid, GTK_WIDGET (dd->rangetext), 1, 6, 1, 1);
513 	{
514 		char *selection_text =
515 			selection_to_string (
516 				wb_control_cur_sheet_view (GNM_WBC (wbcg)),
517 				TRUE);
518 		gnm_expr_entry_load_from_text  (dd->rangetext, selection_text);
519 		g_free (selection_text);
520 	}
521 
522 	dd->gentry = GTK_ENTRY (gtk_entry_new ());
523 	gtk_widget_set_hexpand (GTK_WIDGET (dd->gentry), TRUE);
524 	gtk_grid_attach (grid, GTK_WIDGET (dd->gentry), 1, 0, 1, 1);
525 	gtk_widget_grab_focus (GTK_WIDGET (dd->gentry));
526 	gnm_editable_enters (GTK_WINDOW (dialog), GTK_WIDGET (dd->gentry));
527 
528 	dd->matches_table = make_matches_table (dd);
529 
530 	{
531 		GtkWidget *scrolled_window =
532 			gtk_scrolled_window_new (NULL, NULL);
533 		gtk_container_add (GTK_CONTAINER (scrolled_window),
534 				   GTK_WIDGET (dd->matches_table));
535 		gtk_box_pack_start (GTK_BOX (gtk_builder_get_object (gui, "matches_vbox")),
536 				    scrolled_window,
537 				    TRUE, TRUE, 0);
538 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
539 						GTK_POLICY_NEVER,
540 						GTK_POLICY_ALWAYS);
541 	}
542 
543 	/* Set sensitivity of buttons.  */
544 	cursor_change (dd->matches_table, dd);
545 
546 #define SETW(w,f) gtk_toggle_button_set_active				\
547 	     (GTK_TOGGLE_BUTTON (gtk_builder_get_object (gui, w)),  f())
548 	SETW("search_expr", gnm_conf_get_searchreplace_change_cell_expressions);
549 	SETW("search_other", gnm_conf_get_searchreplace_change_cell_other);
550 	SETW("search_string", gnm_conf_get_searchreplace_change_cell_strings);
551 	SETW("search_comments", gnm_conf_get_searchreplace_change_comments);
552 	SETW("search_expr_results", gnm_conf_get_searchreplace_search_results);
553 	SETW("ignore_case", gnm_conf_get_searchreplace_ignore_case);
554 	SETW("match_words", gnm_conf_get_searchreplace_whole_words_only);
555 #undef SETW
556 
557 	gtk_toggle_button_set_active
558 	  (GTK_TOGGLE_BUTTON
559 	   (gtk_builder_get_object
560 	    (gui,
561 	     search_type_group[gnm_conf_get_searchreplace_regex ()])), TRUE);
562 	gtk_toggle_button_set_active
563 	  (GTK_TOGGLE_BUTTON
564 	   (gtk_builder_get_object
565 	    (gui,
566 	     direction_group
567 	     [gnm_conf_get_searchreplace_columnmajor () ? 1 : 0])), TRUE);
568 	gtk_toggle_button_set_active
569 	  (GTK_TOGGLE_BUTTON
570 	   (gtk_builder_get_object
571 	    (gui,
572 	     scope_group[gnm_conf_get_searchreplace_scope ()])), TRUE);
573 
574 	g_signal_connect (G_OBJECT (dd->matches_table), "cursor_changed",
575 		G_CALLBACK (cursor_change), dd);
576 	g_signal_connect (G_OBJECT (dd->matches_table), "select_cursor_row",
577 		G_CALLBACK (cb_next), dd);
578 	go_gtk_builder_signal_connect (gui, "search_button", "clicked",
579 		G_CALLBACK (search_clicked), dd);
580 	g_signal_connect (G_OBJECT (dd->prev_button), "clicked",
581 		G_CALLBACK (prev_clicked), dd);
582 	g_signal_connect (G_OBJECT (dd->next_button), "clicked",
583 		G_CALLBACK (next_clicked), dd);
584 	go_gtk_builder_signal_connect_swapped (gui, "close_button", "clicked",
585 		G_CALLBACK (gtk_widget_destroy), dd->dialog);
586 	g_signal_connect (G_OBJECT (gnm_expr_entry_get_entry (dd->rangetext)), "focus-in-event",
587 		G_CALLBACK (range_focused), dd);
588 	go_gtk_builder_signal_connect (gui, "scope_range", "toggled",
589 		G_CALLBACK (cb_focus_on_entry), dd->rangetext);
590 
591 #ifdef USE_GURU
592 	wbc_gtk_attach_guru_with_unfocused_rs (wbcg, GTK_WIDGET (dialog), dd->rangetext);
593 #endif
594 	g_object_set_data_full (G_OBJECT (dialog),
595 		"state", dd, (GDestroyNotify) free_state);
596 	gnm_dialog_setup_destroy_handlers (dialog, wbcg,
597 		GNM_DIALOG_DESTROY_SHEET_REMOVED);
598 	gnm_init_help_button (
599 		go_gtk_builder_get_widget (gui, "help_button"),
600 		GNUMERIC_HELP_LINK_SEARCH);
601 	gnm_restore_window_geometry (GTK_WINDOW (dialog), SEARCH_KEY);
602 
603 	go_gtk_nonmodal_dialog (wbcg_toplevel (wbcg), GTK_WINDOW (dialog));
604 	gtk_widget_show_all (GTK_WIDGET (dialog));
605 }
606 
607 /* ------------------------------------------------------------------------- */
608