/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2001-2003 CodeFactory AB
 * Copyright (C) 2001-2003 Mikael Hallendal <micke@imendio.com>
 * Copyright (C) 2005-2006 Imendio AB
 *
 * 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 <config.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <gconf/gconf-client.h>

#include "dh-marshal.h"
#include "dh-keyword-model.h"
#include "dh-search.h"
#include "dh-preferences.h"
#include "dh-base.h"

#define d(x)

typedef enum _DhSearchSource DhSearchSource;

enum _DhSearchSource {
	SEARCH_API    = 0,
	SEARCH_ENTRY
};

struct _DhSearchPriv {
	DhKeywordModel *model;

	DhLink         *selected_link;
	
	GtkWidget      *advanced_box;

	GtkWidget      *book;
	GtkWidget      *page;
	GtkWidget      *entry;
	GtkWidget      *hitlist;

	GCompletion    *completion;

	guint           idle_complete;
	guint           idle_filter;

	gboolean        first;

	guint           advanced_options_id;
	
	GString        *book_str;
	GString        *page_str;
	GString        *entry_str;

	DhSearchSource  search_source;
};


static void  search_init                       (DhSearch         *search);
static void  search_class_init                 (DhSearchClass    *klass);
static void  search_finalize                   (GObject          *object);
static void  search_advanced_options_setup     (DhSearch         *search);
static void  search_advanced_options_notify_cb (GConfClient      *client,
						guint             cnxn_id,
						GConfEntry       *entry,
						gpointer          user_data);
static void  search_selection_changed_cb       (GtkTreeSelection *selection,
						DhSearch         *content);
static gboolean search_tree_button_press_cb    (GtkTreeView      *view,
						GdkEventButton   *event,
						DhSearch         *search);
static gboolean search_entry_key_press_event_cb (GtkEntry        *entry,
						 GdkEventKey     *event,
						 DhSearch        *search);
static void  search_entry_changed_cb           (GtkEntry         *entry,
						DhSearch         *search);
static void  search_entry_activated_cb         (GtkEntry         *entry,
						DhSearch         *search);
static void  search_entry_text_inserted_cb     (GtkEntry         *entry,
						const gchar      *text,
						gint              length,
						gint             *position,
						DhSearch         *search);
static gboolean search_complete_idle           (DhSearch         *search);
static gboolean search_filter_idle             (DhSearch         *search);
static gchar *  search_complete_func           (DhLink           *link);
static gchar *  search_get_search_string       (DhSearch         *search);

enum {
        LINK_SELECTED,
        LAST_SIGNAL
};

static GtkVBox *parent_class;
static gint     signals[LAST_SIGNAL] = { 0 };

GType
dh_search_get_type (void)
{
        static GType type = 0;

        if (!type) {
                static const GTypeInfo info =
                        {
                                sizeof (DhSearchClass),
                                NULL,
                                NULL,
                                (GClassInitFunc) search_class_init,
                                NULL,
                                NULL,
                                sizeof (DhSearch),
                                0,
                                (GInstanceInitFunc) search_init,
                        };
                
                type = g_type_register_static (GTK_TYPE_VBOX,
					       "DhSearch", 
					       &info, 0);
        }
        
        return type;
}

static void
search_class_init (DhSearchClass *klass)
{
        GObjectClass   *object_class;
        GtkWidgetClass *widget_class;
	
        parent_class = g_type_class_peek_parent (klass);

        object_class = (GObjectClass *) klass;
        widget_class = (GtkWidgetClass *) klass;
	
	object_class->finalize = search_finalize;

        signals[LINK_SELECTED] =
                g_signal_new ("link_selected",
			      G_TYPE_FROM_CLASS (klass),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (DhSearchClass, link_selected),
			      NULL, NULL,
			      dh_marshal_VOID__POINTER,
			      G_TYPE_NONE,
			      1, G_TYPE_POINTER);
}

static void
search_init (DhSearch *search)
{
	DhSearchPriv *priv;

	priv = g_new0 (DhSearchPriv, 1);
	search->priv = priv;
	
	priv->book_str = g_string_new ("");
	priv->page_str = g_string_new ("");
	priv->entry_str = g_string_new ("");

	priv->idle_complete = 0;
	priv->idle_filter   = 0;

	priv->completion = 
		g_completion_new ((GCompletionFunc) search_complete_func);

	priv->hitlist = gtk_tree_view_new ();
	priv->model   = dh_keyword_model_new ();

	gtk_tree_view_set_model (GTK_TREE_VIEW (priv->hitlist),
				 GTK_TREE_MODEL (priv->model));

	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (priv->hitlist), FALSE);

	gtk_box_set_spacing (GTK_BOX (search), 2);
}

static void
search_finalize (GObject *object)
{
	DhSearchPriv *priv;
	GConfClient  *gconf_client;
	
	priv = DH_SEARCH (object)->priv;

	g_string_free (priv->book_str, TRUE);
	g_string_free (priv->page_str, TRUE);
	g_string_free (priv->entry_str, TRUE);

	g_completion_free (priv->completion);

	gconf_client = dh_base_get_gconf_client (dh_base_get ());	
	gconf_client_notify_remove (gconf_client, priv->advanced_options_id);

	G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
search_advanced_options_setup (DhSearch *search)
{
	DhSearchPriv *priv;
	gboolean      advanced_options;
	GConfClient  *gconf_client;

	priv = search->priv;

	gconf_client = dh_base_get_gconf_client (dh_base_get ());
	
	advanced_options = gconf_client_get_bool (gconf_client,
						  GCONF_ADVANCED_OPTIONS,
						  NULL);
	if (advanced_options) {
		gtk_widget_show (priv->advanced_box);

		g_signal_handlers_block_by_func (priv->book, search_entry_changed_cb, search);
		g_signal_handlers_block_by_func (priv->page, search_entry_changed_cb, search);

		gtk_entry_set_text (GTK_ENTRY (priv->book), 
				    priv->book_str->len > 5 ? 
				    priv->book_str->str + 5 : "");
		gtk_entry_set_text (GTK_ENTRY (priv->page), 
				    priv->page_str->len > 5 ? 
				    priv->page_str->str + 5 : "");

		g_signal_handlers_unblock_by_func (priv->book, search_entry_changed_cb, search);
		g_signal_handlers_unblock_by_func (priv->page, search_entry_changed_cb, search);

	} else {
		gtk_widget_hide (priv->advanced_box);
	}		
}

static void
search_advanced_options_notify_cb (GConfClient     *client,
				   guint            cnxn_id,
				   GConfEntry      *entry,
				   gpointer         user_data)
{
	DhSearch     *search;
	DhSearchPriv *priv;

	search = DH_SEARCH (user_data);
	priv = search->priv;

	search_advanced_options_setup (search);

	/* Simulate a new search to update. */
	search_entry_activated_cb (GTK_ENTRY (priv->entry), search);
}

static void
search_selection_changed_cb (GtkTreeSelection *selection, DhSearch *search)
{
	DhSearchPriv *priv;
 	GtkTreeIter   iter;
	
	priv = search->priv;

	if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
		DhLink *link;
		
		gtk_tree_model_get (GTK_TREE_MODEL (priv->model), &iter,
				    DH_KEYWORD_MODEL_COL_LINK, &link,
				    -1);

		if (link != priv->selected_link) {
			priv->selected_link = link;
		
			d(g_print ("Emiting signal with link to: %s (%s)\n",
				   link->name, link->uri));
		
			g_signal_emit (search, signals[LINK_SELECTED], 0, link);
		}
	}
}

/* Make it possible to jump back to the currently selected item, useful when the
 * html view has been scrolled away.
 */
static gboolean
search_tree_button_press_cb (GtkTreeView    *view,
			     GdkEventButton *event,
			     DhSearch       *search)
{
	GtkTreePath  *path;
	GtkTreeIter   iter;
	DhSearchPriv *priv;
	DhLink       *link;

	priv = search->priv;
	
	gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path,
				       NULL, NULL, NULL);
	if (!path) {
		return FALSE;
	}
	
	gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->model), &iter, path);
	gtk_tree_path_free (path);
	
	gtk_tree_model_get (GTK_TREE_MODEL (priv->model),
			    &iter,
			    DH_KEYWORD_MODEL_COL_LINK, &link,
			    -1);

	priv->selected_link = link;
	
	g_signal_emit (search, signals[LINK_SELECTED], 0, link);
	
	/* Always return FALSE so the tree view gets the event and can update
	 * the selection etc.
	 */
	return FALSE;
}

static gboolean
search_entry_key_press_event_cb (GtkEntry    *entry,
				 GdkEventKey *event,
				 DhSearch    *search)
{
	DhSearchPriv *priv;
	
	priv = search->priv;
	
	if (event->keyval == GDK_Tab) {
		if (event->state & GDK_CONTROL_MASK) {
			gtk_widget_grab_focus (priv->hitlist);
		} else {
			gtk_editable_set_position (GTK_EDITABLE (entry), -1);
			gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1);
		}
		return TRUE;
	}

	if (event->keyval == GDK_Return ||
	    event->keyval == GDK_KP_Enter) {
		GtkTreeIter  iter;
		DhLink      *link;
		gchar       *name;
		
		/* Get the first entry found. */
		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->model), &iter)) {
			gtk_tree_model_get (GTK_TREE_MODEL (priv->model),
					    &iter,
					    DH_KEYWORD_MODEL_COL_LINK, &link,
					    DH_KEYWORD_MODEL_COL_NAME, &name,
					    -1);
			
			gtk_entry_set_text (GTK_ENTRY (entry), name);
			g_free (name);
			
			gtk_editable_set_position (GTK_EDITABLE (entry), -1);
			gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1);
			
			g_signal_emit (search, signals[LINK_SELECTED], 0, link);
			
			return TRUE;
		}
	}
	
	return FALSE;
}

static gchar *
search_get_search_string (DhSearch *search)
{
	DhSearchPriv *priv;
	GString      *string;

	priv = search->priv;

	string = g_string_new ("");

	if (GTK_WIDGET_VISIBLE (priv->advanced_box) ||
	    priv->search_source == SEARCH_API) {

		if (priv->book_str->len > 0) {
			g_string_append (string, priv->book_str->str);
			g_string_append (string, " ");
		}
		
		if (priv->page_str->len > 0) {
			g_string_append (string, priv->page_str->str);
			g_string_append (string, " ");
		}
	}

	if (priv->entry_str->len > 0) {
		g_string_append (string, priv->entry_str->str);
		g_string_append (string, " ");
	}
	
	return g_string_free (string, FALSE);
}

static void
search_update_string (DhSearch *search, GtkEntry *entry)
{
	const gchar  *str = gtk_entry_get_text (entry);
	DhSearchPriv *priv;

	priv = search->priv;

	priv->search_source = SEARCH_ENTRY;
	
	if (GTK_WIDGET (entry) == priv->book) {
		if (str && str[0]) {
			g_string_printf (priv->book_str, "book:%s", str);
		} else {
			g_string_set_size (priv->book_str, 0);
		}
	} else if (GTK_WIDGET (entry) == priv->page) {
		if (str && str[0]) {
			g_string_printf (priv->page_str, "page:%s", str);
		} else {
			g_string_set_size (priv->page_str, 0);
		}
	} else {
		if (GTK_WIDGET_VISIBLE (priv->advanced_box) == FALSE) {
			g_string_set_size (priv->book_str, 0);
			g_string_set_size (priv->page_str, 0);
		}

		g_string_set_size (priv->entry_str, 0);
		if (str && str[0]) {
			g_string_append (priv->entry_str, str);
		}
	}
}

static void
search_entry_changed_cb (GtkEntry *entry, DhSearch *search)
{
	DhSearchPriv *priv;
	
	priv = search->priv;
 
	d(g_print ("Entry changed\n"));

	search_update_string (search, entry);

	if (!priv->idle_filter) {
		priv->idle_filter =
			g_idle_add ((GSourceFunc) search_filter_idle, search);
	}
}

static void
search_entry_activated_cb (GtkEntry *entry, DhSearch *search)
{
	DhSearchPriv *priv;
	DhLink       *link;
	gchar        *str;
	
	priv = search->priv;
	
	str = search_get_search_string (search);
	link = dh_keyword_model_filter (priv->model, str);

	g_free (str);
}

static void
search_entry_text_inserted_cb (GtkEntry    *entry,
			       const gchar *text,
			       gint         length,
			       gint        *position,
			       DhSearch    *search)
{
	DhSearchPriv *priv;
	
	priv = search->priv;
	
	if (!priv->idle_complete) {
		priv->idle_complete = 
			g_idle_add ((GSourceFunc) search_complete_idle, 
				    search);
	}
}

static gboolean
search_complete_idle (DhSearch *search)
{
	DhSearchPriv *priv;
	const gchar  *text;
	gchar        *completed = NULL;
	GList        *list;
	gint          text_length;
	
	priv = search->priv;
	
	text = gtk_entry_get_text (GTK_ENTRY (priv->entry));

	list = g_completion_complete (priv->completion,
				      (gchar *)text,
				      &completed);

	if (completed) {
		text_length = strlen (text);
		
		gtk_entry_set_text (GTK_ENTRY (priv->entry), completed);
 		gtk_editable_set_position (GTK_EDITABLE (priv->entry),
 					   text_length);
		gtk_editable_select_region (GTK_EDITABLE (priv->entry),
					    text_length, -1);
	}
	
	priv->idle_complete = 0;

	return FALSE;
}

static gboolean
search_filter_idle (DhSearch *search)
{
	DhSearchPriv *priv;
	gchar        *str;
	DhLink       *link;
	
	priv = search->priv;

	d(g_print ("Filter idle\n"));
	
	str = search_get_search_string (search);
	link = dh_keyword_model_filter (priv->model, str);
	g_free (str);

	priv->idle_filter = 0;

	if (link) {
		g_signal_emit (search, signals[LINK_SELECTED], 0, link);
	}

	return FALSE;
}

static gchar *
search_complete_func (DhLink *link)
{
	return link->name;
}

static void
search_cell_data_func (GtkTreeViewColumn *tree_column,
		       GtkCellRenderer   *cell,
		       GtkTreeModel      *tree_model,
		       GtkTreeIter       *iter,
		       gpointer           data)
{
	DhSearch     *search;
	DhSearchPriv *priv;
	gchar        *name;
	gboolean      is_deprecated;
	GdkColor     *color;

	search = data;
	priv = search->priv;

	gtk_tree_model_get (tree_model, iter,
			    DH_KEYWORD_MODEL_COL_NAME, &name,
			    DH_KEYWORD_MODEL_COL_IS_DEPRECATED, &is_deprecated,
			    -1);

	if (is_deprecated) {
		color = &GTK_WIDGET (search)->style->text_aa[GTK_STATE_NORMAL];
	} else {
		color = NULL;
	}
	
	g_object_set (cell,
		      "text", name,
		      "foreground-gdk", color,
		      NULL);

	g_free (name);
}

GtkWidget *
dh_search_new (GList *keywords)
{
	DhSearch         *search;
	DhSearchPriv     *priv;
	GtkTreeSelection *selection;
        GtkWidget        *list_sw;
	GtkWidget        *frame;
	GtkWidget        *hbox;
	GtkWidget        *book_label, *page_label;
	GtkSizeGroup     *group;
	GtkCellRenderer  *cell;
	GConfClient      *gconf_client;
		
	search = g_object_new (DH_TYPE_SEARCH, NULL);

	priv = search->priv;

	gtk_container_set_border_width (GTK_CONTAINER (search), 2);

	/* Setup the book box */
	priv->book = gtk_entry_new ();
	g_signal_connect (priv->book, "changed", 
			  G_CALLBACK (search_entry_changed_cb),
			  search);
	g_signal_connect (priv->book, "activate",
			  G_CALLBACK (search_entry_activated_cb),
			  search);

	book_label = gtk_label_new_with_mnemonic (_("_Book:"));
	gtk_label_set_mnemonic_widget (GTK_LABEL (book_label), priv->book);

	priv->advanced_box = gtk_vbox_new (FALSE, 2);
 	gtk_box_pack_start (GTK_BOX (search), priv->advanced_box, FALSE, FALSE, 0);
	
	hbox = gtk_hbox_new (FALSE, 6);
	gtk_box_pack_start (GTK_BOX (hbox), book_label, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), priv->book, TRUE, TRUE, 0);
 	gtk_box_pack_start (GTK_BOX (priv->advanced_box), hbox, FALSE, FALSE, 0);

	/* Setup the page box */
	priv->page = gtk_entry_new ();
	g_signal_connect (priv->page, "changed", 
			  G_CALLBACK (search_entry_changed_cb),
			  search);
	g_signal_connect (priv->page, "activate",
			  G_CALLBACK (search_entry_activated_cb),
			  search);

	page_label = gtk_label_new_with_mnemonic (_("_Page:"));
	gtk_label_set_mnemonic_widget (GTK_LABEL (page_label), priv->page);

	hbox = gtk_hbox_new (FALSE, 6);
	gtk_box_pack_start (GTK_BOX (hbox), page_label, FALSE, FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), priv->page, TRUE, TRUE, 0);
 	gtk_box_pack_start (GTK_BOX (priv->advanced_box), hbox, FALSE, FALSE, 0);
	
	/* Align the labels */
	group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
	gtk_size_group_add_widget (group, book_label);
	gtk_size_group_add_widget (group, page_label);
	g_object_unref (G_OBJECT (group));

	gtk_widget_show_all (priv->advanced_box);
	gtk_widget_set_no_show_all (priv->advanced_box, TRUE);

	/* Setup the keyword box */
	priv->entry = gtk_entry_new ();
	g_signal_connect (priv->entry, "key_press_event",
			  G_CALLBACK (search_entry_key_press_event_cb),
			  search);

	g_signal_connect (priv->hitlist, "button_press_event",
			  G_CALLBACK (search_tree_button_press_cb),
			  search);

	g_signal_connect (priv->entry, "changed", 
			  G_CALLBACK (search_entry_changed_cb),
			  search);

	g_signal_connect (priv->entry, "activate",
			  G_CALLBACK (search_entry_activated_cb),
			  search);
	
	g_signal_connect (priv->entry, "insert_text",
			  G_CALLBACK (search_entry_text_inserted_cb),
			  search);

	gtk_box_pack_start (GTK_BOX (search), priv->entry, FALSE, FALSE, 0);

	/* Setup the hitlist */
	frame = gtk_frame_new (NULL);
	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
	
        list_sw = gtk_scrolled_window_new (NULL, NULL);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (list_sw),
                                        GTK_POLICY_NEVER, 
                                        GTK_POLICY_AUTOMATIC);

	gtk_container_add (GTK_CONTAINER (frame), list_sw);

	cell = gtk_cell_renderer_text_new ();
	g_object_set (cell,
		      "ellipsize", PANGO_ELLIPSIZE_END,
		      NULL);

	gtk_tree_view_insert_column_with_data_func (
		GTK_TREE_VIEW (priv->hitlist),
		-1,
		NULL, 
		cell,
		search_cell_data_func,
		search, NULL);
	
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->hitlist),
					   FALSE);
	gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->hitlist),
					 DH_KEYWORD_MODEL_COL_NAME);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->hitlist));

	g_signal_connect (selection, "changed",
			  G_CALLBACK (search_selection_changed_cb),
			  search);
	
	gtk_container_add (GTK_CONTAINER (list_sw), priv->hitlist);

	gtk_box_pack_end_defaults (GTK_BOX (search), frame);

	g_completion_add_items (priv->completion, keywords);
	dh_keyword_model_set_words (priv->model, keywords);

	gtk_widget_show_all (GTK_WIDGET (search));
	
	gconf_client = dh_base_get_gconf_client (dh_base_get ());
	priv->advanced_options_id = gconf_client_notify_add (gconf_client,
							     GCONF_ADVANCED_OPTIONS,
							     search_advanced_options_notify_cb,
							     search, NULL, NULL);

	search_advanced_options_setup (search);
	
	return GTK_WIDGET (search);
}

void
dh_search_set_search_string (DhSearch *search, const gchar *str)
{
	DhSearchPriv  *priv;
	gchar        **split, **leftover, *lower;
	gchar         *string = NULL;
	gint           i;

	g_return_if_fail (DH_IS_SEARCH (search));

	priv = search->priv;

	priv->search_source = SEARCH_API;

	g_string_set_size (priv->book_str, 0);
	g_string_set_size (priv->page_str, 0);
	g_string_set_size (priv->entry_str, 0);

	g_signal_handlers_block_by_func (priv->book, search_entry_changed_cb, search);
	g_signal_handlers_block_by_func (priv->page, search_entry_changed_cb, search);
	g_signal_handlers_block_by_func (priv->entry, search_entry_changed_cb, search);

	if ((leftover = split = g_strsplit (str, " ", -1)) != NULL) {

		for (i = 0; split[i] != NULL; i++) {

			lower = g_ascii_strdown (split[i], -1);
			
			/* Determine if there was a book or page specification
			 */
			if (!strncmp (lower, "book:", 5)) {
				g_string_append (priv->book_str, split[i]);
				leftover++;
			} else if (!strncmp (lower, "page:", 5)) {
				g_string_append (priv->page_str, split[i]);
				leftover++;
			} else {
				/* No more specifications */
				break;
			}
			
			g_free (lower);
		}

		/* Collect the search string */
		string = NULL;
		for (i = 0; leftover[i] != NULL; i++) {
			if (string == NULL) {
				string = g_strdup (leftover[i]);
			} else { 
				lower = g_strdup_printf ("%s %s", string, leftover[i]);
				g_free (string);
				string = lower;
			}
		}

		g_strfreev (split);

		if (string) {
			g_string_append (priv->entry_str, string);
		}

		if (string) {
			g_free (string);
		}

	} else if (str) {
		g_string_append (priv->entry_str, str);
	}
	
	gtk_entry_set_text (GTK_ENTRY (priv->entry), 
			    priv->entry_str->str);

	if (GTK_WIDGET_VISIBLE (priv->advanced_box)) {
		gtk_entry_set_text (GTK_ENTRY (priv->book), 
				    priv->book_str->len > 5 ? 
				    priv->book_str->str + 5 : "");
		gtk_entry_set_text (GTK_ENTRY (priv->page), 
				    priv->page_str->len > 5 ? 
				    priv->page_str->str + 5 : "");
	}

	gtk_editable_set_position (GTK_EDITABLE (priv->entry), -1);
	gtk_editable_select_region (GTK_EDITABLE (priv->entry), -1, -1);

	g_signal_handlers_unblock_by_func (priv->book, search_entry_changed_cb, search);
	g_signal_handlers_unblock_by_func (priv->page, search_entry_changed_cb, search);
	g_signal_handlers_unblock_by_func (priv->entry, search_entry_changed_cb, search);

	if (!priv->idle_filter) {
		priv->idle_filter =
			g_idle_add ((GSourceFunc) search_filter_idle, search);
	}
}

void
dh_search_grab_focus (DhSearch *search)
{
	DhSearchPriv *priv;
	
	g_return_if_fail (DH_IS_SEARCH (search));

	priv = search->priv;

	gtk_widget_grab_focus (priv->entry);
}



syntax highlighted by Code2HTML, v. 0.9.1