bgo #782633 - Scintilla editor occupies 100% of a core after switching tabs.
[anjuta.git] / plugins / document-manager / search-box.c
blob0349b27b92f4d1bfce15e17dd00008649dc51c36
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * anjuta
4 * Copyright (C) Johannes Schmid 2007 <jhs@gnome.org>
5 *
6 * anjuta is free software.
7 *
8 * You may redistribute it and/or modify it under the terms of the
9 * GNU General Public License, as published by the Free Software
10 * Foundation; either version 2 of the License, or (at your option)
11 * any later version.
13 * anjuta 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.
16 * See the GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with anjuta. If not, write to:
20 * The Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301, USA.
25 #include <glib/gi18n.h>
26 #include "search-box.h"
28 #include <stdlib.h>
29 #include <gtk/gtk.h>
31 #include <libanjuta/anjuta-shell.h>
32 #include <libanjuta/anjuta-status.h>
33 #include <libanjuta/anjuta-debug.h>
35 #include <libanjuta/interfaces/ianjuta-editor.h>
36 #include <libanjuta/interfaces/ianjuta-editor-search.h>
37 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
38 #include <libanjuta/interfaces/ianjuta-indicable.h>
40 #define ANJUTA_STOCK_GOTO_LINE "anjuta-goto-line"
42 /* width of the entries in chars, that include the space for the icon */
43 #define LINE_ENTRY_WIDTH 7
44 #define SEARCH_ENTRY_WIDTH 45
46 /* Time spend to do search in the idle callback */
47 #define CONTINUOUS_SEARCH_TIMEOUT 0.1
49 struct _SearchBoxPrivate
51 GtkWidget* grid;
53 GtkWidget* search_entry;
54 GtkWidget* replace_entry;
56 GtkWidget* close_button;
57 GtkWidget* next_button;
58 GtkWidget* previous_button;
60 GtkWidget* replace_button;
61 GtkWidget* replace_all_button;
63 GtkWidget* goto_entry;
64 GtkWidget* goto_button;
66 IAnjutaEditor* current_editor;
67 AnjutaStatus* status;
68 AnjutaShell* shell;
70 /* Search options popup menu */
71 GtkWidget* popup_menu;
72 GtkAction* case_action;
73 GtkAction* highlight_action;
74 GtkAction* regex_action;
76 gboolean case_sensitive;
77 gboolean highlight_all;
78 gboolean regex_mode;
80 IAnjutaEditorCell *start_highlight;
81 IAnjutaEditorCell *end_highlight;
82 guint idle_id;
84 GtkCssProvider *provider;
87 #ifdef GET_PRIVATE
88 # undef GET_PRIVATE
89 #endif
90 #define GET_PRIVATE(o) \
91 (G_TYPE_INSTANCE_GET_PRIVATE((o), SEARCH_TYPE_BOX, SearchBoxPrivate))
93 G_DEFINE_TYPE (SearchBox, search_box, GTK_TYPE_HBOX);
95 static void
96 on_search_box_hide (GtkWidget* button, SearchBox* search_box)
98 search_box_hide (search_box);
101 static void
102 on_document_changed (AnjutaDocman* docman, IAnjutaDocument* doc,
103 SearchBox* search_box)
105 if (!doc || !IANJUTA_IS_EDITOR (doc))
107 gtk_widget_hide (GTK_WIDGET (search_box));
108 search_box->priv->current_editor = NULL;
110 else
112 search_box->priv->current_editor = IANJUTA_EDITOR (doc);
113 if (search_box->priv->highlight_all) search_box_highlight_all (search_box);
117 static void
118 on_goto_activated (GtkWidget* widget, SearchBox* search_box)
120 const gchar* str_line = gtk_entry_get_text (GTK_ENTRY (search_box->priv->goto_entry));
122 gint line = atoi (str_line);
123 if (line > 0)
125 ianjuta_editor_goto_line (search_box->priv->current_editor, line, NULL);
129 static void
130 search_box_set_entry_color (SearchBox* search_box, gboolean found)
132 GtkStyleContext *context;
134 context = gtk_widget_get_style_context (GTK_WIDGET (search_box->priv->search_entry));
135 if (!found)
137 gtk_style_context_add_class (context, "not-found");
139 else
141 gtk_style_context_remove_class (context, "not-found");
145 static gboolean
146 on_goto_key_pressed (GtkWidget* entry, GdkEventKey* event, SearchBox* search_box)
148 switch (event->keyval)
150 case GDK_KEY_0:
151 case GDK_KEY_1:
152 case GDK_KEY_2:
153 case GDK_KEY_3:
154 case GDK_KEY_4:
155 case GDK_KEY_5:
156 case GDK_KEY_6:
157 case GDK_KEY_7:
158 case GDK_KEY_8:
159 case GDK_KEY_9:
160 case GDK_KEY_KP_0:
161 case GDK_KEY_KP_1:
162 case GDK_KEY_KP_2:
163 case GDK_KEY_KP_3:
164 case GDK_KEY_KP_4:
165 case GDK_KEY_KP_5:
166 case GDK_KEY_KP_6:
167 case GDK_KEY_KP_7:
168 case GDK_KEY_KP_8:
169 case GDK_KEY_KP_9:
170 case GDK_KEY_Return:
171 case GDK_KEY_KP_Enter:
172 case GDK_KEY_BackSpace:
173 case GDK_KEY_Delete:
174 case GDK_KEY_Tab:
176 /* This is a number or enter which is ok */
177 break;
179 case GDK_KEY_Escape:
181 search_box_hide (search_box);
183 default:
185 /* Not a number */
186 gdk_beep ();
187 return TRUE;
190 return FALSE;
193 static gboolean
194 on_search_box_key_pressed (GtkWidget* widget, GdkEventKey* event, SearchBox* search_box)
196 switch (event->keyval)
198 case GDK_KEY_Escape:
200 gtk_widget_hide (GTK_WIDGET (search_box));
201 search_box_set_entry_color (search_box, TRUE);
202 if (search_box->priv->current_editor)
204 ianjuta_document_grab_focus (IANJUTA_DOCUMENT (search_box->priv->current_editor),
205 NULL);
208 default:
210 /* Do nothing... */
213 return FALSE;
216 static gboolean
217 on_search_focus_out (GtkWidget* widget, GdkEvent* event, SearchBox* search_box)
219 anjuta_status_pop (search_box->priv->status);
221 return FALSE;
225 /* Search regular expression in text, return TRUE and matching part as start and
226 * end integer position */
227 static gboolean
228 search_regex_in_text (const gchar* search_entry, const gchar* editor_text, gboolean search_forward, gint * start_pos, gint * end_pos)
230 GRegex * regex;
231 GMatchInfo *match_info;
232 gboolean found;
233 GError * err = NULL;
235 regex = g_regex_new (search_entry, 0, 0, &err);
236 if (err)
238 g_message ("%s",err->message);
239 g_error_free (err);
240 g_regex_unref(regex);
241 return FALSE;
244 found = g_regex_match (regex, editor_text, 0, &match_info);
246 if (found)
248 if (search_forward)
249 g_match_info_fetch_pos(match_info, 0, start_pos, end_pos);
250 else
254 g_match_info_fetch_pos(match_info, 0, start_pos, end_pos);
256 while (g_match_info_next(match_info, NULL));
259 *start_pos = g_utf8_pointer_to_offset(editor_text, &editor_text[*start_pos]);
260 *end_pos = g_utf8_pointer_to_offset(editor_text, &editor_text[*end_pos]);
263 if (regex)
264 g_regex_unref(regex);
265 if (match_info)
266 g_match_info_free (match_info);
268 return found;
272 /* Search string in text, return TRUE and matching part as start and
273 * end integer position */
274 static gboolean
275 search_str_in_text (const gchar* search_entry, const gchar* editor_text, gboolean case_sensitive, gint * start_pos, gint * end_pos)
277 gboolean found;
279 if (strlen (editor_text) >= strlen (search_entry))
281 gchar* selected_text_case;
282 gchar* search_text_case;
284 if (case_sensitive)
286 selected_text_case = g_strdup (editor_text);
287 search_text_case = g_strdup (search_entry);
289 else
291 selected_text_case = g_utf8_casefold (editor_text, strlen (editor_text));
292 search_text_case = g_utf8_casefold (search_entry, strlen (search_entry));
295 gchar* strstr = g_strstr_len (selected_text_case, -1, search_text_case);
297 if (strstr)
299 *start_pos = g_utf8_pointer_to_offset(selected_text_case, strstr);
300 *end_pos = g_utf8_pointer_to_offset(selected_text_case, strstr + strlen (search_entry));
301 found = TRUE;
304 g_free (selected_text_case);
305 g_free (search_text_case);
308 return found;
311 /* Search string in editor, return TRUE and matching part as start and
312 * end editor cell */
313 static gboolean
314 editor_search (IAnjutaEditor *editor,
315 const gchar *search_text,
316 gboolean case_sensitive,
317 gboolean search_forward,
318 gboolean regex_mode,
319 IAnjutaEditorCell* search_start,
320 IAnjutaEditorCell* search_end,
321 IAnjutaEditorCell** result_start,
322 IAnjutaEditorCell** result_end)
324 gboolean found;
326 if (regex_mode)
328 gint start_pos;
329 gint end_pos;
330 gchar *text_to_search;
332 text_to_search = ianjuta_editor_get_text (editor,
333 IANJUTA_ITERABLE (search_start),
334 IANJUTA_ITERABLE (search_end), NULL);
336 found = search_regex_in_text (search_text, text_to_search, search_forward, &start_pos, &end_pos);
338 start_pos += ianjuta_iterable_get_position(IANJUTA_ITERABLE (search_start), NULL);
339 end_pos += ianjuta_iterable_get_position(IANJUTA_ITERABLE (search_start), NULL);
341 if (found && start_pos >= 0)
343 *result_start = IANJUTA_EDITOR_CELL (ianjuta_editor_get_start_position (editor,
344 NULL));
345 *result_end = IANJUTA_EDITOR_CELL (ianjuta_editor_get_start_position (editor,
346 NULL));
348 if (!ianjuta_iterable_set_position(IANJUTA_ITERABLE(*result_start), start_pos, NULL) ||
349 !ianjuta_iterable_set_position(IANJUTA_ITERABLE(*result_end), end_pos, NULL))
351 g_object_unref(*result_start);
352 g_object_unref(*result_end);
353 found = FALSE;
357 g_free(text_to_search);
359 else
361 if (search_forward)
363 found = ianjuta_editor_search_forward (IANJUTA_EDITOR_SEARCH (editor),
364 search_text, case_sensitive,
365 search_start, search_end,
366 result_start,
367 result_end, NULL);
369 else
371 found = ianjuta_editor_search_backward (IANJUTA_EDITOR_SEARCH (editor),
372 search_text, case_sensitive,
373 search_end, search_start,
374 result_start,
375 result_end, NULL);
379 return found;
382 gboolean
383 search_box_incremental_search (SearchBox* search_box,
384 gboolean search_forward,
385 gboolean search_next,
386 gboolean wrap)
388 IAnjutaIterable* real_start;
389 IAnjutaEditorCell* search_start;
390 IAnjutaEditorCell* search_end;
391 IAnjutaEditorCell* result_start;
392 IAnjutaEditorCell* result_end;
393 IAnjutaEditorSelection* selection;
395 const gchar* search_text = gtk_entry_get_text (GTK_ENTRY (search_box->priv->search_entry));
397 gboolean found = FALSE;
399 if (!search_box->priv->current_editor || !search_text || !strlen (search_text))
400 return FALSE;
402 selection = IANJUTA_EDITOR_SELECTION (search_box->priv->current_editor);
404 if (ianjuta_editor_selection_has_selection (selection, NULL))
406 search_start =
407 IANJUTA_EDITOR_CELL (ianjuta_editor_selection_get_start (selection, NULL));
409 else
411 search_start =
412 IANJUTA_EDITOR_CELL (ianjuta_editor_get_position (search_box->priv->current_editor,
413 NULL));
416 real_start =
417 ianjuta_iterable_clone (IANJUTA_ITERABLE (search_start), NULL);
419 /* If forward, set search start and end to current position of
420 * cursor and editor end, respectively, or if backward to editor
421 * start and current position of cursor, respectively. Current
422 * position of cursor is selection start if have selection. */
423 if (search_forward)
425 search_end = IANJUTA_EDITOR_CELL (ianjuta_editor_get_position (search_box->priv->current_editor,
426 NULL));
427 ianjuta_iterable_last (IANJUTA_ITERABLE (search_end), NULL);
429 else
431 search_end = search_start;
432 search_start = IANJUTA_EDITOR_CELL (ianjuta_editor_get_position (search_box->priv->current_editor,
433 NULL));
434 ianjuta_iterable_first (IANJUTA_ITERABLE (search_start), NULL);
437 /* When there's a selection, if forward, set search start and end
438 * to match end and editor end, respectively, or if backward to
439 * editor start and match start, respectively. If forward and
440 * selection starts with a match, look for next match.
442 if (ianjuta_editor_selection_has_selection (selection,
443 NULL) && search_next)
445 gchar* selected_text =
446 ianjuta_editor_selection_get (selection, NULL);
448 gint start_pos, end_pos;
449 gboolean selected_have_search_text = FALSE;
451 selected_have_search_text = search_box->priv->regex_mode ?
452 search_regex_in_text (search_text, selected_text, TRUE, &start_pos, &end_pos) :
453 search_str_in_text (search_text, selected_text, search_box->priv->case_sensitive, &start_pos, &end_pos);
455 if (selected_have_search_text)
457 IAnjutaIterable* selection_start =
458 ianjuta_editor_selection_get_start (selection, NULL);
460 if (search_forward && start_pos == 0)
462 end_pos += ianjuta_iterable_get_position(IANJUTA_ITERABLE (selection_start), NULL);
463 ianjuta_iterable_set_position (IANJUTA_ITERABLE(search_start), end_pos, NULL);
464 ianjuta_iterable_last (IANJUTA_ITERABLE (search_end), NULL);
466 else if (!search_forward)
468 start_pos += ianjuta_iterable_get_position(IANJUTA_ITERABLE (selection_start), NULL);
469 ianjuta_iterable_set_position (IANJUTA_ITERABLE(search_end), start_pos, NULL);
470 ianjuta_iterable_first (IANJUTA_ITERABLE (search_start), NULL);
472 g_object_unref (selection_start);
475 g_free (selected_text);
478 /* Try searching in current position */
479 found = editor_search (search_box->priv->current_editor,
480 search_text,
481 search_box->priv->case_sensitive,
482 search_forward,
483 search_box->priv->regex_mode,
484 search_start,
485 search_end,
486 &result_start,
487 &result_end);
489 if (found)
491 anjuta_status_pop (ANJUTA_STATUS (search_box->priv->status));
493 else if (wrap)
495 /* Try to wrap if not found */
496 ianjuta_iterable_first (IANJUTA_ITERABLE (search_start), NULL);
497 ianjuta_iterable_last (IANJUTA_ITERABLE (search_end), NULL);
499 /* Try to search again */
500 found = editor_search (search_box->priv->current_editor,
501 search_text,
502 search_box->priv->case_sensitive,
503 search_forward,
504 search_box->priv->regex_mode,
505 search_start,
506 search_end,
507 &result_start,
508 &result_end);
510 /* Check if successful */
511 if (found)
513 if (ianjuta_iterable_compare (IANJUTA_ITERABLE (result_start),
514 real_start, NULL) != 0)
516 anjuta_status_pop (search_box->priv->status);
517 if (search_forward)
519 anjuta_status_push (search_box->priv->status,
520 _("Search for \"%s\" reached the end and was continued at the top."), search_text);
522 else
524 anjuta_status_push (search_box->priv->status,
525 _("Search for \"%s\" reached top and was continued at the bottom."), search_text);
528 else if (ianjuta_editor_selection_has_selection (selection, NULL))
530 found = FALSE;
531 anjuta_status_pop (search_box->priv->status);
532 if (search_forward)
534 anjuta_status_push (search_box->priv->status,
535 _("Search for \"%s\" reached the end and was continued at the top but no new match was found."), search_text);
537 else
539 anjuta_status_push (search_box->priv->status,
540 _("Search for \"%s\" reached top and was continued at the bottom but no new match was found."), search_text);
543 else
545 found = FALSE;
550 if (found)
552 ianjuta_editor_selection_set (selection,
553 IANJUTA_ITERABLE (result_start),
554 IANJUTA_ITERABLE (result_end), TRUE, NULL);
555 g_object_unref (result_start);
556 g_object_unref (result_end);
558 else
560 if(ianjuta_editor_selection_get (selection, NULL)!=NULL)
562 IAnjutaIterable* selection_start =
563 ianjuta_editor_selection_get_start (selection, NULL);
564 ianjuta_editor_selection_set (selection,
565 IANJUTA_ITERABLE (selection_start),
566 IANJUTA_ITERABLE (selection_start), TRUE, NULL);
567 g_object_unref (selection_start);
572 search_box_set_entry_color (search_box, found);
573 g_object_unref (real_start);
574 g_object_unref (search_start);
575 g_object_unref (search_end);
577 return found;
580 static gboolean
581 highlight_in_background (SearchBox *search_box)
583 gboolean found = FALSE;
586 if (search_box->priv->start_highlight != NULL)
588 const gchar* search_text = gtk_entry_get_text (GTK_ENTRY (search_box->priv->search_entry));
589 GTimer *timer = g_timer_new();
591 if (*search_text != '\0')
595 IAnjutaEditorCell* result_start;
596 IAnjutaEditorCell* result_end;
598 found = editor_search (search_box->priv->current_editor,
599 search_text,
600 search_box->priv->case_sensitive,
601 TRUE,
602 search_box->priv->regex_mode,
603 search_box->priv->start_highlight,
604 search_box->priv->end_highlight,
605 &result_start,
606 &result_end);
607 if (found)
609 ianjuta_indicable_set(IANJUTA_INDICABLE(search_box->priv->current_editor),
610 IANJUTA_ITERABLE (result_start),
611 IANJUTA_ITERABLE (result_end),
612 IANJUTA_INDICABLE_IMPORTANT, NULL);
613 g_object_unref (result_start);
614 g_object_unref (search_box->priv->start_highlight);
615 search_box->priv->start_highlight = result_end;
618 while (found && g_timer_elapsed(timer, NULL) < CONTINUOUS_SEARCH_TIMEOUT);
620 g_timer_destroy (timer);
623 if (!found)
625 search_box->priv->idle_id = 0;
626 g_clear_object (&search_box->priv->start_highlight);
627 g_clear_object (&search_box->priv->end_highlight);
630 return found;
633 void
634 search_box_highlight_all (SearchBox *search_box)
636 if (!search_box->priv->current_editor)
637 return;
639 ianjuta_indicable_clear(IANJUTA_INDICABLE(search_box->priv->current_editor), NULL);
640 if (search_box->priv->start_highlight != NULL) g_object_unref (search_box->priv->start_highlight);
641 if (search_box->priv->end_highlight != NULL) g_object_unref (search_box->priv->end_highlight);
642 search_box->priv->start_highlight = IANJUTA_EDITOR_CELL (ianjuta_editor_get_start_position (search_box->priv->current_editor, NULL));
643 search_box->priv->end_highlight = IANJUTA_EDITOR_CELL (ianjuta_editor_get_end_position (search_box->priv->current_editor, NULL));
645 if (search_box->priv->idle_id == 0)
647 search_box->priv->idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
648 (GSourceFunc)highlight_in_background,
649 search_box,
650 NULL);
654 void
655 search_box_clear_highlight (SearchBox * search_box)
657 if (!search_box->priv->current_editor)
658 return;
660 ianjuta_indicable_clear(IANJUTA_INDICABLE(search_box->priv->current_editor), NULL);
663 void
664 search_box_toggle_highlight (SearchBox * search_box, gboolean status)
666 if (!search_box->priv->current_editor)
667 return;
669 search_box->priv->highlight_all = status;
670 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(search_box->priv->highlight_action),
671 status);
673 if (!status)
675 ianjuta_indicable_clear(IANJUTA_INDICABLE(search_box->priv->current_editor), NULL);
676 g_clear_object (&search_box->priv->start_highlight);
677 g_clear_object (&search_box->priv->end_highlight);
679 else
681 search_box_highlight_all (search_box);
685 void
686 search_box_toggle_case_sensitive (SearchBox * search_box, gboolean status)
688 if (!search_box->priv->current_editor)
689 return;
690 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(search_box->priv->case_action),
691 status);
693 search_box->priv->case_sensitive = status;
694 if (search_box->priv->highlight_all) search_box_highlight_all (search_box);
697 void
698 search_box_toggle_regex (SearchBox * search_box, gboolean status)
700 if (!search_box->priv->current_editor)
701 return;
703 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(search_box->priv->regex_action),
704 status);
706 search_box->priv->regex_mode = status;
707 if (search_box->priv->highlight_all) search_box_highlight_all (search_box);
710 static void
711 on_search_box_entry_changed (GtkWidget * widget, SearchBox * search_box)
713 if (!search_box->priv->regex_mode)
715 GtkEntryBuffer* buffer = gtk_entry_get_buffer (GTK_ENTRY(widget));
716 if (gtk_entry_buffer_get_length (buffer))
717 search_box_incremental_search (search_box, TRUE, FALSE, TRUE);
718 else
720 /* clear selection */
721 IAnjutaIterable* cursor =
722 ianjuta_editor_get_position (IANJUTA_EDITOR (search_box->priv->current_editor),
723 NULL);
724 ianjuta_editor_selection_set (IANJUTA_EDITOR_SELECTION (search_box->priv->current_editor),
725 cursor,
726 cursor,
727 FALSE, NULL);
731 if (search_box->priv->highlight_all) search_box_highlight_all (search_box);
734 static void
735 search_box_forward_search (SearchBox * search_box, GtkWidget* widget)
737 search_box_incremental_search (search_box, TRUE, TRUE, TRUE);
740 static void
741 on_search_box_backward_search (GtkWidget * widget, SearchBox * search_box)
743 search_box_incremental_search (search_box, FALSE, TRUE, TRUE);
746 static gboolean
747 search_box_replace (SearchBox * search_box, GtkWidget * widget,
748 gboolean undo /* treat as undo action */)
751 IAnjutaEditorSelection* selection;
752 gchar * selection_text;
753 gboolean replace_successful = FALSE;
755 const gchar* replace_text = gtk_entry_get_text (GTK_ENTRY (search_box->priv->replace_entry));
756 const gchar* search_text = gtk_entry_get_text (GTK_ENTRY (search_box->priv->search_entry));
758 selection = IANJUTA_EDITOR_SELECTION (search_box->priv->current_editor);
759 selection_text = ianjuta_editor_selection_get (selection, NULL);
761 if (ianjuta_editor_selection_has_selection (selection, NULL))
763 if (search_box->priv->regex_mode)
765 GRegex * regex;
766 gchar * replacement_text;
767 gint start_pos, end_pos;
768 GError * err = NULL;
769 gboolean result = search_regex_in_text (search_text, selection_text, TRUE, &start_pos, &end_pos);
771 if (result)
773 regex = g_regex_new (search_text, 0, 0, NULL);
774 replacement_text = g_regex_replace(regex, selection_text, strlen(selection_text), 0,
775 replace_text, 0, &err);
776 if (err)
778 g_message ("%s",err->message);
779 g_error_free (err);
780 g_regex_unref(regex);
782 else
784 if (undo)
785 ianjuta_document_begin_undo_action (IANJUTA_DOCUMENT (selection), NULL);
786 ianjuta_editor_selection_replace (selection, replacement_text, strlen(replacement_text), NULL);
787 if (undo)
788 ianjuta_document_end_undo_action (IANJUTA_DOCUMENT (selection), NULL);
790 replace_successful = TRUE;
793 if (regex)
794 g_regex_unref(regex);
795 if (replacement_text)
796 g_free(replacement_text);
799 else if ((search_box->priv->case_sensitive && g_str_equal (selection_text, search_text)) ||
800 (!search_box->priv->case_sensitive && strcasecmp (selection_text, search_text) == 0))
802 if (undo)
803 ianjuta_document_begin_undo_action (IANJUTA_DOCUMENT (selection), NULL);
804 ianjuta_editor_selection_replace (selection, replace_text, strlen(replace_text), NULL);
805 if (undo)
806 ianjuta_document_end_undo_action (IANJUTA_DOCUMENT (selection), NULL);
808 replace_successful = TRUE;
811 g_free(selection_text);
814 return replace_successful;
818 static void
819 on_replace_activated (GtkWidget* widget, SearchBox* search_box)
821 gboolean successful_replace;
823 if (!search_box->priv->current_editor)
824 return;
826 /* Either replace search-term or try to move search forward to next occurence */
828 successful_replace = search_box_replace (search_box, widget, TRUE);
830 if (successful_replace)
832 search_box_forward_search (search_box, widget);
836 static void
837 do_popup_menu (GtkWidget* widget, GdkEventButton *event, SearchBox* search_box)
839 int button, event_time;
841 if (event)
843 button = event->button;
844 event_time = event->time;
846 else
848 button = 0;
849 event_time = gtk_get_current_event_time ();
852 if (!gtk_menu_get_attach_widget(GTK_MENU (search_box->priv->popup_menu)))
853 gtk_menu_attach_to_widget (GTK_MENU (search_box->priv->popup_menu), widget, NULL);
855 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(search_box->priv->case_action), search_box->priv->case_sensitive);
856 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(search_box->priv->regex_action), search_box->priv->regex_mode);
857 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(search_box->priv->highlight_action), search_box->priv->highlight_all);
859 gtk_menu_popup (GTK_MENU (search_box->priv->popup_menu), NULL, NULL, NULL, NULL,
860 button, event_time);
864 static gboolean
865 on_search_entry_icon_pressed (GtkWidget* widget, GtkEntryIconPosition pos,
866 GdkEvent* event, SearchBox * search_box)
868 do_popup_menu (widget, (GdkEventButton*)event, search_box);
869 return TRUE;
872 static gboolean
873 on_search_entry_popup_menu (GtkWidget* widget, SearchBox* search_box)
875 do_popup_menu (widget, NULL, search_box);
876 return TRUE;
879 static void
880 on_replace_all_activated (GtkWidget* widget, SearchBox* search_box)
882 IAnjutaIterable* cursor;
884 if (!search_box->priv->current_editor)
885 return;
887 /* Cache current position and search from begin */
888 cursor = ianjuta_editor_get_position (IANJUTA_EDITOR (search_box->priv->current_editor),
889 NULL);
890 ianjuta_editor_goto_start (IANJUTA_EDITOR (search_box->priv->current_editor), NULL);
892 /* Replace all instances of search_entry with replace_entry text */
893 ianjuta_document_begin_undo_action (IANJUTA_DOCUMENT (search_box->priv->current_editor), NULL);
894 while (search_box_incremental_search (search_box, TRUE, TRUE, FALSE))
896 search_box_replace (search_box, widget, FALSE);
898 ianjuta_document_end_undo_action (IANJUTA_DOCUMENT (search_box->priv->current_editor), NULL);
900 /* Back to cached position */
901 ianjuta_editor_selection_set (IANJUTA_EDITOR_SELECTION (search_box->priv->current_editor),
902 cursor, cursor, TRUE, NULL);
903 g_object_unref (cursor);
906 static void
907 search_box_init (SearchBox *search_box)
909 search_box->priv = GET_PRIVATE(search_box);
910 GList* focus_chain = NULL;
911 GtkStyleContext *context;
913 /* Button images */
914 GtkWidget* close =
915 gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
917 /* Searching */
918 search_box->priv->search_entry = gtk_entry_new();
919 gtk_widget_set_tooltip_text (search_box->priv->search_entry,
920 _("Use the context menu of the \"Find\" icon for more search options"));
921 g_signal_connect_swapped (G_OBJECT (search_box->priv->search_entry), "activate",
922 G_CALLBACK (search_box_forward_search),
923 search_box);
924 g_signal_connect (G_OBJECT (search_box), "key-press-event",
925 G_CALLBACK (on_search_box_key_pressed),
926 search_box);
927 g_signal_connect (G_OBJECT (search_box->priv->search_entry), "changed",
928 G_CALLBACK (on_search_box_entry_changed),
929 search_box);
930 g_signal_connect (G_OBJECT (search_box->priv->search_entry), "focus-out-event",
931 G_CALLBACK (on_search_focus_out),
932 search_box);
933 g_signal_connect (G_OBJECT (search_box->priv->search_entry), "icon-press",
934 G_CALLBACK (on_search_entry_icon_pressed),
935 search_box);
936 g_signal_connect (G_OBJECT (search_box->priv->search_entry), "popup-menu",
937 G_CALLBACK (on_search_entry_popup_menu),
938 search_box);
940 search_box->priv->close_button = gtk_button_new();
941 gtk_button_set_image (GTK_BUTTON (search_box->priv->close_button), close);
942 gtk_button_set_relief (GTK_BUTTON (search_box->priv->close_button), GTK_RELIEF_NONE);
944 g_signal_connect (G_OBJECT (search_box->priv->close_button), "clicked",
945 G_CALLBACK (on_search_box_hide), search_box);
947 /* CSS custom style */
948 search_box->priv->provider = gtk_css_provider_new ();
949 gtk_css_provider_load_from_data (search_box->priv->provider,
950 ".not-found {color: @gedit_not_found_fg; background-image: none; background-color: @gedit_not_found_bg;}"
951 ".not-found:selected {color: @theme_selected_fg_color; background-color: @theme_selected_bg_color;}"
952 , -1, NULL);
953 context = gtk_widget_get_style_context (GTK_WIDGET (search_box->priv->search_entry));
954 gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (search_box->priv->provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
957 /* Previous, Next Navigation */
958 search_box->priv->next_button = gtk_button_new ();
959 gtk_container_add (GTK_CONTAINER (search_box->priv->next_button),
960 gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
961 GTK_ICON_SIZE_BUTTON));
962 gtk_button_set_relief (GTK_BUTTON (search_box->priv->next_button), GTK_RELIEF_NONE);
963 g_signal_connect_swapped (G_OBJECT(search_box->priv->next_button), "clicked",
964 G_CALLBACK (search_box_forward_search), search_box);
965 search_box->priv->previous_button = gtk_button_new ();
966 gtk_container_add (GTK_CONTAINER (search_box->priv->previous_button),
967 gtk_image_new_from_stock (GTK_STOCK_GO_BACK,
968 GTK_ICON_SIZE_BUTTON));
969 gtk_button_set_relief (GTK_BUTTON (search_box->priv->previous_button), GTK_RELIEF_NONE);
970 g_signal_connect (G_OBJECT(search_box->priv->previous_button), "clicked",
971 G_CALLBACK (on_search_box_backward_search), search_box);
973 /* Goto line */
974 search_box->priv->goto_entry = gtk_entry_new ();
975 gtk_entry_set_width_chars (GTK_ENTRY (search_box->priv->goto_entry), LINE_ENTRY_WIDTH);
976 gtk_entry_set_icon_from_stock (GTK_ENTRY (search_box->priv->goto_entry),
977 GTK_ENTRY_ICON_SECONDARY,
978 ANJUTA_STOCK_GOTO_LINE);
979 g_signal_connect (G_OBJECT (search_box->priv->goto_entry), "activate",
980 G_CALLBACK (on_goto_activated),
981 search_box);
982 g_signal_connect (G_OBJECT (search_box->priv->goto_entry), "key-press-event",
983 G_CALLBACK (on_goto_key_pressed),
984 search_box);
985 /* Replace */
986 search_box->priv->replace_entry = gtk_entry_new();
987 g_signal_connect (G_OBJECT (search_box->priv->replace_entry), "activate",
988 G_CALLBACK (on_replace_activated),
989 search_box);
991 search_box->priv->replace_button = gtk_button_new_with_label(_("Replace"));
992 gtk_button_set_relief (GTK_BUTTON (search_box->priv->replace_button), GTK_RELIEF_NONE);
993 g_signal_connect (G_OBJECT(search_box->priv->replace_button), "clicked",
994 G_CALLBACK (on_replace_activated), search_box);
996 search_box->priv->replace_all_button = gtk_button_new_with_label(_("Replace all"));
997 gtk_button_set_relief (GTK_BUTTON (search_box->priv->replace_all_button), GTK_RELIEF_NONE);
998 g_signal_connect (G_OBJECT(search_box->priv->replace_all_button), "clicked",
999 G_CALLBACK (on_replace_all_activated), search_box);
1001 /* Popup Menu Options */
1002 search_box->priv->regex_mode = FALSE;
1003 search_box->priv->highlight_all = FALSE;
1004 search_box->priv->case_sensitive = FALSE;
1006 /* Highlight iterator */
1007 search_box->priv->start_highlight = NULL;
1008 search_box->priv->end_highlight = NULL;
1009 search_box->priv->idle_id = 0;
1011 /* Initialize search_box grid */
1012 search_box->priv->grid = gtk_grid_new();
1013 gtk_orientable_set_orientation (GTK_ORIENTABLE (search_box->priv->grid),
1014 GTK_ORIENTATION_VERTICAL);
1015 gtk_grid_set_row_spacing (GTK_GRID (search_box->priv->grid), 5);
1017 /* Attach search elements to grid */
1018 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->goto_entry, 0, 0, 1, 1);
1020 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->search_entry, 1, 0, 1, 1);
1022 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->previous_button, 2, 0, 1, 1);
1023 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->next_button, 3, 0, 1, 1);
1025 gtk_grid_attach_next_to (GTK_GRID (search_box->priv->grid),
1026 search_box->priv->close_button,
1027 search_box->priv->next_button,
1028 GTK_POS_RIGHT, 1, 1);
1029 gtk_widget_set_hexpand(search_box->priv->close_button, TRUE);
1030 gtk_widget_set_halign(search_box->priv->close_button,
1031 GTK_ALIGN_END);
1033 /* Add Replace elements to search box on 2nd level */
1034 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->replace_entry, 1, 1, 1, 1);
1035 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->replace_button, 2, 1, 1, 1);
1036 gtk_grid_attach (GTK_GRID (search_box->priv->grid), search_box->priv->replace_all_button, 3, 1, 1, 1);
1038 /* Expand search entries (a bit) */
1039 gtk_entry_set_width_chars (GTK_ENTRY (search_box->priv->search_entry), SEARCH_ENTRY_WIDTH);
1040 gtk_entry_set_width_chars (GTK_ENTRY (search_box->priv->replace_entry), SEARCH_ENTRY_WIDTH);
1042 /* Set nice icons */
1043 gtk_entry_set_icon_from_stock (GTK_ENTRY (search_box->priv->search_entry),
1044 GTK_ENTRY_ICON_PRIMARY,
1045 GTK_STOCK_FIND);
1046 gtk_entry_set_icon_from_stock (GTK_ENTRY (search_box->priv->replace_entry),
1047 GTK_ENTRY_ICON_PRIMARY,
1048 GTK_STOCK_FIND_AND_REPLACE);
1050 /* Pack grid into search box */
1051 gtk_box_pack_start (GTK_BOX(search_box), search_box->priv->grid, TRUE, TRUE, 0);
1053 /* Set focus chain */
1054 focus_chain = g_list_prepend (focus_chain, search_box->priv->search_entry);
1055 focus_chain = g_list_prepend (focus_chain, search_box->priv->replace_entry);
1056 focus_chain = g_list_prepend (focus_chain, search_box->priv->next_button);
1057 focus_chain = g_list_prepend (focus_chain, search_box->priv->previous_button);
1058 focus_chain = g_list_prepend (focus_chain, search_box->priv->replace_button);
1059 focus_chain = g_list_prepend (focus_chain, search_box->priv->replace_all_button);
1060 focus_chain = g_list_prepend (focus_chain, search_box->priv->goto_entry);
1061 focus_chain = g_list_prepend (focus_chain, search_box->priv->close_button);
1062 focus_chain = g_list_prepend (focus_chain, search_box->priv->search_entry);
1063 focus_chain = g_list_reverse (focus_chain);
1064 gtk_container_set_focus_chain (GTK_CONTAINER (search_box->priv->grid),
1065 focus_chain);
1066 g_list_free (focus_chain);
1068 /* Show all children but keep the top box hidden. */
1069 gtk_widget_show_all (GTK_WIDGET (search_box));
1070 gtk_widget_hide (GTK_WIDGET (search_box));
1073 static void
1074 search_box_finalize (GObject *object)
1076 SearchBox *search_box = SEARCH_BOX (object);
1078 if (search_box->priv->idle_id) g_source_remove (search_box->priv->idle_id);
1079 if (search_box->priv->start_highlight) g_object_unref (search_box->priv->start_highlight);
1080 if (search_box->priv->end_highlight) g_object_unref (search_box->priv->end_highlight);
1081 if (search_box->priv->provider) g_object_unref (search_box->priv->provider);
1083 G_OBJECT_CLASS (search_box_parent_class)->finalize (object);
1086 static void
1087 search_box_class_init (SearchBoxClass *klass)
1089 GObjectClass* object_class = G_OBJECT_CLASS (klass);
1091 g_type_class_add_private (klass, sizeof (SearchBoxPrivate));
1093 object_class->finalize = search_box_finalize;
1096 GtkWidget*
1097 search_box_new (AnjutaDocman *docman)
1099 SearchBox* search_box;
1100 AnjutaUI *ui;
1102 search_box = SEARCH_BOX (g_object_new (SEARCH_TYPE_BOX, "homogeneous",
1103 FALSE, NULL));
1105 g_signal_connect (G_OBJECT (docman), "document-changed",
1106 G_CALLBACK (on_document_changed), search_box);
1108 search_box->priv->status = anjuta_shell_get_status (docman->shell, NULL);
1110 ui = anjuta_shell_get_ui (docman->shell, NULL);
1111 search_box->priv->popup_menu = gtk_ui_manager_get_widget (GTK_UI_MANAGER (ui),
1112 "/SearchboxPopup");
1113 g_assert (search_box->priv->popup_menu != NULL && GTK_IS_MENU (search_box->priv->popup_menu));
1115 search_box->priv->case_action =
1116 gtk_ui_manager_get_action (GTK_UI_MANAGER (ui),
1117 "/SearchboxPopup/CaseCheck");
1119 search_box->priv->highlight_action =
1120 gtk_ui_manager_get_action (GTK_UI_MANAGER (ui),
1121 "/SearchboxPopup/HighlightAll");
1122 search_box->priv->regex_action =
1123 gtk_ui_manager_get_action (GTK_UI_MANAGER (ui),
1124 "/SearchboxPopup/RegexSearch");
1126 g_signal_connect (search_box->priv->popup_menu, "deactivate",
1127 G_CALLBACK (gtk_widget_hide), NULL);
1129 return GTK_WIDGET (search_box);
1132 void
1133 search_box_fill_search_focus (SearchBox* search_box, gboolean on_replace)
1135 IAnjutaEditor* te = search_box->priv->current_editor;
1137 if (IANJUTA_IS_EDITOR (te) && !search_box->priv->regex_mode)
1139 gchar *buffer;
1141 buffer = ianjuta_editor_selection_get (IANJUTA_EDITOR_SELECTION (te), NULL);
1142 if (buffer != NULL)
1144 g_strstrip (buffer);
1145 if (*buffer != 0)
1148 gtk_entry_set_text (GTK_ENTRY (search_box->priv->search_entry), buffer);
1149 gtk_editable_select_region (GTK_EDITABLE (search_box->priv->search_entry), 0, -1);
1152 g_free (buffer);
1156 /* Toggle replace level (replace entry, replace buttons) of search box */
1157 search_box_set_replace (search_box, on_replace);
1159 gtk_widget_grab_focus (search_box->priv->search_entry);
1162 void
1163 search_box_grab_line_focus (SearchBox* search_box)
1165 gtk_widget_grab_focus (search_box->priv->goto_entry);
1168 void
1169 search_box_hide (SearchBox* search_box)
1171 gtk_widget_hide (GTK_WIDGET (search_box));
1172 search_box_set_entry_color (search_box, TRUE);
1173 if (search_box->priv->current_editor)
1175 ianjuta_document_grab_focus (IANJUTA_DOCUMENT (search_box->priv->current_editor),
1176 NULL);
1180 void
1181 search_box_set_replace (SearchBox* search_box, gboolean replace)
1183 if (replace)
1185 gtk_widget_show (search_box->priv->replace_entry);
1186 gtk_widget_show (search_box->priv->replace_button);
1187 gtk_widget_show (search_box->priv->replace_all_button);
1189 else
1191 gtk_widget_hide (search_box->priv->replace_entry);
1192 gtk_widget_hide (search_box->priv->replace_button);
1193 gtk_widget_hide (search_box->priv->replace_all_button);
1197 const gchar* search_box_get_search_string (SearchBox* search_box)
1199 g_return_val_if_fail (search_box != NULL && SEARCH_IS_BOX(search_box), NULL);
1201 return gtk_entry_get_text (GTK_ENTRY (search_box->priv->search_entry));
1204 void search_box_set_search_string (SearchBox* search_box, const gchar* search)
1206 g_return_if_fail (search_box != NULL && SEARCH_IS_BOX(search_box));
1208 gtk_entry_set_text (GTK_ENTRY (search_box->priv->search_entry), search);
1211 const gchar* search_box_get_replace_string (SearchBox* search_box)
1213 g_return_val_if_fail (search_box != NULL && SEARCH_IS_BOX(search_box), NULL);
1215 return gtk_entry_get_text (GTK_ENTRY (search_box->priv->replace_entry));
1219 void search_box_set_replace_string (SearchBox* search_box, const gchar* replace)
1221 g_return_if_fail (search_box != NULL && SEARCH_IS_BOX(search_box));
1223 gtk_entry_set_text (GTK_ENTRY (search_box->priv->replace_entry), replace);
1226 void
1227 search_box_session_load (SearchBox* search_box, AnjutaSession* session)
1229 g_return_if_fail (search_box != NULL && SEARCH_IS_BOX(search_box));
1231 search_box->priv->case_sensitive = anjuta_session_get_int (session, "Search Box", "Case Sensitive") ? TRUE : FALSE;
1232 search_box->priv->regex_mode = anjuta_session_get_int (session, "Search Box", "Regular Expression") ? TRUE : FALSE;
1233 search_box->priv->highlight_all = anjuta_session_get_int (session, "Search Box", "Highlight Match") ? TRUE : FALSE;
1236 void
1237 search_box_session_save (SearchBox* search_box, AnjutaSession* session)
1239 g_return_if_fail (search_box != NULL && SEARCH_IS_BOX(search_box));
1241 anjuta_session_set_int (session, "Search Box", "Case Sensitive", search_box->priv->case_sensitive ? 1 : 0);
1242 anjuta_session_set_int (session, "Search Box", "Regular Expression", search_box->priv->regex_mode ? 1 : 0);
1243 anjuta_session_set_int (session, "Search Box", "Highlight Match", search_box->priv->highlight_all ? 1 : 0);