/* * R : A Computer Langage for Statistical Data Analysis * Copyright (C) 1998-2004 Lyndon Drake * and the R Development Core Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Avoid braced-groups warning from -Wall */ #define G_STMT_START do #define G_STMT_END while(0) #include "gnome-find-dialog.h" #include "terminal.h" #include "terminal-find.h" #include #include #include #define ERRBUF_SIZE 500 #define MSGBUF_SIZE 2000 /* indexes for a find match */ typedef struct _find_selection find_selection; struct _find_selection { int select_start; int select_end; }; static GnomeFindDialogParams find_params; /* find parameters from dialog box */ static gint find_pos, find_pos_max; /* current position of the find 'cursor', and the max (min is 0) */ static gint find_pos_init; /* the initial find position (for wrapped searches) */ static gboolean search_wrapped; /* used to determine if a search has wrapped */ static gchar *find_text_cache; /* cache of the console text */ static gboolean line_cache_update; /* TRUE if line cache needs updating */ static gint line_cache_start, line_cache_end; /* indexes into find_text_cache of the area covered by the line cache */ static GList *find_current_match; /* List of matches on currently cached line */ regex_t *preg; /* compiled regular expression structure */ /* FIXME: wrapped searches */ void find_update_text_cache(void) { /* update text cache */ find_text_cache = gtk_editable_get_chars(GTK_EDITABLE(R_gtk_terminal_text), 0, -1); find_pos_max = gtk_text_get_length(GTK_TEXT(R_gtk_terminal_text)) - 1; /* force line cache update */ line_cache_update = TRUE; } void find_compile_regex(GnomeFindDialog *find_dialog) { int cflags; int regex_result; char errbuf[ERRBUF_SIZE]; char messagebuf[MSGBUF_SIZE]; GtkWidget *message_dialog; if(find_params.regex == TRUE) { /* compile the regular expression */ cflags = REG_EXTENDED; if(find_params.case_sensitive == FALSE) { cflags = cflags | REG_ICASE; } preg = (regex_t *) g_malloc(sizeof(regex_t)); regex_result = regcomp(preg, find_params.find_text, cflags); if(regex_result != 0) { regerror(regex_result, preg, errbuf, ERRBUF_SIZE); g_snprintf(messagebuf, MSGBUF_SIZE, "Error compiling regular expression: %s", errbuf); message_dialog = gnome_message_box_new(messagebuf, GNOME_MESSAGE_BOX_ERROR, GNOME_STOCK_BUTTON_OK, NULL); if (find_dialog != NULL ) { gnome_dialog_set_parent(GNOME_DIALOG(message_dialog), GTK_WINDOW(find_dialog)); } else { gnome_dialog_set_parent(GNOME_DIALOG(message_dialog), GTK_WINDOW(R_gtk_main_window)); } gnome_dialog_run_and_close(GNOME_DIALOG(message_dialog)); return; } } } void find_free_regex(void) { if(preg != NULL) { regfree(preg); preg = NULL; } } void find_free_select(gpointer data, gpointer user_data) { if(data != NULL) { g_free(data); } } int find_update_line_cache(GnomeFindDialog *find_dialog) { int find_text_len; int eflags; regmatch_t pmatch[1]; int regex_result; char errbuf[ERRBUF_SIZE]; char messagebuf[MSGBUF_SIZE]; int strcmp_result; int tmp_find_pos; gchar *find_line_cache; find_selection *find_select; GtkWidget *message_dialog; /* initialisation */ find_line_cache = NULL; find_text_len = strlen(find_params.find_text); line_cache_update = FALSE; /* free matches list if necessary */ if (find_current_match != NULL) { find_current_match = g_list_first(find_current_match); g_list_foreach(find_current_match, find_free_select, NULL); g_list_free(find_current_match); find_current_match = NULL; } /* Move one line */ switch (find_params.direction) { case GNOME_FIND_FORWARDS: do { if(find_pos > find_pos_max) { if(find_params.wrap_search == TRUE) { find_pos = 0; search_wrapped = TRUE; } else { return GNOME_FIND_NOMATCH; } } else { find_pos++; if((search_wrapped) && (find_pos >= find_pos_init)) { return GNOME_FIND_NOMATCH; } } } while (find_text_cache[find_pos] == '\n'); break; case GNOME_FIND_BACKWARDS: do { if(find_pos < 0) { if(find_params.wrap_search == TRUE) { find_pos = find_pos_max; search_wrapped = TRUE; } else { return GNOME_FIND_NOMATCH; } } else { find_pos--; if((search_wrapped) && (find_pos <= find_pos_init)) { return GNOME_FIND_NOMATCH; } } } while (find_text_cache[find_pos] == '\n'); break; } line_cache_start = line_cache_end = find_pos; while ((line_cache_start > 0) && (find_text_cache[line_cache_start - 1] != '\n')) { line_cache_start--; } while ((line_cache_end < find_pos_max) && (find_text_cache[line_cache_end + 1] != '\n')) { line_cache_end++; } /* Grab the line */ find_line_cache = g_strndup(find_text_cache + line_cache_start, line_cache_end - line_cache_start + 1); /* Search line */ if (find_params.regex == TRUE) { /* Regular expression search */ tmp_find_pos = 0; eflags = 0; do { if (tmp_find_pos != 0) { eflags = REG_NOTBOL; } /* execute the match */ regex_result = regexec(preg, find_line_cache + tmp_find_pos, 1, pmatch, eflags); if(regex_result == 0) { /* construct list item if found */ find_select = g_new(find_selection, 1); find_select->select_start = line_cache_start + tmp_find_pos + pmatch[0].rm_so; find_select->select_end = line_cache_start + tmp_find_pos + pmatch[0].rm_eo; find_current_match = g_list_append(find_current_match, (gpointer) find_select); tmp_find_pos += pmatch[0].rm_so + 1; } else if (regex_result != REG_NOMATCH) { /* report regexec errors and terminate the search */ regerror(regex_result, preg, errbuf, ERRBUF_SIZE); g_snprintf(messagebuf, MSGBUF_SIZE, "Error matching regular expression: %s", errbuf); message_dialog = gnome_message_box_new(messagebuf, GNOME_MESSAGE_BOX_ERROR, GNOME_STOCK_BUTTON_OK, NULL); if (find_dialog != NULL ) { gnome_dialog_set_parent(GNOME_DIALOG(message_dialog), GTK_WINDOW(find_dialog)); } else { gnome_dialog_set_parent(GNOME_DIALOG(message_dialog), GTK_WINDOW(R_gtk_main_window)); } gnome_dialog_run_and_close(GNOME_DIALOG(message_dialog)); if (find_line_cache != NULL) { g_free(find_line_cache); } return GNOME_FIND_NOMATCH; } } while ((tmp_find_pos <= (line_cache_end - line_cache_start)) && (regex_result == 0)); } else { /* Literal search */ for (tmp_find_pos = line_cache_start; tmp_find_pos <= line_cache_end; tmp_find_pos++) { if (find_params.case_sensitive == TRUE) { strcmp_result = strncmp(find_params.find_text, find_text_cache + tmp_find_pos, find_text_len); } else { strcmp_result = g_strncasecmp(find_params.find_text, find_text_cache + tmp_find_pos, find_text_len); } /* construct list item if found */ if(strcmp_result == 0) { find_select = g_new(find_selection, 1); find_select->select_start = tmp_find_pos; find_select->select_end = tmp_find_pos + find_text_len; find_current_match = g_list_append(find_current_match, (gpointer) find_select); } } } if (find_line_cache != NULL) { g_free(find_line_cache); } /* Return result */ if (find_current_match != NULL) { if(find_params.direction == GNOME_FIND_BACKWARDS) find_current_match = g_list_last(find_current_match); return GNOME_FIND_MATCH; } return GNOME_FIND_NOTFOUND; } void find_process_result(GnomeFindDialog *find_dialog, int find_result) { find_selection *find_select; GtkWidget *message_dialog; if (find_dialog != NULL) g_return_if_fail(GNOME_IS_FIND_DIALOG(find_dialog)); switch (find_result) { case GNOME_FIND_NOMATCH: message_dialog = gnome_message_box_new("Could not find text in console output.", GNOME_MESSAGE_BOX_WARNING, GNOME_STOCK_BUTTON_OK, NULL); if (find_dialog != NULL ) { gnome_dialog_set_parent(GNOME_DIALOG(message_dialog), GTK_WINDOW(find_dialog)); } else { gnome_dialog_set_parent(GNOME_DIALOG(message_dialog), GTK_WINDOW(R_gtk_main_window)); } gnome_dialog_run_and_close (GNOME_DIALOG(message_dialog)); if(find_dialog != NULL) gnome_dialog_set_default(GNOME_DIALOG(find_dialog), GNOME_FIND_BUTTON_FIND); break; case GNOME_FIND_MATCH: find_select = (find_selection *) find_current_match->data; gtk_editable_set_position(GTK_EDITABLE(R_gtk_terminal_text), find_select->select_end); gtk_editable_select_region(GTK_EDITABLE(R_gtk_terminal_text), find_select->select_start, find_select->select_end); break; } } void R_gtk_terminal_find(GnomeFindDialog *find_dialog) { int find_result; find_selection *find_select; if(find_dialog != NULL) g_return_if_fail(GNOME_IS_FIND_DIALOG(find_dialog)); if(find_params.find_text == NULL) return; /* update the text cache if necessary */ if(find_text_cache == NULL) { find_update_text_cache(); } /* compile the regular expression */ find_compile_regex(find_dialog); /* do the actual find */ find_result = GNOME_FIND_NOTFOUND; while (find_result == GNOME_FIND_NOTFOUND) { if ((line_cache_update == TRUE) || (find_pos < line_cache_start) || (find_pos > line_cache_end)) { find_result = find_update_line_cache(find_dialog); } else if (find_params.direction == GNOME_FIND_FORWARDS) { if ((find_current_match == NULL) || (find_current_match->next == NULL)) { find_pos = line_cache_end + 1; find_result = find_update_line_cache(find_dialog); } else { find_select = (find_selection *) find_current_match->next->data; if ((search_wrapped) && (find_select->select_start >= find_pos_init)) { find_result = GNOME_FIND_NOMATCH; } else { find_current_match = find_current_match->next; find_result = GNOME_FIND_MATCH; } } } else if (find_params.direction == GNOME_FIND_BACKWARDS) { if ((find_current_match == NULL) || (find_current_match->prev == NULL)) { find_pos = line_cache_start - 1; find_result = find_update_line_cache(find_dialog); } else { find_select = (find_selection *) find_current_match->prev->data; if ((search_wrapped) && (find_select->select_start <= find_pos_init)) { find_result = GNOME_FIND_NOMATCH; } else { find_current_match = find_current_match->prev; find_result = GNOME_FIND_MATCH; } } } } /* update UI based on result */ find_process_result(find_dialog, find_result); /* free the compiled regex */ find_free_regex(); } static void find_console_changed(GtkWidget *widget, gpointer data) { if (find_text_cache != NULL) { g_free(find_text_cache); find_text_cache = NULL; } } static void find_dialog_cb(GtkWidget *widget, gpointer data) { gchar *tmp_text; g_return_if_fail(widget != NULL); g_return_if_fail(GNOME_IS_FIND_DIALOG(widget)); tmp_text = gnome_find_dialog_get_find_text(GNOME_FIND_DIALOG(widget)); if(find_params.find_text != NULL) { if(strcmp(find_params.find_text, tmp_text) != 0) { line_cache_update = TRUE; } g_free(find_params.find_text); } if(find_params.regex != GNOME_FIND_DIALOG(widget)->params.regex) line_cache_update = TRUE; find_params = GNOME_FIND_DIALOG(widget)->params; find_params.find_text = tmp_text; switch(find_params.start_pos) { case GNOME_FIND_TOP: find_pos = 0; break; case GNOME_FIND_CURSOR: find_pos = gtk_editable_get_position(GTK_EDITABLE(R_gtk_terminal_text)); break; case GNOME_FIND_BOTTOM: find_pos = gtk_text_get_length(GTK_TEXT(R_gtk_terminal_text)) - 1; break; } find_pos_init = find_pos; search_wrapped = FALSE; R_gtk_terminal_find(GNOME_FIND_DIALOG(widget)); } static void find_again_dialog_cb(GtkWidget *widget, gpointer data) { gchar *tmp_text; g_return_if_fail(widget != NULL); g_return_if_fail(GNOME_IS_FIND_DIALOG(widget)); tmp_text = gnome_find_dialog_get_find_text(GNOME_FIND_DIALOG(widget)); if(find_params.find_text != NULL) { if(strcmp(find_params.find_text, tmp_text) != 0) { line_cache_update = TRUE; } g_free(find_params.find_text); } if(find_params.regex != GNOME_FIND_DIALOG(widget)->params.regex) line_cache_update = TRUE; find_params = GNOME_FIND_DIALOG(widget)->params; find_params.find_text = tmp_text; R_gtk_terminal_find(GNOME_FIND_DIALOG(widget)); } void edit_find_cb(GtkWidget *widget, gpointer data) { GtkWidget *find_dialog; find_dialog = gnome_find_dialog_new("Find text", &find_params, TRUE, TRUE, TRUE); gnome_dialog_set_parent(GNOME_DIALOG(find_dialog), GTK_WINDOW(R_gtk_main_window)); gtk_signal_connect(GTK_OBJECT(find_dialog), "find", (GtkSignalFunc) find_dialog_cb, NULL); gtk_signal_connect(GTK_OBJECT(find_dialog), "find_again", (GtkSignalFunc) find_again_dialog_cb, NULL); gtk_widget_show(find_dialog); } void edit_find_again_cb(GtkWidget *widget, gpointer data) { R_gtk_terminal_find(NULL); } void R_gtk_terminal_find_init() { /* Find functionality */ find_params.find_text = NULL; find_params.direction = GNOME_FIND_FORWARDS; find_params.start_pos = GNOME_FIND_TOP; find_params.case_sensitive = FALSE; find_params.wrap_search = FALSE; find_params.regex = FALSE; find_pos = 0; find_pos_init = 0; search_wrapped = FALSE; find_text_cache = NULL; line_cache_update = TRUE; find_current_match = NULL; preg = NULL; gtk_signal_connect(GTK_OBJECT(R_gtk_terminal_text), "changed", (GtkSignalFunc) find_console_changed, NULL); }