aboutsummaryrefslogblamecommitdiffstats
path: root/src/bookmarks/ephy-keywords-entry.c
blob: 5a6accf0fcd11aa7018121049cc60afeff0b9dc6 (plain) (tree)




















                                                                              
                       


                           


























































                                                                                                
                              
                                
                                
                             
                               
                     





                                                                  
                                             













                                                                    
                                           







                                                                   
                                                   
 


                                                                                       
 

                                                      
                                             
                                                     



                                                                                             
                                     

















































































































































                                                                                      
/*
 *  Copyright (C) 2002  Marco Pesenti Gritti
 *
 *  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, 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 "ephy-keywords-entry.h"
#include "ephy-marshal.h"
#include "ephy-gobject-misc.h"
#include "ephy-debug.h"

#include <gdk/gdkkeysyms.h>

/**
 * Private data
 */
struct _EphyKeywordsEntryPrivate
{
    EphyBookmarks *bookmarks;
};

/**
 * Private functions, only availble from this file
 */
static void     ephy_keywords_entry_class_init      (EphyKeywordsEntryClass *klass);
static void     ephy_keywords_entry_init        (EphyKeywordsEntry *w);
static void     ephy_keywords_entry_finalize_impl   (GObject *o);
static gint     ephy_keywords_entry_key_press       (GtkWidget *widget,
                                 GdkEventKey *event);

enum
{
    KEYWORDS_CHANGED,
    LAST_SIGNAL
};

static GObjectClass *parent_class = NULL;

static guint keywords_entry_signals[LAST_SIGNAL] = { 0 };

MAKE_GET_TYPE (ephy_keywords_entry, "EphyKeywordsEntry", EphyKeywordsEntry,
           ephy_keywords_entry_class_init,
           ephy_keywords_entry_init, GTK_TYPE_ENTRY);

static void
ephy_keywords_entry_class_init (EphyKeywordsEntryClass *class)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (class);
    GtkWidgetClass *widget_class;

    parent_class = g_type_class_peek_parent (class);
    widget_class = (GtkWidgetClass*) class;

    gobject_class->finalize = ephy_keywords_entry_finalize_impl;

    widget_class->key_press_event = ephy_keywords_entry_key_press;

    keywords_entry_signals[KEYWORDS_CHANGED] =
                g_signal_new ("keywords_changed",
                              G_OBJECT_CLASS_TYPE (gobject_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (EphyKeywordsEntryClass, keywords_changed),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE,
                              0);
}

static void
try_to_expand_keyword (GtkEditable *editable)
{
    char *entry_text;
    const char *user_text;
    const char *expand_text;
    const char *insert_text;
    int user_text_length;
    int keyword_offset = 0;
    int position;
    EphyKeywordsEntry *entry = EPHY_KEYWORDS_ENTRY (editable);
    EphyNode *node;

    entry_text = gtk_editable_get_chars (editable, 0, -1);
    g_return_if_fail (entry_text != NULL);

    LOG ("Entry text \"%s\"", entry_text)

    user_text = g_utf8_strrchr (entry_text, -1, ' ');

    if (user_text)
    {
        user_text = g_utf8_find_next_char (user_text, NULL);
        keyword_offset = g_utf8_pointer_to_offset
            (entry_text, user_text);
    }
    else
    {
        user_text = entry_text;
    }

    LOG ("User text \"%s\"", user_text)

    node = ephy_bookmarks_find_keyword (entry->priv->bookmarks,
                        user_text, TRUE);
    if (node)
    {
        expand_text = ephy_node_get_property_string
            (node, EPHY_NODE_KEYWORD_PROP_NAME);

        LOG ("Expand text %s", expand_text)

        user_text_length = g_utf8_strlen (user_text, -1);

        insert_text = g_utf8_offset_to_pointer (expand_text, user_text_length);

        gtk_editable_insert_text (editable,
                      insert_text,
                      -1,
                      &position);
        gtk_editable_select_region (editable, user_text_length + keyword_offset, -1);
    }
    else
    {
        LOG ("No expansion.")
    }

    g_free (entry_text);
}

/* Until we have a more elegant solution, this is how we figure out if
 * the GtkEntry inserted characters, assuming that the return value is
 * TRUE indicating that the GtkEntry consumed the key event for some
 * reason. This is a clone of code from GtkEntry.
 */
static gboolean
entry_would_have_inserted_characters (const GdkEventKey *event)
{
    switch (event->keyval) {
    case GDK_BackSpace:
    case GDK_Clear:
    case GDK_Insert:
    case GDK_Delete:
    case GDK_Home:
    case GDK_End:
    case GDK_Left:
    case GDK_Right:
    case GDK_Return:
        return FALSE;
    default:
        if (event->keyval >= 0x20 && event->keyval <= 0xFF) {
            if ((event->state & GDK_CONTROL_MASK) != 0) {
                return FALSE;
            }
            if ((event->state & GDK_MOD1_MASK) != 0) {
                return FALSE;
            }
        }
        return event->length > 0;
    }
}

static int
get_editable_number_of_chars (GtkEditable *editable)
{
    char *text;
    int length;

    text = gtk_editable_get_chars (editable, 0, -1);
    length = g_utf8_strlen (text, -1);
    g_free (text);
    return length;
}

static void
set_position_and_selection_to_end (GtkEditable *editable)
{
    int end;

    end = get_editable_number_of_chars (editable);
    gtk_editable_select_region (editable, end, end);
    gtk_editable_set_position (editable, end);
}

static gboolean
position_and_selection_are_at_end (GtkEditable *editable)
{
    int end;
    int start_sel, end_sel;

    end = get_editable_number_of_chars (editable);
    if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel))
    {
        if (start_sel != end || end_sel != end)
        {
            return FALSE;
        }
    }
    return gtk_editable_get_position (editable) == end;
}

static gint
ephy_keywords_entry_key_press (GtkWidget *widget,
                   GdkEventKey *event)
{
    GtkEditable *editable;
    GdkEventKey *keyevent;
    EphyKeywordsEntry *entry;
    gboolean result;

    entry = EPHY_KEYWORDS_ENTRY (widget);
    editable = GTK_EDITABLE (entry);
    keyevent = (GdkEventKey *)event;

    /* After typing the right arrow key we move the selection to
     * the end, if we have a valid selection - since this is most
     * likely an auto-completion. We ignore shift / control since
     * they can validly be used to extend the selection.
     */
    if ((keyevent->keyval == GDK_Right || keyevent->keyval == GDK_End) &&
        !(keyevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) &&
        gtk_editable_get_selection_bounds (editable, NULL, NULL))
    {
        set_position_and_selection_to_end (editable);
    }

    result = GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);

    /* Only do expanding when we are typing at the end of the
     * text.
     */
    if (entry_would_have_inserted_characters (event)
        && position_and_selection_are_at_end (editable))
    {
        try_to_expand_keyword (editable);
    }

    g_signal_emit (G_OBJECT (entry), keywords_entry_signals[KEYWORDS_CHANGED], 0);

    return result;
}

static void
ephy_keywords_entry_init (EphyKeywordsEntry *w)
{
    w->priv = g_new0 (EphyKeywordsEntryPrivate, 1);
    w->priv->bookmarks = NULL;
}

static void
ephy_keywords_entry_finalize_impl (GObject *o)
{
    EphyKeywordsEntry *w = EPHY_KEYWORDS_ENTRY (o);
    EphyKeywordsEntryPrivate *p = w->priv;

    g_free (p);
    G_OBJECT_CLASS (parent_class)->finalize (o);
}

GtkWidget *
ephy_keywords_entry_new (void)
{
    return GTK_WIDGET (g_object_new (EPHY_TYPE_LOCATION_ENTRY, NULL));
}

void
ephy_keywords_entry_set_bookmarks (EphyKeywordsEntry *w,
                   EphyBookmarks *bookmarks)
{
    w->priv->bookmarks = bookmarks;
}