/*
* Maketool - GTK-based front end for gmake
* Copyright (c) 1999-2003 Greg Banks
*
* 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
*/
#include "maketool.h"
#include "log.h"
#include "ui.h"
#include "util.h"
#include <regex.h> /* POSIX regular expression fns */
#include <gdk/gdkkeysyms.h>
CVSID("$Id: find.c,v 1.19 2003/09/29 01:07:20 gnb Exp $");
#define FINDCASE 0 /* TODO: implement case-insensitive literals */
static GtkWidget *find_shell = 0;
typedef enum { FD_FORWARDS, FD_BACKWARDS, FD_MAX_DIRECTIONS } FindDirections;
typedef enum
{
FT_NONE=-1, /* invalid value */
FT_LITERAL, /* case-INsensitive literal */
#if FINDCASE
FT_CASE_LITERAL, /* case-sensitive literal */
#endif
FT_REGEXP, /* regular expression */
FT_MAX_TYPES
} FindTypes;
static GtkWidget *dirn_radio[FD_MAX_DIRECTIONS];
static GtkWidget *type_radio[FT_MAX_TYPES];
static GtkWidget *string_entry;
typedef struct
{
FindTypes type;
char *string;
regex_t regexp;
} FindState;
static GList *find_state_stack = 0;
static GList *find_state_current = 0;
FindDirections direction = FD_FORWARDS;
gboolean wrap = TRUE;
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
#if DEBUG
static void
find_state_print(FILE *fp, const FindState *s)
{
const char *type_str = 0;
switch (s->type)
{
case FT_LITERAL: /* case-INsensitive literal */
type_str = "insensitive-literal";
break;
#if FINDCASE
case FT_CASE_LITERAL: /* case-sensitive literal */
type_str = "sensitive-literal";
break;
#endif
case FT_REGEXP: /* regular expression */
type_str = "regexp";
break;
default:
type_str = "unknown";
break;
}
fprintf(fp, "{ type=%s string=\"%s\" }",
type_str, s->string);
}
#endif
static int
find_state_compare(const FindState *s1, const FindState *s2)
{
if (s1->type != s2->type)
return (int)s1->type - (int)s2->type;
return strcmp(s1->string, s2->string);
}
static gboolean
find_state_get(FindState *state)
{
int i;
state->type = FT_NONE;
state->string = 0;
/* get current type */
for (i=0 ; i<FT_MAX_TYPES ; i++)
{
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(type_radio[i])))
{
state->type = i;
break;
}
}
if (state->type == FT_NONE)
return FALSE;
/* TODO: separately reinit these */
state->string = (char *)gtk_entry_get_text(GTK_ENTRY(string_entry));
if (state->type == FT_REGEXP)
{
guint err;
if ((err = regcomp(&state->regexp, state->string, REG_EXTENDED|REG_NOSUB)) != 0)
{
char errbuf[1024];
regerror(err, &state->regexp, errbuf, sizeof(errbuf));
/* TODO: decent error reporting mechanism */
fprintf(stderr, _("%s: error in regular expression \"%s\": %s\n"),
argv0, state->string, errbuf);
regfree(&state->regexp);
return FALSE;
}
}
return TRUE;
}
static void
find_state_set(const FindState *state)
{
/* set current type */
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(type_radio[state->type]), TRUE);
gtk_entry_set_text(GTK_ENTRY(string_entry), state->string);
}
static void
find_state_push(FindState *s)
{
#if DEBUG
fprintf(stderr, "pushing find state: ");
find_state_print(stderr, s);
fprintf(stderr, "\n");
#endif
find_state_stack = g_list_prepend(find_state_stack, s);
find_state_current = find_state_stack;
}
static FindState *
find_state_find(const FindState *proto)
{
GList *link = g_list_find_custom(find_state_stack, (gpointer)proto,
(GCompareFunc)find_state_compare);
return (link == 0 ? 0 : (FindState *)link->data);
}
/*
* Notice that `prev' wrt to the user is backwards to
* `prev' wrt the stack linkage.
*/
static void
find_state_prev(void)
{
if (find_state_current != 0 &&
find_state_current->next != 0)
{
find_state_current = find_state_current->next;
find_state_set((FindState *)find_state_current->data);
}
}
static void
find_state_next(void)
{
if (find_state_current != 0 &&
find_state_current->prev != 0)
{
find_state_current = find_state_current->prev;
find_state_set((FindState *)find_state_current->data);
}
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static gboolean found = FALSE;
/* returns FALSE to stop iteration */
static gboolean
find_apply_func(LogRec *lr, gpointer user_data)
{
FindState *state = (FindState *)user_data;
switch (state->type)
{
case FT_LITERAL: /* case-INsensitive literal */
/* TODO: implement this properly */
found = (strstr(log_get_text(lr), state->string) != 0);
break;
#if FINDCASE
case FT_CASE_LITERAL: /* case-sensitive literal */
found = (strstr(log_get_text(lr), state->string) != 0);
break;
#endif
case FT_REGEXP: /* regular expression */
found = !regexec(&state->regexp, log_get_text(lr), 0, 0, 0);
break;
case FT_NONE:
case FT_MAX_TYPES:
break; /* shut up gcc -Wall */
}
if (found)
log_set_selected(lr);
return !found;
}
static void
do_find(void)
{
FindState *state, proto;
memset(&proto, 0, sizeof(proto));
if (!find_state_get(&proto))
return;
state = find_state_find(&proto);
if (state == 0)
{
/* TODO: enforce a max length of the stack */
#if DEBUG
fprintf(stderr, "do_find: creating new state\n");
#endif
state = g_new(FindState, 1);
memcpy(state, &proto, sizeof(*state));
state->string = g_strdup(state->string);
find_state_push(state);
}
else
{
#if DEBUG
fprintf(stderr, "do_find: reusing old state\n");
#endif
find_state_stack = g_list_remove(find_state_stack, state);
find_state_push(state);
}
/* do the actual search */
found = FALSE;
log_apply_after(
find_apply_func,
(direction == FD_FORWARDS),
log_selected(),
(gpointer)state);
if (!found && wrap)
{
/* try again from the other end */
log_apply_after(
find_apply_func,
(direction == FD_FORWARDS),
0,
(gpointer)state);
}
if (!found)
{
gdk_beep();
message(_("Pattern not found."));
}
/* The Find Again menu item is now available */
grey_menu_items();
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static void
find_find_cb(GtkWidget *w, gpointer data)
{
do_find();
}
static void
find_close_cb(GtkWidget *w, gpointer data)
{
gtk_widget_hide(find_shell);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static gboolean
find_keypress_cb(GtkWidget *w, GdkEvent *event, gpointer user_data)
{
static const char regexp_metachars[] = "[]*+()^$";
switch (event->key.keyval)
{
case GDK_Page_Up:
find_state_prev();
break;
case GDK_Page_Down:
find_state_next();
break;
default:
/*
* If the user types (rather than pastes) in characters
* which indicate he's probably going to use a regexp,
* make it a regexp for him. He can always set it back.
*/
if (event->key.length == 1 &&
strchr(regexp_metachars, event->key.string[0]) != 0)
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(type_radio[FT_REGEXP]), TRUE);
break;
}
return TRUE; /* keep processing */
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static void
find_wrap_cb(GtkWidget *w, void *user_data)
{
wrap = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
}
static void
find_direction_cb(GtkWidget *w, void *user_data)
{
direction = (gtk_toggle_button_get_active(
GTK_TOGGLE_BUTTON(dirn_radio[FD_FORWARDS])) ?
FD_FORWARDS : FD_BACKWARDS);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
static void
create_find_shell(void)
{
GtkWidget *label;
GtkWidget *hbox;
GtkWidget *radio;
GtkWidget *check;
GtkWidget *entry;
find_shell = ui_create_dialog(toplevel, _("Maketool: Find"));
ui_set_help_tag(find_shell, "find-window");
gtk_container_border_width(GTK_CONTAINER(GTK_DIALOG(find_shell)->vbox), SPACING);
label = gtk_label_new(_("Use PageUp/PageDown to recall earlier searches."));
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(find_shell)->vbox), label, FALSE, TRUE, 0);
gtk_widget_show(label);
hbox = gtk_hbox_new(FALSE, 0);
/*gtk_container_border_width(GTK_CONTAINER(hbox), SPACING);*/
gtk_box_set_spacing(GTK_BOX(hbox), 4);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(find_shell)->vbox), hbox, TRUE, TRUE, 0);
gtk_widget_show(hbox);
label = gtk_label_new(_("Search for:")); /* TODO: better label */
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_RIGHT);
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
gtk_widget_show(label);
entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
gtk_widget_show(entry);
gtk_signal_connect(GTK_OBJECT(entry), "activate",
GTK_SIGNAL_FUNC(find_find_cb), 0);
gtk_signal_connect(GTK_OBJECT(entry), "key-press-event",
GTK_SIGNAL_FUNC(find_keypress_cb), 0);
string_entry = entry;
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(find_shell)->vbox), hbox, TRUE, TRUE, 0);
gtk_widget_show(hbox);
radio = gtk_radio_button_new_with_label_from_widget(0, _("Literal"));
gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
gtk_widget_show(radio);
type_radio[FT_LITERAL] = radio;
#if FINDCASE
radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Case Sensitive Literal"));
gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
gtk_widget_show(radio);
type_radio[FT_CASE_LITERAL] = radio;
#endif
radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Regular Expression"));
gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
gtk_widget_show(radio);
type_radio[FT_REGEXP] = radio;
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(find_shell)->vbox), hbox, TRUE, TRUE, 0);
gtk_widget_show(hbox);
radio = gtk_radio_button_new_with_label_from_widget(0, _("Search Forwards"));
gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), direction == FD_FORWARDS);
gtk_signal_connect(GTK_OBJECT(radio), "toggled",
GTK_SIGNAL_FUNC(find_direction_cb), 0);
gtk_widget_show(radio);
dirn_radio[FD_FORWARDS] = radio;
radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Search Backwards"));
gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), direction == FD_BACKWARDS);
gtk_signal_connect(GTK_OBJECT(radio), "toggled",
GTK_SIGNAL_FUNC(find_direction_cb), 0);
gtk_widget_show(radio);
dirn_radio[FD_BACKWARDS] = radio;
check = gtk_check_button_new_with_label(_("Wrap"));
gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, TRUE, 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), wrap);
gtk_signal_connect(GTK_OBJECT(check), "toggled",
GTK_SIGNAL_FUNC(find_wrap_cb), 0);
gtk_widget_show(check);
ui_dialog_create_button(find_shell, _("Find"),
find_find_cb, (gpointer)0);
ui_dialog_create_button(find_shell, _("Close"),
find_close_cb, (gpointer)0);
}
/* TODO: grey out Find button in Find dialog when no string entered
* or log is empty */
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
void
edit_find_cb(GtkWidget *w, gpointer data)
{
if (find_shell == 0)
create_find_shell();
gtk_widget_grab_focus(string_entry);
gtk_entry_select_region(GTK_ENTRY(string_entry), 0, -1);
gtk_widget_show(find_shell);
}
void
edit_find_again_cb(GtkWidget *w, gpointer data)
{
do_find();
}
gboolean
find_can_find_again(void)
{
return (find_state_stack != 0);
}
/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
/*END*/
syntax highlighted by Code2HTML, v. 0.9.1