/*
* Copyright (C) 2002-2004 Marco Pesenti Gritti <mpeseng@tin.it>
* Copyright (C) 2005, 2006 Peter Harvey <pah06@uow.edu.au>
*
* 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 "ephy-topics-entry.h"
#include "ephy-nodes-cover.h"
#include "ephy-node-common.h"
#include "ephy-bookmarks.h"
#include "ephy-debug.h"
#include <glib/gi18n.h>
#include <gtk/gtktreeselection.h>
#include <gtk/gtkentrycompletion.h>
#include <string.h>
static void ephy_topics_entry_class_init (EphyTopicsEntryClass *klass);
static void ephy_topics_entry_init (EphyTopicsEntry *editor);
#define EPHY_TOPICS_ENTRY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_TOPICS_ENTRY, EphyTopicsEntryPrivate))
struct _EphyTopicsEntryPrivate
{
EphyBookmarks *bookmarks;
EphyNode *bookmark;
GtkListStore *store;
GtkEntryCompletion *completion;
gboolean update_keywords;
char *action;
};
enum
{
PROP_0,
PROP_BOOKMARKS,
PROP_BOOKMARK
};
static GObjectClass *parent_class = NULL;
GType
ephy_topics_entry_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0))
{
static const GTypeInfo our_info =
{
sizeof (EphyTopicsEntryClass),
NULL,
NULL,
(GClassInitFunc) ephy_topics_entry_class_init,
NULL,
NULL,
sizeof (EphyTopicsEntry),
0,
(GInstanceInitFunc) ephy_topics_entry_init
};
type = g_type_register_static (GTK_TYPE_ENTRY,
"EphyTopicsEntry",
&our_info, 0);
}
return type;
}
static void
update_widget (EphyTopicsEntry *entry)
{
EphyNode *node;
GPtrArray *children, *topics;
GtkTreeIter iter;
gint i, priority;
const char *title;
char *tmp1, *tmp2;
gboolean update_text;
entry->priv->update_keywords = FALSE;
update_text = !GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (entry));
gtk_entry_set_completion (GTK_ENTRY (entry), NULL);
gtk_list_store_clear (entry->priv->store);
if (update_text) gtk_entry_set_text (GTK_ENTRY (entry), "");
node = ephy_bookmarks_get_keywords (entry->priv->bookmarks);
children = ephy_node_get_children (node);
topics = g_ptr_array_sized_new (children->len);
for (i = 0; i < children->len; i++)
{
node = g_ptr_array_index (children, i);
priority = ephy_node_get_property_int (node, EPHY_NODE_KEYWORD_PROP_PRIORITY);
if (priority != EPHY_NODE_NORMAL_PRIORITY)
continue;
g_ptr_array_add (topics, node);
}
g_ptr_array_sort (topics, ephy_bookmarks_compare_topic_pointers);
for (i = 0; i < topics->len; i++)
{
node = g_ptr_array_index (topics, i);
title = ephy_node_get_property_string (node, EPHY_NODE_KEYWORD_PROP_NAME);
if (ephy_node_has_child (node, entry->priv->bookmark))
{
if (update_text)
{
gtk_entry_append_text (GTK_ENTRY (entry), title);
gtk_entry_append_text (GTK_ENTRY (entry), "; ");
}
}
else
{
tmp1 = g_utf8_casefold (title, -1);
tmp2 = g_utf8_normalize (tmp1, -1, G_NORMALIZE_DEFAULT);
gtk_list_store_append (entry->priv->store, &iter);
gtk_list_store_set (entry->priv->store, &iter, 0, title, 1, tmp2, -1);
g_free (tmp2);
g_free (tmp1);
}
}
g_ptr_array_free (topics, TRUE);
gtk_entry_set_completion (GTK_ENTRY (entry), entry->priv->completion);
if (update_text) gtk_editable_set_position (GTK_EDITABLE (entry), -1);
entry->priv->update_keywords = TRUE;
}
static void
update_action (EphyTopicsEntry *entry)
{
GtkEditable *editable = GTK_EDITABLE (entry);
const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
char *key;
gint start, end;
if(entry->priv->action)
{
gtk_entry_completion_delete_action (entry->priv->completion, 0);
g_free (entry->priv->action);
entry->priv->action = 0;
}
/* Find the start and end locations */
start = gtk_editable_get_position (editable);
while (start > 0 && text[start-1] != ';')
{
start--;
}
if(start > 0 && text[start-1] == ';' && text[start] == ' ')
{
start++;
}
end = start;
while (text[end] && text[end] != ';')
{
end++;
}
/* If no text to work with, exit */
key = g_strndup (text+start, end-start);
g_strstrip (key);
if (*key != 0 && ephy_bookmarks_find_keyword (entry->priv->bookmarks, key, FALSE) == NULL)
{
entry->priv->action = key;
key = g_strdup_printf (_("Create topic “%s”"), key);
gtk_entry_completion_insert_action_text (entry->priv->completion, 0, key);
}
g_free (key);
}
static void
update_keywords (EphyTopicsEntry *entry)
{
EphyNode *node;
GPtrArray *children;
const char *text;
char **split;
char *title, *tmp;
gint i, j, priority;
if(!entry->priv->update_keywords) return;
/* Get the list of strings input by the user */
text = gtk_entry_get_text (GTK_ENTRY (entry));
split = g_strsplit (text, ";", 0);
for (i=0; split[i]; i++)
{
g_strstrip (split[i]);
tmp = g_utf8_casefold (split[i], -1);
g_free (split[i]);
split[i] = g_utf8_normalize (tmp, -1, G_NORMALIZE_DEFAULT);
g_free (tmp);
}
/* Test each keyword and set/unset as appropriate */
node = ephy_bookmarks_get_keywords (entry->priv->bookmarks);
children = ephy_node_get_children (node);
for (i = 0; i < children->len; i++)
{
node = g_ptr_array_index (children, i);
priority = ephy_node_get_property_int (node, EPHY_NODE_KEYWORD_PROP_PRIORITY);
if (priority != EPHY_NODE_NORMAL_PRIORITY)
continue;
text = ephy_node_get_property_string (node, EPHY_NODE_KEYWORD_PROP_NAME);
tmp = g_utf8_casefold (text, -1);
title = g_utf8_normalize (tmp, -1, G_NORMALIZE_DEFAULT);
g_free (tmp);
for (j=0; split[j]; j++)
if (strcmp (title, split[j]) == 0)
break;
if (split[j])
{
split[j][0] = 0;
ephy_bookmarks_set_keyword (entry->priv->bookmarks, node,
entry->priv->bookmark);
}
else
{
ephy_bookmarks_unset_keyword (entry->priv->bookmarks, node,
entry->priv->bookmark);
}
g_free (title);
}
g_strfreev (split);
update_action (entry);
}
static void
insert_text (EphyTopicsEntry *entry,
const char *title)
{
GtkEditable *editable = GTK_EDITABLE (entry);
const char *text = gtk_entry_get_text (GTK_ENTRY (entry));
char *key;
gint start, end;
/* Find the start and end locations */
start = gtk_editable_get_position (editable);
while (start > 0 && text[start-1] != ';')
{
start--;
}
if(start > 0 && text[start-1] == ';' && text[start] == ' ')
{
start++;
}
end = start;
while (text[end] && text[end] != ';')
{
end++;
}
end = end;
if (text[end] == ';')
{
end++;
if (text[end] == ' ')
{
end++;
}
}
/* If we were provided with nothing, then we're meant to find a topic
* or create one from whatever has been entered */
if (title == NULL)
{
/* If no text to work with, exit */
key = g_strndup (text+start, end-start);
g_strstrip (key);
if (*key == 0)
{
g_free (key);
return;
}
}
/* Replace the text in the current position with the title */
gtk_editable_delete_text (editable, start, end);
gtk_editable_insert_text (editable, title, strlen(title), &start);
gtk_editable_insert_text (editable, "; ", 2, &start);
gtk_editable_set_position (editable, start);
update_widget(entry);
}
static void
action_cb (GtkEntryCompletion *completion,
gint index,
gpointer user_data)
{
EphyTopicsEntry *entry = EPHY_TOPICS_ENTRY (gtk_entry_completion_get_entry (completion));
char *action = g_strdup(entry->priv->action);
if (ephy_bookmarks_find_keyword (entry->priv->bookmarks, action, FALSE) == NULL)
{
ephy_bookmarks_add_keyword (entry->priv->bookmarks, action);
}
insert_text (entry, action);
g_free (action);
}
static gboolean
match_selected_cb (GtkEntryCompletion *completion,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
EphyTopicsEntry *entry = EPHY_TOPICS_ENTRY (gtk_entry_completion_get_entry (completion));
char *title;
gtk_tree_model_get (model, iter, 0, &title, -1);
insert_text (entry, title);
g_free (title);
return TRUE;
}
static void
tree_changed_cb (EphyBookmarks *bookmarks,
EphyTopicsEntry *entry)
{
update_widget(entry);
}
static gboolean
focus_out_cb (GtkEditable *editable,
GdkEventFocus *event,
gpointer user_data)
{
update_widget (EPHY_TOPICS_ENTRY (editable));
return FALSE;
}
static gboolean
match_func (GtkEntryCompletion *completion,
const gchar *key,
GtkTreeIter *iter,
gpointer user_data)
{
gboolean result;
const char *real_key;
char *text;
real_key = g_strrstr (key, ";");
if (real_key) real_key++;
else real_key = key;
while (*real_key == ' ') real_key++;
gtk_tree_model_get (gtk_entry_completion_get_model (completion), iter, 1, &text, -1);
if (text == NULL) return FALSE;
result = g_str_has_prefix (text, real_key);
g_free (text);
return result;
}
static void
ephy_topics_entry_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EphyTopicsEntry *entry = EPHY_TOPICS_ENTRY (object);
switch (prop_id)
{
case PROP_BOOKMARKS:
entry->priv->bookmarks = g_value_get_object (value);
g_signal_connect_object (entry->priv->bookmarks, "tree-changed",
G_CALLBACK (tree_changed_cb), entry,
G_CONNECT_AFTER);
break;
case PROP_BOOKMARK:
entry->priv->bookmark = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GObject *
ephy_topics_entry_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_params)
{
GObject *object;
EphyTopicsEntry *entry;
EphyTopicsEntryPrivate *priv;
object = parent_class->constructor (type, n_construct_properties,
construct_params);
entry = EPHY_TOPICS_ENTRY (object);
priv = EPHY_TOPICS_ENTRY_GET_PRIVATE (object);
priv->store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
priv->completion = gtk_entry_completion_new ();
priv->update_keywords = TRUE;
gtk_entry_completion_set_model (priv->completion, GTK_TREE_MODEL (priv->store));
gtk_entry_completion_set_text_column (priv->completion, 0);
gtk_entry_completion_set_popup_completion (priv->completion, TRUE);
gtk_entry_completion_set_popup_single_match (priv->completion, TRUE);
gtk_entry_completion_set_match_func (priv->completion, match_func, NULL, NULL);
g_signal_connect (priv->completion, "match-selected",
G_CALLBACK (match_selected_cb), NULL);
g_signal_connect (object, "focus-out-event",
G_CALLBACK (focus_out_cb), NULL);
g_signal_connect (object, "changed",
G_CALLBACK (update_keywords), NULL);
g_signal_connect (G_OBJECT (priv->completion), "action-activated",
G_CALLBACK (action_cb), NULL);
update_widget (entry);
return object;
}
static void
ephy_topics_entry_init (EphyTopicsEntry *entry)
{
entry->priv = EPHY_TOPICS_ENTRY_GET_PRIVATE (entry);
}
static void
ephy_topics_entry_finalize (GObject *object)
{
EphyTopicsEntry *entry = EPHY_TOPICS_ENTRY (object);
g_free (entry->priv->action);
parent_class->finalize (object);
}
GtkWidget *
ephy_topics_entry_new (EphyBookmarks *bookmarks,
EphyNode *bookmark)
{
EphyTopicsEntry *entry;
g_assert (bookmarks != NULL);
entry = EPHY_TOPICS_ENTRY (g_object_new
(EPHY_TYPE_TOPICS_ENTRY,
"bookmarks", bookmarks,
"bookmark", bookmark,
NULL));
return GTK_WIDGET (entry);
}
static void
ephy_topics_entry_class_init (EphyTopicsEntryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
object_class->set_property = ephy_topics_entry_set_property;
object_class->constructor = ephy_topics_entry_constructor;
object_class->finalize = ephy_topics_entry_finalize;
g_object_class_install_property (object_class,
PROP_BOOKMARKS,
g_param_spec_object ("bookmarks",
"Bookmarks set",
"Bookmarks set",
EPHY_TYPE_BOOKMARKS,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_object_class_install_property (object_class,
PROP_BOOKMARK,
g_param_spec_pointer ("bookmark",
"Bookmark",
"Bookmark",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
g_type_class_add_private (object_class, sizeof(EphyTopicsEntryPrivate));
}