aboutsummaryrefslogtreecommitdiffstats
path: root/src/bookmarks
diff options
context:
space:
mode:
Diffstat (limited to 'src/bookmarks')
-rw-r--r--src/bookmarks/.cvsignore6
-rw-r--r--src/bookmarks/Makefile.am31
-rw-r--r--src/bookmarks/ephy-bookmarks-editor.c534
-rw-r--r--src/bookmarks/ephy-bookmarks-editor.h59
-rw-r--r--src/bookmarks/ephy-bookmarks.c988
-rw-r--r--src/bookmarks/ephy-bookmarks.h108
-rw-r--r--src/bookmarks/ephy-keywords-entry.c286
-rw-r--r--src/bookmarks/ephy-keywords-entry.h68
-rw-r--r--src/bookmarks/ephy-new-bookmark.c340
-rw-r--r--src/bookmarks/ephy-new-bookmark.h65
-rw-r--r--src/bookmarks/ephy-node-view.c531
-rw-r--r--src/bookmarks/ephy-node-view.h82
-rw-r--r--src/bookmarks/ephy-tree-model-node.c702
-rw-r--r--src/bookmarks/ephy-tree-model-node.h81
14 files changed, 3881 insertions, 0 deletions
diff --git a/src/bookmarks/.cvsignore b/src/bookmarks/.cvsignore
new file mode 100644
index 000000000..6e5ca7ed4
--- /dev/null
+++ b/src/bookmarks/.cvsignore
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.lo
+*.la
diff --git a/src/bookmarks/Makefile.am b/src/bookmarks/Makefile.am
new file mode 100644
index 000000000..4dd24c1bd
--- /dev/null
+++ b/src/bookmarks/Makefile.am
@@ -0,0 +1,31 @@
+INCLUDES = \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/embed \
+ -I$(top_srcdir)/lib/widgets \
+ $(WARN_CFLAGS) \
+ $(EPIPHANY_DEPENDENCY_CFLAGS) \
+ -DSHARE_DIR=\"$(pkgdatadir)\" \
+ -DDATADIR=\""$(datadir)"\" \
+ -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
+ -DG_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DGTK_DISABLE_DEPRECATED \
+ -DGDK_PIXBUF_DISABLE_DEPRECATED \
+ -DGNOME_DISABLE_DEPRECATED
+
+noinst_LTLIBRARIES = libephybookmarks.la
+
+libephybookmarks_la_SOURCES = \
+ ephy-bookmarks.c \
+ ephy-bookmarks.h \
+ ephy-tree-model-node.c \
+ ephy-tree-model-node.h \
+ ephy-node-view.c \
+ ephy-node-view.h \
+ ephy-bookmarks-editor.c \
+ ephy-bookmarks-editor.h \
+ ephy-keywords-entry.c \
+ ephy-keywords-entry.h \
+ ephy-new-bookmark.c \
+ ephy-new-bookmark.h
diff --git a/src/bookmarks/ephy-bookmarks-editor.c b/src/bookmarks/ephy-bookmarks-editor.c
new file mode 100644
index 000000000..2888950df
--- /dev/null
+++ b/src/bookmarks/ephy-bookmarks-editor.c
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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 <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <libgnome/gnome-i18n.h>
+#include <string.h>
+
+#include "ephy-bookmarks-editor.h"
+#include "ephy-node-view.h"
+#include "ephy-window.h"
+#include "ephy-keywords-entry.h"
+#include "ephy-dnd.h"
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+static void ephy_bookmarks_editor_class_init (EphyBookmarksEditorClass *klass);
+static void ephy_bookmarks_editor_init (EphyBookmarksEditor *editor);
+static void ephy_bookmarks_editor_finalize (GObject *object);
+static void ephy_bookmarks_editor_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ephy_bookmarks_editor_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+struct EphyBookmarksEditorPrivate
+{
+ EphyBookmarks *bookmarks;
+ EphyNodeView *bm_view;
+ EphyNodeView *key_view;
+ EphyNodeFilter *bookmarks_filter;
+ GtkWidget *title_entry;
+ GtkWidget *keywords_entry;
+ GtkWidget *search_entry;
+};
+
+enum
+{
+ PROP_0,
+ PROP_BOOKMARKS
+};
+
+enum
+{
+ RESPONSE_REMOVE
+};
+
+static GObjectClass *parent_class = NULL;
+
+GType
+ephy_bookmarks_editor_get_type (void)
+{
+ static GType ephy_bookmarks_editor_type = 0;
+
+ if (ephy_bookmarks_editor_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyBookmarksEditorClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_bookmarks_editor_class_init,
+ NULL,
+ NULL,
+ sizeof (EphyBookmarksEditor),
+ 0,
+ (GInstanceInitFunc) ephy_bookmarks_editor_init
+ };
+
+ ephy_bookmarks_editor_type = g_type_register_static (GTK_TYPE_DIALOG,
+ "EphyBookmarksEditor",
+ &our_info, 0);
+ }
+
+ return ephy_bookmarks_editor_type;
+}
+
+static void
+ephy_bookmarks_editor_class_init (EphyBookmarksEditorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_bookmarks_editor_finalize;
+
+ object_class->set_property = ephy_bookmarks_editor_set_property;
+ object_class->get_property = ephy_bookmarks_editor_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_BOOKMARKS,
+ g_param_spec_object ("bookmarks",
+ "Bookmarks set",
+ "Bookmarks set",
+ EPHY_BOOKMARKS_TYPE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ephy_bookmarks_editor_finalize (GObject *object)
+{
+ EphyBookmarksEditor *editor;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_BOOKMARKS_EDITOR (object));
+
+ editor = EPHY_BOOKMARKS_EDITOR (object);
+
+ g_return_if_fail (editor->priv != NULL);
+
+ g_object_unref (G_OBJECT (editor->priv->bookmarks_filter));
+
+ g_free (editor->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+ephy_bookmarks_editor_node_selected_cb (GtkWidget *view,
+ EphyNode *node,
+ EphyBookmarksEditor *editor)
+{
+ const char *title;
+ const char *keywords;
+
+ if (node != NULL)
+ {
+ g_assert (EPHY_IS_NODE (node));
+
+ title = ephy_node_get_property_string
+ (node, EPHY_NODE_BMK_PROP_TITLE);
+ keywords = ephy_node_get_property_string
+ (node, EPHY_NODE_BMK_PROP_KEYWORDS);
+ gtk_entry_set_text (GTK_ENTRY (editor->priv->title_entry),
+ title ? g_strdup (title) : "");
+ gtk_entry_set_text (GTK_ENTRY (editor->priv->keywords_entry),
+ keywords ? g_strdup (keywords) : "");
+ gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->title_entry), TRUE);
+ gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->keywords_entry), TRUE);
+ }
+ else
+ {
+ gtk_entry_set_text (GTK_ENTRY (editor->priv->title_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (editor->priv->keywords_entry), "");
+ gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->title_entry), FALSE);
+ gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->keywords_entry), FALSE);
+ }
+}
+
+static void
+ephy_bookmarks_editor_node_activated_cb (GtkWidget *view,
+ EphyNode *node,
+ EphyBookmarksEditor *editor)
+{
+ const char *location;
+ GtkWindow *window;
+
+ g_return_if_fail (EPHY_IS_NODE (node));
+ location = ephy_node_get_property_string
+ (node, EPHY_NODE_BMK_PROP_LOCATION);
+ g_return_if_fail (location != NULL);
+
+ window = gtk_window_get_transient_for (GTK_WINDOW (editor));
+ g_return_if_fail (IS_EPHY_WINDOW (window));
+ ephy_window_load_url (EPHY_WINDOW (window), location);
+}
+
+static void
+ephy_bookmarks_editor_response_cb (GtkDialog *dialog,
+ int response_id,
+ EphyBookmarksEditor *editor)
+{
+ switch (response_id)
+ {
+ case GTK_RESPONSE_CLOSE:
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ break;
+ case RESPONSE_REMOVE:
+ ephy_node_view_remove (editor->priv->bm_view);
+ break;
+ }
+}
+
+static void
+update_prop_from_entry (EphyBookmarksEditor *editor,
+ GtkWidget *entry,
+ guint id)
+{
+ GList *selection;
+ GValue value = { 0, };
+
+ selection = ephy_node_view_get_selection (editor->priv->bm_view);
+ if (selection)
+ {
+ EphyNode *bm = EPHY_NODE (selection->data);
+ char *tmp;
+
+ tmp = gtk_editable_get_chars
+ (GTK_EDITABLE (entry), 0, -1);
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, tmp);
+ ephy_node_set_property (bm, id, &value);
+ g_value_unset (&value);
+ g_free (tmp);
+ }
+ g_list_free (selection);
+}
+
+static void
+title_entry_changed_cb (GtkWidget *entry, EphyBookmarksEditor *editor)
+{
+ update_prop_from_entry (editor, editor->priv->title_entry,
+ EPHY_NODE_BMK_PROP_TITLE);
+}
+
+static void
+keywords_changed_cb (GtkWidget *entry,
+ EphyBookmarksEditor *editor)
+{
+ EphyNode *node;
+ GList *selection;
+ char *keywords;
+
+ selection = ephy_node_view_get_selection (editor->priv->bm_view);
+ if (selection == NULL) return;
+ node = EPHY_NODE (selection->data);
+ g_list_free (selection);
+
+ keywords = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+ ephy_bookmarks_update_keywords (editor->priv->bookmarks,
+ keywords, node);
+ g_free (keywords);
+
+ update_prop_from_entry (editor, editor->priv->keywords_entry,
+ EPHY_NODE_BMK_PROP_KEYWORDS);
+}
+
+static GtkWidget *
+build_editing_table (EphyBookmarksEditor *editor)
+{
+ GtkWidget *table, *label, *entry;
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_widget_show (table);
+
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_markup (GTK_LABEL (label), _("<b>Title:</b>"));
+ gtk_widget_show (label);
+ entry = gtk_entry_new ();
+ editor->priv->title_entry = entry;
+ gtk_widget_set_sensitive (GTK_WIDGET (entry), FALSE);
+ gtk_widget_show (entry);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+ gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
+ g_signal_connect (G_OBJECT (entry), "changed",
+ G_CALLBACK (title_entry_changed_cb),
+ editor);
+
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_markup (GTK_LABEL (label), _("<b>Keywords:</b>"));
+ gtk_widget_show (label);
+ entry = ephy_keywords_entry_new ();
+ ephy_keywords_entry_set_bookmarks (EPHY_KEYWORDS_ENTRY (entry),
+ editor->priv->bookmarks);
+ editor->priv->keywords_entry = entry;
+ gtk_widget_set_sensitive (GTK_WIDGET (entry), FALSE);
+ gtk_widget_show (entry);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 1, 2);
+ g_signal_connect (G_OBJECT (entry), "keywords_changed",
+ G_CALLBACK (keywords_changed_cb),
+ editor);
+
+ return table;
+}
+
+static void
+bookmarks_filter (EphyBookmarksEditor *editor,
+ EphyNode *keyword)
+{
+ ephy_node_filter_empty (editor->priv->bookmarks_filter);
+ ephy_node_filter_add_expression (editor->priv->bookmarks_filter,
+ ephy_node_filter_expression_new (EPHY_NODE_FILTER_EXPRESSION_HAS_PARENT,
+ keyword),
+ 0);
+ ephy_node_filter_done_changing (editor->priv->bookmarks_filter);
+}
+
+static void
+keyword_node_selected_cb (EphyNodeView *view,
+ EphyNode *node,
+ EphyBookmarksEditor *editor)
+{
+ if (node == NULL)
+ {
+ ephy_node_view_select_node
+ (editor->priv->key_view,
+ ephy_bookmarks_get_bookmarks
+ (editor->priv->bookmarks));
+ }
+ else
+ {
+ bookmarks_filter (editor, node);
+ }
+}
+
+static void
+search_entry_changed_cb (GtkWidget *entry, EphyBookmarksEditor *editor)
+{
+ char *search_text;
+
+ search_text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+
+ GDK_THREADS_ENTER ();
+
+ ephy_node_filter_empty (editor->priv->bookmarks_filter);
+ ephy_node_filter_add_expression (editor->priv->bookmarks_filter,
+ ephy_node_filter_expression_new (EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_CONTAINS,
+ EPHY_NODE_BMK_PROP_TITLE,
+ search_text),
+ 0);
+ ephy_node_filter_add_expression (editor->priv->bookmarks_filter,
+ ephy_node_filter_expression_new (EPHY_NODE_FILTER_EXPRESSION_STRING_PROP_CONTAINS,
+ EPHY_NODE_BMK_PROP_KEYWORDS,
+ search_text),
+ 0);
+ ephy_node_filter_done_changing (editor->priv->bookmarks_filter);
+
+ GDK_THREADS_LEAVE ();
+
+ g_free (search_text);
+}
+
+static GtkWidget *
+build_search_box (EphyBookmarksEditor *editor)
+{
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *entry;
+
+ box = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (box);
+
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_markup (GTK_LABEL (label), _("<b>Search:</b>"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (box),
+ label, FALSE, TRUE, 0);
+
+ entry = gtk_entry_new ();
+ editor->priv->search_entry = entry;
+ gtk_widget_show (entry);
+ gtk_box_pack_start (GTK_BOX (box),
+ entry, TRUE, TRUE, 0);
+ g_signal_connect (G_OBJECT (entry), "changed",
+ G_CALLBACK (search_entry_changed_cb),
+ editor);
+ return box;
+}
+
+static void
+ephy_bookmarks_editor_construct (EphyBookmarksEditor *editor)
+{
+ GtkWidget *hbox, *vbox;
+ EphyNodeView *bm_view, *key_view;
+ EphyNode *node;
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (editor), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (editor), 6);
+ gtk_widget_set_size_request (GTK_WIDGET (editor), 500, 400);
+ g_signal_connect (G_OBJECT (editor),
+ "response",
+ G_CALLBACK (ephy_bookmarks_editor_response_cb),
+ editor);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (editor)->vbox),
+ hbox, TRUE, TRUE, 0);
+ gtk_widget_show (hbox);
+
+ g_assert (editor->priv->bookmarks);
+
+ node = ephy_bookmarks_get_keywords (editor->priv->bookmarks);
+ key_view = ephy_node_view_new (node, NULL);
+ ephy_node_view_set_browse_mode (key_view);
+ ephy_node_view_add_column (key_view, _("Keywords"),
+ EPHY_TREE_MODEL_NODE_COL_KEYWORD, FALSE);
+ gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (key_view), FALSE, TRUE, 0);
+ gtk_widget_set_size_request (GTK_WIDGET (key_view), 120, -1);
+ gtk_widget_show (GTK_WIDGET (key_view));
+ editor->priv->key_view = key_view;
+ g_signal_connect (G_OBJECT (key_view),
+ "node_selected",
+ G_CALLBACK (keyword_node_selected_cb),
+ editor);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ vbox, TRUE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ build_search_box (editor),
+ FALSE, FALSE, 0);
+
+ node = ephy_bookmarks_get_bookmarks (editor->priv->bookmarks);
+ editor->priv->bookmarks_filter = ephy_node_filter_new ();
+ bm_view = ephy_node_view_new (node, editor->priv->bookmarks_filter);
+ ephy_node_view_enable_drag_source (bm_view);
+ ephy_node_view_add_column (bm_view, _("Title"),
+ EPHY_TREE_MODEL_NODE_COL_BOOKMARK, TRUE);
+ ephy_node_view_add_column (bm_view, _("Location"),
+ EPHY_TREE_MODEL_NODE_COL_LOCATION, TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (bm_view), TRUE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (bm_view));
+ editor->priv->bm_view = bm_view;
+ g_signal_connect (G_OBJECT (bm_view),
+ "node_activated",
+ G_CALLBACK (ephy_bookmarks_editor_node_activated_cb),
+ editor);
+ g_signal_connect (G_OBJECT (bm_view),
+ "node_selected",
+ G_CALLBACK (ephy_bookmarks_editor_node_selected_cb),
+ editor);
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ build_editing_table (editor),
+ FALSE, FALSE, 0);
+
+ gtk_dialog_add_button (GTK_DIALOG (editor),
+ GTK_STOCK_REMOVE,
+ RESPONSE_REMOVE);
+ gtk_dialog_add_button (GTK_DIALOG (editor),
+ GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+ gtk_dialog_set_default_response (GTK_DIALOG (editor), GTK_RESPONSE_CLOSE);
+}
+
+GtkWidget *
+ephy_bookmarks_editor_new (EphyBookmarks *bookmarks,
+ GtkWindow *parent)
+{
+ EphyBookmarksEditor *editor;
+
+ g_assert (bookmarks != NULL);
+
+ editor = EPHY_BOOKMARKS_EDITOR (g_object_new
+ (EPHY_TYPE_BOOKMARKS_EDITOR,
+ "bookmarks", bookmarks,
+ NULL));
+
+ if (parent)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (editor), parent);
+ }
+
+ ephy_bookmarks_editor_construct (editor);
+
+ return GTK_WIDGET (editor);
+}
+
+static void
+ephy_bookmarks_editor_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyBookmarksEditor *editor = EPHY_BOOKMARKS_EDITOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_BOOKMARKS:
+ editor->priv->bookmarks = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_bookmarks_editor_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyBookmarksEditor *editor = EPHY_BOOKMARKS_EDITOR (object);
+
+ switch (prop_id)
+ {
+ case PROP_BOOKMARKS:
+ g_value_set_object (value, editor->priv->bookmarks);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_bookmarks_editor_init (EphyBookmarksEditor *editor)
+{
+ editor->priv = g_new0 (EphyBookmarksEditorPrivate, 1);
+}
diff --git a/src/bookmarks/ephy-bookmarks-editor.h b/src/bookmarks/ephy-bookmarks-editor.h
new file mode 100644
index 000000000..f79dfa673
--- /dev/null
+++ b/src/bookmarks/ephy-bookmarks-editor.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_BOOKMARKS_EDITOR_H
+#define EPHY_BOOKMARKS_EDITOR_H
+
+#include <gtk/gtkdialog.h>
+
+#include "ephy-node-view.h"
+#include "ephy-bookmarks.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_BOOKMARKS_EDITOR (ephy_bookmarks_editor_get_type ())
+#define EPHY_BOOKMARKS_EDITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_BOOKMARKS_EDITOR, EphyBookmarksEditor))
+#define EPHY_BOOKMARKS_EDITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_BOOKMARKS_EDITOR, EphyBookmarksEditorClass))
+#define EPHY_IS_BOOKMARKS_EDITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_BOOKMARKS_EDITOR))
+#define EPHY_IS_BOOKMARKS_EDITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_BOOKMARKS_EDITOR))
+#define EPHY_BOOKMARKS_EDITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_BOOKMARKS_EDITOR, EphyBookmarksEditorClass))
+
+typedef struct EphyBookmarksEditorPrivate EphyBookmarksEditorPrivate;
+
+typedef struct
+{
+ GtkDialog parent;
+
+ EphyBookmarksEditorPrivate *priv;
+} EphyBookmarksEditor;
+
+typedef struct
+{
+ GtkDialogClass parent;
+} EphyBookmarksEditorClass;
+
+GType ephy_bookmarks_editor_get_type (void);
+
+GtkWidget *ephy_bookmarks_editor_new (EphyBookmarks *bookmarks,
+ GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* EPHY_BOOKMARKS_EDITOR_H */
diff --git a/src/bookmarks/ephy-bookmarks.c b/src/bookmarks/ephy-bookmarks.c
new file mode 100644
index 000000000..e27a30496
--- /dev/null
+++ b/src/bookmarks/ephy-bookmarks.c
@@ -0,0 +1,988 @@
+/*
+ * 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-bookmarks.h"
+#include "ephy-file-helpers.h"
+#include "ephy-shell.h"
+#include "ephy-history.h"
+
+#include <string.h>
+#include <libgnome/gnome-i18n.h>
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+#define EPHY_BOOKMARKS_XML_VERSION "0.1"
+
+#define MAX_FAVORITES_NUM 10
+
+struct EphyBookmarksPrivate
+{
+ char *xml_file;
+ EphyNode *bookmarks;
+ EphyNode *keywords;
+ EphyNode *favorites;
+ EphyNode *lower_fav;
+ double lower_score;
+ GHashTable *bookmarks_hash;
+ GStaticRWLock *bookmarks_hash_lock;
+ GHashTable *favorites_hash;
+ GStaticRWLock *favorites_hash_lock;
+ GHashTable *keywords_hash;
+ GStaticRWLock *keywords_hash_lock;
+};
+
+static void
+ephy_bookmarks_class_init (EphyBookmarksClass *klass);
+static void
+ephy_bookmarks_init (EphyBookmarks *tab);
+static void
+ephy_bookmarks_finalize (GObject *object);
+static void
+ephy_bookmarks_autocompletion_source_init (EphyAutocompletionSourceIface *iface);
+
+static GObjectClass *parent_class = NULL;
+
+GType
+ephy_bookmarks_get_type (void)
+{
+ static GType ephy_bookmarks_type = 0;
+
+ if (ephy_bookmarks_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyBookmarksClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_bookmarks_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (EphyBookmarks),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_bookmarks_init
+ };
+
+ static const GInterfaceInfo autocompletion_source_info =
+ {
+ (GInterfaceInitFunc) ephy_bookmarks_autocompletion_source_init,
+ NULL,
+ NULL
+ };
+
+ ephy_bookmarks_type = g_type_register_static (G_TYPE_OBJECT,
+ "EphyBookmarks",
+ &our_info, 0);
+
+ g_type_add_interface_static (ephy_bookmarks_type,
+ EPHY_TYPE_AUTOCOMPLETION_SOURCE,
+ &autocompletion_source_info);
+ }
+
+ return ephy_bookmarks_type;
+}
+
+static void
+ephy_bookmarks_autocompletion_source_set_basic_key (EphyAutocompletionSource *source,
+ const gchar *basic_key)
+{
+ /* nothing to do here */
+}
+
+static void
+ephy_bookmarks_autocompletion_source_foreach (EphyAutocompletionSource *source,
+ const gchar *current_text,
+ EphyAutocompletionSourceForeachFunc func,
+ gpointer data)
+{
+ GPtrArray *children;
+ int i;
+ EphyBookmarks *eb = EPHY_BOOKMARKS (source);
+
+ children = ephy_node_get_children (eb->priv->bookmarks);
+ for (i = 0; i < children->len; i++)
+ {
+ EphyNode *kid;
+ const char *url, *smart_url, *title, *keywords;
+ char *item;
+
+ kid = g_ptr_array_index (children, i);
+ url = ephy_node_get_property_string
+ (kid, EPHY_NODE_BMK_PROP_LOCATION);
+ smart_url = ephy_node_get_property_string
+ (kid, EPHY_NODE_BMK_PROP_SMART_LOCATION);
+ title = ephy_node_get_property_string
+ (kid, EPHY_NODE_BMK_PROP_TITLE);
+ keywords = ephy_node_get_property_string
+ (kid, EPHY_NODE_BMK_PROP_KEYWORDS);
+ item = g_strconcat (title, keywords, NULL);
+
+ if (smart_url == NULL ||
+ g_utf8_strlen (smart_url, -1) == 0)
+ {
+ smart_url = NULL;
+ }
+
+ func (source,
+ smart_url ? NULL : item,
+ title,
+ smart_url ? smart_url : url,
+ (smart_url != NULL),
+ TRUE, 0, data);
+
+ g_free (item);
+ }
+ ephy_node_thaw (eb->priv->bookmarks);
+}
+
+static void
+ephy_bookmarks_emit_data_changed (EphyBookmarks *eb)
+{
+ g_signal_emit_by_name (eb, "data-changed");
+}
+
+static void
+ephy_bookmarks_autocompletion_source_init (EphyAutocompletionSourceIface *iface)
+{
+ iface->foreach = ephy_bookmarks_autocompletion_source_foreach;
+ iface->set_basic_key = ephy_bookmarks_autocompletion_source_set_basic_key;
+}
+
+static void
+ephy_bookmarks_class_init (EphyBookmarksClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_bookmarks_finalize;
+}
+
+static gboolean
+ephy_bookmarks_clean_empty_keywords (EphyBookmarks *eb)
+{
+ GPtrArray *children;
+ int i;
+
+ children = ephy_node_get_children (eb->priv->keywords);
+ ephy_node_thaw (eb->priv->keywords);
+ for (i = 0; i < children->len; i++)
+ {
+ EphyNode *kid;
+
+ kid = g_ptr_array_index (children, i);
+
+ if (ephy_node_get_n_children (kid) == 0)
+ {
+ DEBUG_MSG (("Remove empty keyword: %s\n",
+ ephy_node_get_property_string (kid,
+ EPHY_NODE_KEYWORD_PROP_NAME)));
+ ephy_node_unref (kid);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+ephy_bookmarks_load (EphyBookmarks *eb)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root, child;
+ char *tmp;
+
+ if (g_file_test (eb->priv->xml_file, G_FILE_TEST_EXISTS) == FALSE)
+ return;
+
+ doc = xmlParseFile (eb->priv->xml_file);
+ g_assert (doc != NULL);
+
+ root = xmlDocGetRootElement (doc);
+
+ tmp = xmlGetProp (root, "version");
+ g_assert (tmp != NULL && strcmp (tmp, EPHY_BOOKMARKS_XML_VERSION) == 0);
+ g_free (tmp);
+
+ for (child = root->children; child != NULL; child = child->next)
+ {
+ EphyNode *node;
+
+ node = ephy_node_new_from_xml (child);
+ }
+
+ xmlFreeDoc (doc);
+}
+
+static void
+ephy_bookmarks_save (EphyBookmarks *eb)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ GPtrArray *children;
+ int i;
+
+ DEBUG_MSG (("Saving bookmarks\n"));
+
+ /* save nodes to xml */
+ xmlIndentTreeOutput = TRUE;
+ doc = xmlNewDoc ("1.0");
+
+ root = xmlNewDocNode (doc, NULL, "ephy_bookmarks", NULL);
+ xmlSetProp (root, "version", EPHY_BOOKMARKS_XML_VERSION);
+ xmlDocSetRootElement (doc, root);
+
+ children = ephy_node_get_children (eb->priv->keywords);
+ for (i = 0; i < children->len; i++)
+ {
+ EphyNode *kid;
+
+ kid = g_ptr_array_index (children, i);
+
+ if (kid != eb->priv->bookmarks)
+ {
+ ephy_node_save_to_xml (kid, root);
+ }
+ }
+ ephy_node_thaw (eb->priv->keywords);
+
+ children = ephy_node_get_children (eb->priv->bookmarks);
+ for (i = 0; i < children->len; i++)
+ {
+ EphyNode *kid;
+
+ kid = g_ptr_array_index (children, i);
+
+ ephy_node_save_to_xml (kid, root);
+ }
+ ephy_node_thaw (eb->priv->bookmarks);
+
+ xmlSaveFormatFile (eb->priv->xml_file, doc, 1);
+}
+
+static double
+get_history_item_score (EphyHistory *eh, const char *page)
+{
+ return ephy_history_get_page_visits (eh, page);
+}
+
+static EphyNode *
+compute_lower_fav (EphyNode *favorites, double *score)
+{
+ GPtrArray *children;
+ int i;
+ EphyEmbedShell *embed_shell;
+ EphyHistory *history;
+ EphyNode *result = NULL;
+
+ embed_shell = ephy_shell_get_embed_shell (ephy_shell);
+ history = ephy_embed_shell_get_global_history (embed_shell);
+
+ *score = DBL_MAX;
+ children = ephy_node_get_children (favorites);
+ for (i = 0; i < children->len; i++)
+ {
+ const char *url;
+ EphyNode *child;
+ double item_score;
+
+ child = g_ptr_array_index (children, i);
+ url = ephy_node_get_property_string
+ (child, EPHY_NODE_BMK_PROP_LOCATION);
+ item_score = get_history_item_score (history, url);
+ if (*score > item_score)
+ {
+ *score = item_score;
+ result = child;
+ }
+ }
+ ephy_node_thaw (favorites);
+
+ if (result == NULL) *score = 0;
+
+ return result;
+}
+
+static void
+ephy_bookmarks_update_favorites (EphyBookmarks *eb)
+{
+ eb->priv->lower_fav = compute_lower_fav (eb->priv->favorites,
+ &eb->priv->lower_score);
+}
+
+static gboolean
+add_to_favorites (EphyBookmarks *eb, EphyNode *node, EphyHistory *eh)
+{
+ const char *url;
+ EphyNode *fav_node;
+ gboolean full_menu;
+ double score;
+
+ url = ephy_node_get_property_string (node, EPHY_NODE_BMK_PROP_LOCATION);
+ g_static_rw_lock_reader_lock (eb->priv->favorites_hash_lock);
+ fav_node = g_hash_table_lookup (eb->priv->favorites_hash, url);
+ g_static_rw_lock_reader_unlock (eb->priv->favorites_hash_lock);
+ if (fav_node) return FALSE;
+
+ score = get_history_item_score (eh, url);
+ full_menu = ephy_node_get_n_children (eb->priv->favorites)
+ > MAX_FAVORITES_NUM;
+ if (full_menu && score < eb->priv->lower_score) return FALSE;
+
+ if (eb->priv->lower_fav && full_menu)
+ {
+ ephy_node_remove_child (eb->priv->favorites,
+ eb->priv->lower_fav);
+ }
+
+ ephy_node_add_child (eb->priv->favorites, node);
+ ephy_bookmarks_update_favorites (eb);
+
+ return TRUE;
+}
+
+static void
+update_favorites_menus ()
+{
+ Session *session;
+ GList *l;
+
+ session = ephy_shell_get_session (ephy_shell);
+ l = session_get_windows (session);
+
+ for (; l != NULL; l = l->next)
+ {
+ EphyWindow *window = EPHY_WINDOW (l->data);
+
+ ephy_window_update_control (window, FavoritesControl);
+ }
+}
+
+static void
+history_site_visited_cb (EphyHistory *gh, const char *url, EphyBookmarks *eb)
+{
+ EphyNode *node;
+
+ g_static_rw_lock_reader_lock (eb->priv->bookmarks_hash_lock);
+ node = g_hash_table_lookup (eb->priv->bookmarks_hash, url);
+ g_static_rw_lock_reader_unlock (eb->priv->bookmarks_hash_lock);
+ if (!node) return;
+
+ if (add_to_favorites (eb, node, gh))
+ {
+ update_favorites_menus ();
+ }
+}
+
+static void
+ephy_setup_history_notifiers (EphyBookmarks *eb)
+{
+ EphyEmbedShell *embed_shell;
+ EphyHistory *history;
+
+ embed_shell = ephy_shell_get_embed_shell (ephy_shell);
+ history = ephy_embed_shell_get_global_history (embed_shell);
+
+ g_signal_connect (history, "visited",
+ G_CALLBACK (history_site_visited_cb), eb);
+}
+
+static void
+keywords_added_cb (EphyNode *node,
+ EphyNode *child,
+ EphyBookmarks *eb)
+{
+ g_static_rw_lock_writer_lock (eb->priv->keywords_hash_lock);
+
+ g_hash_table_insert (eb->priv->keywords_hash,
+ (char *) ephy_node_get_property_string (child, EPHY_NODE_KEYWORD_PROP_NAME),
+ child);
+
+ g_static_rw_lock_writer_unlock (eb->priv->keywords_hash_lock);
+}
+
+static void
+keywords_removed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyBookmarks *eb)
+{
+ g_static_rw_lock_writer_lock (eb->priv->keywords_hash_lock);
+
+ g_hash_table_remove (eb->priv->keywords_hash,
+ ephy_node_get_property_string (child, EPHY_NODE_KEYWORD_PROP_NAME));
+
+ g_static_rw_lock_writer_unlock (eb->priv->keywords_hash_lock);
+}
+
+static void
+favorites_added_cb (EphyNode *node,
+ EphyNode *child,
+ EphyBookmarks *eb)
+{
+ g_static_rw_lock_writer_lock (eb->priv->favorites_hash_lock);
+
+ g_hash_table_insert (eb->priv->favorites_hash,
+ (char *) ephy_node_get_property_string (child, EPHY_NODE_BMK_PROP_LOCATION),
+ child);
+
+ g_static_rw_lock_writer_unlock (eb->priv->favorites_hash_lock);
+}
+
+static void
+favorites_removed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyBookmarks *eb)
+{
+ g_static_rw_lock_writer_lock (eb->priv->favorites_hash_lock);
+
+ g_hash_table_remove (eb->priv->favorites_hash,
+ ephy_node_get_property_string (child, EPHY_NODE_BMK_PROP_LOCATION));
+
+ g_static_rw_lock_writer_unlock (eb->priv->favorites_hash_lock);
+}
+
+static void
+bookmarks_added_cb (EphyNode *node,
+ EphyNode *child,
+ EphyBookmarks *eb)
+{
+ g_static_rw_lock_writer_lock (eb->priv->bookmarks_hash_lock);
+
+ g_hash_table_insert (eb->priv->bookmarks_hash,
+ (char *) ephy_node_get_property_string (child, EPHY_NODE_BMK_PROP_LOCATION),
+ child);
+
+ g_static_rw_lock_writer_unlock (eb->priv->bookmarks_hash_lock);
+}
+
+static void
+bookmarks_removed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyBookmarks *eb)
+{
+ ephy_bookmarks_emit_data_changed (eb);
+ g_idle_add ((GSourceFunc)ephy_bookmarks_clean_empty_keywords, eb);
+
+ g_static_rw_lock_writer_lock (eb->priv->bookmarks_hash_lock);
+
+ g_hash_table_remove (eb->priv->bookmarks_hash,
+ ephy_node_get_property_string (child, EPHY_NODE_BMK_PROP_LOCATION));
+
+ g_static_rw_lock_writer_unlock (eb->priv->bookmarks_hash_lock);
+}
+
+static void
+ephy_bookmarks_init (EphyBookmarks *eb)
+{
+ GValue value = { 0, };
+
+ eb->priv = g_new0 (EphyBookmarksPrivate, 1);
+
+ eb->priv->xml_file = g_build_filename (ephy_dot_dir (),
+ "bookmarks.xml",
+ NULL);
+
+ eb->priv->bookmarks_hash = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ eb->priv->bookmarks_hash_lock = g_new0 (GStaticRWLock, 1);
+ g_static_rw_lock_init (eb->priv->bookmarks_hash_lock);
+
+ eb->priv->keywords_hash = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ eb->priv->keywords_hash_lock = g_new0 (GStaticRWLock, 1);
+ g_static_rw_lock_init (eb->priv->keywords_hash_lock);
+
+ eb->priv->favorites_hash = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ eb->priv->favorites_hash_lock = g_new0 (GStaticRWLock, 1);
+ g_static_rw_lock_init (eb->priv->favorites_hash_lock);
+
+ /* Bookmarks */
+ eb->priv->bookmarks = ephy_node_new ();
+ ephy_node_ref (eb->priv->bookmarks);
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, _("All"));
+ ephy_node_set_property (eb->priv->bookmarks,
+ EPHY_NODE_KEYWORD_PROP_NAME,
+ &value);
+ g_value_unset (&value);
+ g_signal_connect_object (G_OBJECT (eb->priv->bookmarks),
+ "child_added",
+ G_CALLBACK (bookmarks_added_cb),
+ G_OBJECT (eb),
+ 0);
+ g_signal_connect_object (G_OBJECT (eb->priv->bookmarks),
+ "child_removed",
+ G_CALLBACK (bookmarks_removed_cb),
+ G_OBJECT (eb),
+ 0);
+
+ /* Keywords */
+ eb->priv->keywords = ephy_node_new ();
+ ephy_node_ref (eb->priv->keywords);
+
+ ephy_node_add_child (eb->priv->keywords,
+ eb->priv->bookmarks);
+ g_signal_connect_object (G_OBJECT (eb->priv->keywords),
+ "child_added",
+ G_CALLBACK (keywords_added_cb),
+ G_OBJECT (eb),
+ 0);
+ g_signal_connect_object (G_OBJECT (eb->priv->keywords),
+ "child_removed",
+ G_CALLBACK (keywords_removed_cb),
+ G_OBJECT (eb),
+ 0);
+
+ eb->priv->favorites = ephy_node_new ();
+ ephy_node_ref (eb->priv->favorites);
+ g_signal_connect_object (G_OBJECT (eb->priv->favorites),
+ "child_added",
+ G_CALLBACK (favorites_added_cb),
+ G_OBJECT (eb),
+ 0);
+ g_signal_connect_object (G_OBJECT (eb->priv->favorites),
+ "child_removed",
+ G_CALLBACK (favorites_removed_cb),
+ G_OBJECT (eb),
+ 0);
+
+ ephy_bookmarks_load (eb);
+ ephy_bookmarks_emit_data_changed (eb);
+
+ ephy_setup_history_notifiers (eb);
+ ephy_bookmarks_update_favorites (eb);
+}
+
+static void
+ephy_bookmarks_finalize (GObject *object)
+{
+ EphyBookmarks *eb;
+
+ g_return_if_fail (IS_EPHY_BOOKMARKS (object));
+
+ eb = EPHY_BOOKMARKS (object);
+
+ g_return_if_fail (eb->priv != NULL);
+
+ ephy_bookmarks_save (eb);
+
+ ephy_node_unref (eb->priv->bookmarks);
+ ephy_node_unref (eb->priv->keywords);
+ ephy_node_unref (eb->priv->favorites);
+
+ g_hash_table_destroy (eb->priv->bookmarks_hash);
+ g_static_rw_lock_free (eb->priv->bookmarks_hash_lock);
+ g_hash_table_destroy (eb->priv->favorites_hash);
+ g_static_rw_lock_free (eb->priv->favorites_hash_lock);
+ g_hash_table_destroy (eb->priv->keywords_hash);
+ g_static_rw_lock_free (eb->priv->keywords_hash_lock);
+
+ g_free (eb->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+EphyBookmarks *
+ephy_bookmarks_new ()
+{
+ EphyBookmarks *tab;
+
+ tab = EPHY_BOOKMARKS (g_object_new (EPHY_BOOKMARKS_TYPE, NULL));
+
+ return tab;
+}
+
+EphyNode *
+ephy_bookmarks_add (EphyBookmarks *eb,
+ const char *title,
+ const char *url,
+ const char *smart_url,
+ const char *keywords)
+{
+ EphyNode *bm;
+ GValue value = { 0, };
+
+ bm = ephy_node_new ();
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, title);
+ ephy_node_set_property (bm, EPHY_NODE_BMK_PROP_TITLE,
+ &value);
+ g_value_unset (&value);
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, url);
+ ephy_node_set_property (bm, EPHY_NODE_BMK_PROP_LOCATION,
+ &value);
+ g_value_unset (&value);
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, smart_url);
+ ephy_node_set_property (bm, EPHY_NODE_BMK_PROP_SMART_LOCATION,
+ &value);
+ g_value_unset (&value);
+
+ ephy_bookmarks_update_keywords (eb, keywords, bm);
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, keywords);
+ ephy_node_set_property (bm, EPHY_NODE_BMK_PROP_KEYWORDS,
+ &value);
+ g_value_unset (&value);
+
+ ephy_node_add_child (eb->priv->bookmarks, bm);
+
+ ephy_bookmarks_emit_data_changed (eb);
+
+ return bm;
+}
+
+static gchar *
+options_skip_spaces (const gchar *str)
+{
+ const gchar *ret = str;
+ while (*ret && g_ascii_isspace (*ret))
+ {
+ ++ret;
+ }
+ return (gchar *) ret;
+}
+
+static char *
+options_find_value_end (const gchar *value_start)
+{
+ const gchar *value_end;
+ if (*value_start == '"')
+ {
+ for (value_end = value_start + 1;
+ *value_end && (*value_end != '"' || *(value_end - 1) == '\\');
+ ++value_end) ;
+ }
+ else
+ {
+ for (value_end = value_start;
+ *value_end && ! (g_ascii_isspace (*value_end)
+ || *value_end == ','
+ || *value_end == ';');
+ ++value_end) ;
+ }
+ return (gchar *) value_end;
+}
+
+static char *
+options_find_next_option (const char *current)
+{
+ const gchar *value_start;
+ const gchar *value_end;
+ const gchar *ret;
+ value_start = strchr (current, '=');
+ if (!value_start) return NULL;
+ value_start = options_skip_spaces (value_start + 1);
+ value_end = options_find_value_end (value_start);
+ if (! (*value_end)) return NULL;
+ for (ret = value_end + 1;
+ *ret && (g_ascii_isspace (*ret)
+ || *ret == ','
+ || *ret == ';');
+ ++ret);
+ return (char *) ret;
+}
+
+/**
+ * Very simple parser for option strings in the
+ * form a=b,c=d,e="f g",...
+ */
+static gchar *
+smart_url_options_get (const gchar *options, const gchar *option)
+{
+ gchar *ret = NULL;
+ gsize optionlen = strlen (option);
+ const gchar *current = options_skip_spaces (options);
+
+ while (current)
+ {
+ if (!strncmp (option, current, optionlen))
+ {
+ if (g_ascii_isspace (*(current + optionlen)) || *(current + optionlen) == '=')
+ {
+ const gchar *value_start;
+ const gchar *value_end;
+ value_start = strchr (current + optionlen, '=');
+ if (!value_start) continue;
+ value_start = options_skip_spaces (value_start + 1);
+ value_end = options_find_value_end (value_start);
+ if (*value_start == '"') value_start++;
+ if (value_end >= value_start)
+ {
+ ret = g_strndup (value_start, value_end - value_start);
+ break;
+ }
+ }
+ }
+ current = options_find_next_option (current);
+ }
+
+ return ret;
+}
+
+static char *
+get_smarturl_only (const char *smarturl)
+{
+ const gchar *openbrace;
+ const gchar *closebrace;
+ const gchar *c;
+
+ openbrace = strchr (smarturl, '{');
+ if (!openbrace) return g_strdup (smarturl);
+ for (c = smarturl; c < openbrace; ++c)
+ {
+ if (!strchr (" \t\n", *c)) return g_strdup (smarturl);
+ }
+
+ closebrace = strchr (openbrace + 1, '}');
+ if (!closebrace) return g_strdup (smarturl);
+
+ return g_strdup (closebrace + 1);
+}
+
+char *
+ephy_bookmarks_solve_smart_url (EphyBookmarks *eb,
+ const char *smart_url,
+ const char *content)
+{
+ gchar *ret;
+ GString *s;
+ gchar *t1;
+ gchar *t2;
+ gchar *encoding;
+ gchar *smarturl_only;
+ gchar *arg;
+
+ g_return_val_if_fail (content != NULL, NULL);
+
+ smarturl_only = get_smarturl_only (smart_url);
+
+ s = g_string_new ("");
+
+ encoding = smart_url_options_get (smart_url, "encoding");
+ if (!encoding)
+ {
+ encoding = g_strdup ("UTF-8");
+ }
+
+ arg = g_convert (content, strlen (content),
+ encoding, "UTF-8", NULL, NULL, NULL);
+
+ t1 = smarturl_only;
+ t2 = strstr (t1, "%s");
+ g_return_val_if_fail (t2 != NULL, NULL);
+ g_string_append_len (s, t1, t2 - t1);
+ g_string_append (s, arg);
+ t1 = t2 + 2;
+ g_string_append (s, t1);
+ ret = s->str;
+ g_string_free (s, FALSE);
+
+ g_free (arg);
+ g_free (encoding);
+ g_free (smarturl_only);
+
+ return ret;
+}
+
+EphyNode *
+ephy_bookmarks_add_keyword (EphyBookmarks *eb,
+ const char *name)
+{
+ EphyNode *key;
+ GValue value = { 0, };
+
+ key = ephy_node_new ();
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, name);
+ ephy_node_set_property (key, EPHY_NODE_KEYWORD_PROP_NAME,
+ &value);
+ g_value_unset (&value);
+
+ ephy_node_add_child (eb->priv->keywords, key);
+
+ return key;
+}
+
+EphyNode *
+ephy_bookmarks_find_keyword (EphyBookmarks *eb,
+ const char *name,
+ gboolean partial_match)
+{
+ EphyNode *node;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ if (!partial_match)
+ {
+ g_static_rw_lock_reader_lock (eb->priv->keywords_hash_lock);
+ node = g_hash_table_lookup (eb->priv->keywords_hash, name);
+ g_static_rw_lock_reader_unlock (eb->priv->keywords_hash_lock);
+ }
+ else
+ {
+ GPtrArray *children;
+ int i;
+
+ if (g_utf8_strlen (name, -1) == 0)
+ {
+ DEBUG_MSG (("Empty name, no keyword matches.\n"));
+ return NULL;
+ }
+
+ children = ephy_node_get_children (eb->priv->keywords);
+ node = NULL;
+ for (i = 0; i < children->len; i++)
+ {
+ EphyNode *kid;
+ const char *key;
+
+ kid = g_ptr_array_index (children, i);
+ key = ephy_node_get_property_string (kid, EPHY_NODE_KEYWORD_PROP_NAME);
+
+ if (g_str_has_prefix (key, name) > 0)
+ {
+ node = kid;
+ }
+ }
+ ephy_node_thaw (eb->priv->keywords);
+ }
+
+ return node;
+}
+
+void
+ephy_bookmarks_set_keyword (EphyBookmarks *eb,
+ EphyNode *keyword,
+ EphyNode *bookmark)
+{
+ ephy_node_add_child (keyword, bookmark);
+}
+
+void
+ephy_bookmarks_unset_keyword (EphyBookmarks *eb,
+ EphyNode *keyword,
+ EphyNode *bookmark)
+{
+ ephy_node_remove_child (keyword, bookmark);
+ ephy_bookmarks_clean_empty_keywords (eb);
+}
+
+static GList *
+diff_keywords (char **ks1, char **ks2)
+{
+ GList *result = NULL;
+ int i, j;
+
+ for (i = 0; ks1 != NULL && ks1[i] != NULL; i++)
+ {
+ gboolean found = FALSE;
+
+ DEBUG_MSG (("Diff keywords, keyword:\"%s\"\n", ks1[i]));
+
+ for (j = 0; ks2 != NULL && ks2[j] != NULL; j++)
+ {
+ if (strcmp (ks1[i], ks2[j]) == 0)
+ {
+ found = TRUE;
+ }
+ }
+
+ if (!found && g_utf8_strlen (ks1[i], -1) > 0)
+ {
+ result = g_list_append (result, ks1[i]);
+ }
+ }
+
+ return result;
+}
+
+void
+ephy_bookmarks_update_keywords (EphyBookmarks *eb,
+ const char *keywords,
+ EphyNode *node)
+{
+ const char *prop;
+ char **ks, **old_ks = NULL;
+ GList *diffs, *l;
+ EphyNode *keyword;
+
+ prop = ephy_node_get_property_string (node, EPHY_NODE_BMK_PROP_KEYWORDS);
+ ks = g_strsplit (keywords, " ", 10);
+ if (prop != NULL)
+ {
+ old_ks = g_strsplit (prop, " ", 10);
+ }
+
+ diffs = diff_keywords (ks, old_ks);
+ for (l = diffs; l != NULL; l = l->next)
+ {
+ char *word = (char *)l->data;
+
+ keyword = ephy_bookmarks_find_keyword
+ (eb, word, FALSE);
+
+ if (!keyword)
+ {
+ keyword = ephy_bookmarks_add_keyword
+ (eb, word);
+ }
+
+ ephy_bookmarks_set_keyword (eb, keyword, node);
+ }
+ g_list_free (diffs);
+
+ diffs = diff_keywords (old_ks, ks);
+ for (l = diffs; l != NULL; l = l->next)
+ {
+ keyword = ephy_bookmarks_find_keyword (eb,
+ (char *)l->data, FALSE);
+ g_return_if_fail (keyword != NULL);
+
+ ephy_bookmarks_unset_keyword (eb, keyword, node);
+ }
+ g_list_free (diffs);
+
+ g_strfreev (ks);
+ g_strfreev (old_ks);
+}
+
+EphyNode *
+ephy_bookmarks_get_keywords (EphyBookmarks *eb)
+{
+ return eb->priv->keywords;
+}
+
+EphyNode *
+ephy_bookmarks_get_bookmarks (EphyBookmarks *eb)
+{
+ return eb->priv->bookmarks;
+}
+
+EphyNode *
+ephy_bookmarks_get_favorites (EphyBookmarks *eb)
+{
+ return eb->priv->favorites;
+}
+
diff --git a/src/bookmarks/ephy-bookmarks.h b/src/bookmarks/ephy-bookmarks.h
new file mode 100644
index 000000000..e355950c4
--- /dev/null
+++ b/src/bookmarks/ephy-bookmarks.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2000, 2001, 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.
+ */
+
+#ifndef EPHY_BOOKMARKS_H
+#define EPHY_BOOKMARKS_H
+
+#include <glib-object.h>
+
+#include "ephy-node.h"
+
+G_BEGIN_DECLS
+
+typedef struct EphyBookmarksClass EphyBookmarksClass;
+
+#define EPHY_BOOKMARKS_TYPE (ephy_bookmarks_get_type ())
+#define EPHY_BOOKMARKS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EPHY_BOOKMARKS_TYPE, EphyBookmarks))
+#define EPHY_BOOKMARKS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EPHY_BOOKMARKS_TYPE, EphyBookmarksClass))
+#define IS_EPHY_BOOKMARKS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EPHY_BOOKMARKS_TYPE))
+#define IS_EPHY_BOOKMARKS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EPHY_BOOKMARKS_TYPE))
+
+typedef struct EphyBookmarks EphyBookmarks;
+typedef struct EphyBookmarksPrivate EphyBookmarksPrivate;
+
+enum
+{
+ EPHY_NODE_BMK_PROP_TITLE = 2,
+ EPHY_NODE_BMK_PROP_LOCATION = 3,
+ EPHY_NODE_BMK_PROP_KEYWORDS = 4,
+ EPHY_NODE_KEYWORD_PROP_NAME = 5,
+ EPHY_NODE_BMK_PROP_SMART_LOCATION = 6
+};
+
+struct EphyBookmarks
+{
+ GObject parent;
+ EphyBookmarksPrivate *priv;
+};
+
+struct EphyBookmarksClass
+{
+ GObjectClass parent_class;
+};
+
+GType ephy_bookmarks_get_type (void);
+
+EphyBookmarks *ephy_bookmarks_new (void);
+
+/* Bookmarks */
+
+EphyNode *ephy_bookmarks_add (EphyBookmarks *eb,
+ const char *title,
+ const char *url,
+ const char *smart_url,
+ const char *keywords);
+
+char *ephy_bookmarks_solve_smart_url (EphyBookmarks *eb,
+ const char *smart_url,
+ const char *content);
+
+/* Keywords */
+
+EphyNode *ephy_bookmarks_add_keyword (EphyBookmarks *eb,
+ const char *name);
+
+EphyNode *ephy_bookmarks_find_keyword (EphyBookmarks *eb,
+ const char *name,
+ gboolean partial_match);
+
+void ephy_bookmarks_set_keyword (EphyBookmarks *eb,
+ EphyNode *keyword,
+ EphyNode *bookmark);
+
+void ephy_bookmarks_unset_keyword (EphyBookmarks *eb,
+ EphyNode *keyword,
+ EphyNode *bookmark);
+
+void ephy_bookmarks_update_keywords (EphyBookmarks *eb,
+ const char *keywords,
+ EphyNode *bookmark_node);
+
+/* Favorites */
+
+EphyNode *ephy_bookmarks_get_favorites (EphyBookmarks *eb);
+
+/* Root */
+
+EphyNode *ephy_bookmarks_get_keywords (EphyBookmarks *eb);
+
+EphyNode *ephy_bookmarks_get_bookmarks (EphyBookmarks *eb);
+
+G_END_DECLS
+
+#endif
diff --git a/src/bookmarks/ephy-keywords-entry.c b/src/bookmarks/ephy-keywords-entry.c
new file mode 100644
index 000000000..94d6c93a9
--- /dev/null
+++ b/src/bookmarks/ephy-keywords-entry.c
@@ -0,0 +1,286 @@
+/*
+ * 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 <gdk/gdkkeysyms.h>
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+#define NOT_IMPLEMENTED g_warning ("not implemented: " G_STRLOC);
+
+/**
+ * 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;
+ char *user_text;
+ const char *expand_text;
+ char *insert_text;
+ int user_text_length;
+ int expand_text_length;
+ int keyword_offset = 0;
+ int tmp;
+ EphyKeywordsEntry *entry = EPHY_KEYWORDS_ENTRY (editable);
+ EphyNode *node;
+
+ entry_text = gtk_editable_get_chars (editable, 0, -1);
+ g_return_if_fail (entry_text != NULL);
+
+ DEBUG_MSG (("Entry text \"%s\"\n", 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;
+ }
+
+ DEBUG_MSG (("User text \"%s\"\n", 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);
+
+ DEBUG_MSG (("Expand text %s\n", expand_text));
+
+ expand_text_length = g_utf8_strlen (expand_text, -1);
+ 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,
+ g_utf8_strlen (insert_text, -1),
+ &tmp);
+ gtk_editable_select_region (editable, user_text_length + keyword_offset, -1);
+ }
+ else
+ {
+ DEBUG_MSG (("No expansion.\n"));
+ }
+
+ 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;
+}
diff --git a/src/bookmarks/ephy-keywords-entry.h b/src/bookmarks/ephy-keywords-entry.h
new file mode 100644
index 000000000..5ab5df257
--- /dev/null
+++ b/src/bookmarks/ephy-keywords-entry.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2002 Ricardo Fernández Pascual
+ *
+ * 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.
+ */
+
+#ifndef EPHY_KEYWORDS_ENTRY_H
+#define EPHY_KEYWORDS_ENTRY_H
+
+#include "ephy-bookmarks.h"
+
+#include <glib-object.h>
+#include <gtk/gtkentry.h>
+
+/* object forward declarations */
+
+typedef struct _EphyKeywordsEntry EphyKeywordsEntry;
+typedef struct _EphyKeywordsEntryClass EphyKeywordsEntryClass;
+typedef struct _EphyKeywordsEntryPrivate EphyKeywordsEntryPrivate;
+
+/**
+ * EphyFolderTbWidget object
+ */
+
+#define EPHY_TYPE_LOCATION_ENTRY (ephy_keywords_entry_get_type())
+#define EPHY_KEYWORDS_ENTRY(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_LOCATION_ENTRY,\
+ EphyKeywordsEntry))
+#define EPHY_KEYWORDS_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_LOCATION_ENTRY,\
+ EphyKeywordsEntryClass))
+#define EPHY_IS_LOCATION_ENTRY(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_LOCATION_ENTRY))
+#define EPHY_IS_LOCATION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_LOCATION_ENTRY))
+#define EPHY_KEYWORDS_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_LOCATION_ENTRY,\
+ EphyKeywordsEntryClass))
+
+struct _EphyKeywordsEntryClass
+{
+ GtkEntryClass parent_class;
+
+ void (* keywords_changed) (EphyKeywordsEntry *entry);
+};
+
+struct _EphyKeywordsEntry
+{
+ GtkEntry parent_object;
+
+ EphyKeywordsEntryPrivate *priv;
+};
+
+GType ephy_keywords_entry_get_type (void);
+
+GtkWidget *ephy_keywords_entry_new (void);
+
+void ephy_keywords_entry_set_bookmarks (EphyKeywordsEntry *w,
+ EphyBookmarks *bookmarks);
+
+#endif
diff --git a/src/bookmarks/ephy-new-bookmark.c b/src/bookmarks/ephy-new-bookmark.c
new file mode 100644
index 000000000..ee0d4b983
--- /dev/null
+++ b/src/bookmarks/ephy-new-bookmark.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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 <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkeditable.h>
+#include <libgnome/gnome-i18n.h>
+
+#include "ephy-new-bookmark.h"
+#include "ephy-keywords-entry.h"
+
+//#define DEBUG_MSG(x) g_print x
+#define DEBUG_MSG(x)
+
+static void ephy_new_bookmark_class_init (EphyNewBookmarkClass *klass);
+static void ephy_new_bookmark_init (EphyNewBookmark *editor);
+static void ephy_new_bookmark_finalize (GObject *object);
+static void ephy_new_bookmark_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ephy_new_bookmark_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+struct EphyNewBookmarkPrivate
+{
+ EphyBookmarks *bookmarks;
+ char *location;
+ char *smarturl;
+
+ GtkWidget *title_entry;
+ GtkWidget *keywords_entry;
+};
+
+enum
+{
+ PROP_0,
+ PROP_BOOKMARKS,
+ PROP_LOCATION
+};
+
+static GObjectClass *parent_class = NULL;
+
+GType
+ephy_new_bookmark_get_type (void)
+{
+ static GType ephy_new_bookmark_type = 0;
+
+ if (ephy_new_bookmark_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyNewBookmarkClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_new_bookmark_class_init,
+ NULL,
+ NULL,
+ sizeof (EphyNewBookmark),
+ 0,
+ (GInstanceInitFunc) ephy_new_bookmark_init
+ };
+
+ ephy_new_bookmark_type = g_type_register_static (GTK_TYPE_DIALOG,
+ "EphyNewBookmark",
+ &our_info, 0);
+ }
+
+ return ephy_new_bookmark_type;
+}
+
+static void
+ephy_new_bookmark_class_init (EphyNewBookmarkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_new_bookmark_finalize;
+
+ object_class->set_property = ephy_new_bookmark_set_property;
+ object_class->get_property = ephy_new_bookmark_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_BOOKMARKS,
+ g_param_spec_object ("bookmarks",
+ "Bookmarks set",
+ "Bookmarks set",
+ EPHY_BOOKMARKS_TYPE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_LOCATION,
+ g_param_spec_string ("location",
+ "Bookmark location",
+ "Bookmark location",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ephy_new_bookmark_finalize (GObject *object)
+{
+ EphyNewBookmark *editor;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_NEW_BOOKMARK (object));
+
+ editor = EPHY_NEW_BOOKMARK (object);
+
+ g_return_if_fail (editor->priv != NULL);
+
+ g_free (editor->priv->location);
+ g_free (editor->priv->smarturl);
+
+ g_free (editor->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+ephy_new_bookmark_add (EphyNewBookmark *new_bookmark)
+{
+ char *title;
+ char *keywords;
+
+ title = gtk_editable_get_chars
+ (GTK_EDITABLE (new_bookmark->priv->title_entry), 0, -1);
+ keywords = gtk_editable_get_chars
+ (GTK_EDITABLE (new_bookmark->priv->keywords_entry), 0, -1);
+ ephy_bookmarks_add (new_bookmark->priv->bookmarks, title,
+ new_bookmark->priv->location,
+ new_bookmark->priv->smarturl, keywords);
+}
+
+static void
+ephy_new_bookmark_response_cb (GtkDialog *dialog,
+ int response_id,
+ EphyNewBookmark *new_bookmark)
+{
+ switch (response_id)
+ {
+ case GTK_RESPONSE_CANCEL:
+ break;
+ case GTK_RESPONSE_OK:
+ ephy_new_bookmark_add (new_bookmark);
+ break;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static GtkWidget *
+build_editing_table (EphyNewBookmark *editor)
+{
+ GtkWidget *table, *label, *entry;
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_widget_show (table);
+
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_markup (GTK_LABEL (label), _("<b>Title:</b>"));
+ gtk_widget_show (label);
+ entry = gtk_entry_new ();
+ editor->priv->title_entry = entry;
+ gtk_widget_set_size_request (entry, 200, -1);
+ gtk_widget_show (entry);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+ gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
+
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_markup (GTK_LABEL (label), _("<b>Keywords:</b>"));
+ gtk_widget_show (label);
+ entry = ephy_keywords_entry_new ();
+ ephy_keywords_entry_set_bookmarks (EPHY_KEYWORDS_ENTRY (entry),
+ editor->priv->bookmarks);
+ editor->priv->keywords_entry = entry;
+ gtk_widget_show (entry);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 1, 2);
+
+ return table;
+}
+
+static void
+ephy_new_bookmark_construct (EphyNewBookmark *editor)
+{
+ GtkWidget *hbox, *vbox;
+
+ gtk_window_set_title (GTK_WINDOW (editor),
+ _("Add bookmark"));
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (editor), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (editor), 6);
+ g_signal_connect (G_OBJECT (editor),
+ "response",
+ G_CALLBACK (ephy_new_bookmark_response_cb),
+ editor);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (editor)->vbox), 12);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (editor)->vbox),
+ hbox, TRUE, TRUE, 0);
+ gtk_widget_show (hbox);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ vbox, TRUE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ build_editing_table (editor),
+ FALSE, FALSE, 0);
+
+ gtk_dialog_add_button (GTK_DIALOG (editor),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (editor),
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK);
+ gtk_dialog_set_default_response (GTK_DIALOG (editor), GTK_RESPONSE_OK);
+}
+
+GtkWidget *
+ephy_new_bookmark_new (EphyBookmarks *bookmarks,
+ GtkWindow *parent,
+ const char *location)
+{
+ EphyNewBookmark *editor;
+
+ g_assert (bookmarks != NULL);
+
+ editor = EPHY_NEW_BOOKMARK (g_object_new
+ (EPHY_TYPE_NEW_BOOKMARK,
+ "bookmarks", bookmarks,
+ "location", location,
+ NULL));
+
+ if (parent)
+ {
+ gtk_window_set_transient_for (GTK_WINDOW (editor), parent);
+ }
+
+ ephy_new_bookmark_construct (editor);
+
+ return GTK_WIDGET (editor);
+}
+
+static void
+ephy_new_bookmark_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyNewBookmark *editor = EPHY_NEW_BOOKMARK (object);
+
+ switch (prop_id)
+ {
+ case PROP_BOOKMARKS:
+ editor->priv->bookmarks = g_value_get_object (value);
+ break;
+ case PROP_LOCATION:
+ g_free (editor->priv->location);
+ editor->priv->location = g_strdup (g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_new_bookmark_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyNewBookmark *editor = EPHY_NEW_BOOKMARK (object);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ g_value_set_string (value, editor->priv->location);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_new_bookmark_init (EphyNewBookmark *editor)
+{
+ editor->priv = g_new0 (EphyNewBookmarkPrivate, 1);
+ editor->priv->location = NULL;
+ editor->priv->smarturl = NULL;
+}
+
+void
+ephy_new_bookmark_set_title (EphyNewBookmark *bookmark,
+ const char *title)
+{
+ DEBUG_MSG (("Setting new bookmark title to: \"%s\"", title));
+ gtk_entry_set_text (GTK_ENTRY (bookmark->priv->title_entry),
+ g_strdup (title));
+}
+
+void
+ephy_new_bookmark_set_smarturl (EphyNewBookmark *bookmark,
+ const char *url)
+{
+ g_free (bookmark->priv->smarturl);
+ bookmark->priv->smarturl = g_strdup (url);
+}
+
diff --git a/src/bookmarks/ephy-new-bookmark.h b/src/bookmarks/ephy-new-bookmark.h
new file mode 100644
index 000000000..7e1091f50
--- /dev/null
+++ b/src/bookmarks/ephy-new-bookmark.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef EPHY_NEW_BOOKMARK_H
+#define EPHY_NEW_BOOKMARK_H
+
+#include <gtk/gtkdialog.h>
+
+#include "ephy-bookmarks.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_NEW_BOOKMARK (ephy_new_bookmark_get_type ())
+#define EPHY_NEW_BOOKMARK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_NEW_BOOKMARK, EphyNewBookmark))
+#define EPHY_NEW_BOOKMARK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_NEW_BOOKMARK, EphyNewBookmarkClass))
+#define EPHY_IS_NEW_BOOKMARK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_NEW_BOOKMARK))
+#define EPHY_IS_NEW_BOOKMARK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_NEW_BOOKMARK))
+#define EPHY_NEW_BOOKMARK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_NEW_BOOKMARK, EphyNewBookmarkClass))
+
+typedef struct EphyNewBookmarkPrivate EphyNewBookmarkPrivate;
+
+typedef struct
+{
+ GtkDialog parent;
+
+ EphyNewBookmarkPrivate *priv;
+} EphyNewBookmark;
+
+typedef struct
+{
+ GtkDialogClass parent;
+} EphyNewBookmarkClass;
+
+GType ephy_new_bookmark_get_type (void);
+
+GtkWidget *ephy_new_bookmark_new (EphyBookmarks *bookmarks,
+ GtkWindow *parent,
+ const char *location);
+
+void ephy_new_bookmark_set_title (EphyNewBookmark *bookmark,
+ const char *title);
+
+void ephy_new_bookmark_set_smarturl (EphyNewBookmark *bookmark,
+ const char *url);
+
+G_END_DECLS
+
+#endif /* EPHY_NEW_BOOKMARK_H */
diff --git a/src/bookmarks/ephy-node-view.c b/src/bookmarks/ephy-node-view.c
new file mode 100644
index 000000000..f537855d8
--- /dev/null
+++ b/src/bookmarks/ephy-node-view.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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 <gtk/gtktreeview.h>
+#include <gtk/gtktreeselection.h>
+#include <gtk/gtktreeviewcolumn.h>
+#include <gtk/gtkcellrenderertext.h>
+#include <libgnome/gnome-i18n.h>
+
+#include "eggtreemodelfilter.h"
+#include "ephy-tree-model-node.h"
+#include "ephy-node-view.h"
+#include "ephy-tree-model-sort.h"
+#include "eggtreemultidnd.h"
+#include "ephy-dnd.h"
+
+static void ephy_node_view_class_init (EphyNodeViewClass *klass);
+static void ephy_node_view_init (EphyNodeView *view);
+static void ephy_node_view_finalize (GObject *object);
+static void ephy_node_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ephy_node_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+struct EphyNodeViewPrivate
+{
+ EphyNode *root;
+
+ EphyTreeModelNode *nodemodel;
+ GtkTreeModel *filtermodel;
+ GtkTreeModel *sortmodel;
+
+ EphyNodeFilter *filter;
+
+ GtkWidget *treeview;
+};
+
+enum
+{
+ NODE_ACTIVATED,
+ NODE_SELECTED,
+ SHOW_POPUP,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_ROOT,
+ PROP_FILTER
+};
+
+static GObjectClass *parent_class = NULL;
+
+static guint ephy_node_view_signals[LAST_SIGNAL] = { 0 };
+
+GType
+ephy_node_view_get_type (void)
+{
+ static GType ephy_node_view_type = 0;
+
+ if (ephy_node_view_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyNodeViewClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_node_view_class_init,
+ NULL,
+ NULL,
+ sizeof (EphyNodeView),
+ 0,
+ (GInstanceInitFunc) ephy_node_view_init
+ };
+
+ ephy_node_view_type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
+ "EphyNodeView",
+ &our_info, 0);
+ }
+
+ return ephy_node_view_type;
+}
+
+static void
+ephy_node_view_class_init (EphyNodeViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_node_view_finalize;
+
+ object_class->set_property = ephy_node_view_set_property;
+ object_class->get_property = ephy_node_view_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ROOT,
+ g_param_spec_object ("root",
+ "Root node",
+ "Root node",
+ EPHY_TYPE_NODE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_FILTER,
+ g_param_spec_object ("filter",
+ "Filter object",
+ "Filter object",
+ EPHY_TYPE_NODE_FILTER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ ephy_node_view_signals[NODE_ACTIVATED] =
+ g_signal_new ("node_activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeViewClass, node_activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ EPHY_TYPE_NODE);
+ ephy_node_view_signals[NODE_SELECTED] =
+ g_signal_new ("node_selected",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeViewClass, node_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ EPHY_TYPE_NODE);
+ ephy_node_view_signals[SHOW_POPUP] =
+ g_signal_new ("show_popup",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EphyNodeViewClass, show_popup),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+ephy_node_view_finalize (GObject *object)
+{
+ EphyNodeView *view;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_NODE_VIEW (object));
+
+ view = EPHY_NODE_VIEW (object);
+
+ g_return_if_fail (view->priv != NULL);
+
+ g_object_unref (G_OBJECT (view->priv->sortmodel));
+ g_object_unref (G_OBJECT (view->priv->filtermodel));
+ g_object_unref (G_OBJECT (view->priv->nodemodel));
+
+ g_free (view->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+filter_changed_cb (EphyNodeFilter *filter,
+ EphyNodeView *view)
+{
+ GtkWidget *window;
+
+ g_return_if_fail (EPHY_IS_NODE_VIEW (view));
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+ if (window != NULL && window->window != NULL)
+ {
+ /* nice busy cursor */
+ GdkCursor *cursor;
+
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (window->window, cursor);
+ gdk_cursor_unref (cursor);
+
+ gdk_flush ();
+
+ gdk_window_set_cursor (window->window, NULL);
+
+ /* no flush: this will cause the cursor to be reset
+ * only when the UI is free again */
+ }
+}
+
+static void
+ephy_node_view_selection_changed_cb (GtkTreeSelection *selection,
+ EphyNodeView *view)
+{
+ GList *list;
+ EphyNode *node = NULL;
+
+ list = ephy_node_view_get_selection (view);
+ if (list)
+ {
+ node = EPHY_NODE (list->data);
+ }
+ g_list_free (list);
+
+ g_signal_emit (G_OBJECT (view), ephy_node_view_signals[NODE_SELECTED], 0, node);
+}
+
+static void
+ephy_node_view_row_activated_cb (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ EphyNodeView *view)
+{
+ GtkTreeIter iter, iter2;
+ EphyNode *node;
+
+ gtk_tree_model_get_iter (view->priv->sortmodel, &iter, path);
+ gtk_tree_model_sort_convert_iter_to_child_iter
+ (GTK_TREE_MODEL_SORT (view->priv->sortmodel), &iter2, &iter);
+ egg_tree_model_filter_convert_iter_to_child_iter
+ (EGG_TREE_MODEL_FILTER (view->priv->filtermodel), &iter, &iter2);
+
+ node = ephy_tree_model_node_node_from_iter (view->priv->nodemodel, &iter);
+
+ g_signal_emit (G_OBJECT (view), ephy_node_view_signals[NODE_ACTIVATED], 0, node);
+}
+
+static gboolean
+ephy_node_view_button_press_cb (GtkTreeView *treeview,
+ GdkEventButton *event,
+ EphyNodeView *view)
+{
+ if (event->button == 3)
+ {
+ g_signal_emit (G_OBJECT (view), ephy_node_view_signals[SHOW_POPUP], 0);
+ }
+
+ return FALSE;
+}
+
+static void
+ephy_node_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyNodeView *view = EPHY_NODE_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ view->priv->root = g_value_get_object (value);
+ break;
+ case PROP_FILTER:
+ view->priv->filter = g_value_get_object (value);
+
+ if (view->priv->filter != NULL)
+ {
+ g_signal_connect_object (G_OBJECT (view->priv->filter),
+ "changed",
+ G_CALLBACK (filter_changed_cb),
+ G_OBJECT (view),
+ 0);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_node_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyNodeView *view = EPHY_NODE_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ g_value_set_object (value, view->priv->root);
+ break;
+ case PROP_FILTER:
+ g_value_set_object (value, view->priv->filter);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+node_from_sort_iter_cb (EphyTreeModelSort *model,
+ GtkTreeIter *iter,
+ void **node,
+ EphyNodeView *view)
+{
+ GtkTreeIter filter_iter, node_iter;
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model),
+ &filter_iter, iter);
+ egg_tree_model_filter_convert_iter_to_child_iter (EGG_TREE_MODEL_FILTER (view->priv->filtermodel),
+ &node_iter, &filter_iter);
+ *node = ephy_tree_model_node_node_from_iter
+ (EPHY_TREE_MODEL_NODE (view->priv->nodemodel), &node_iter);
+}
+
+static void
+ephy_node_view_construct (EphyNodeView *view)
+{
+ GtkTreeSelection *selection;
+
+
+ view->priv->nodemodel = ephy_tree_model_node_new (view->priv->root,
+ view->priv->filter);
+ view->priv->filtermodel = egg_tree_model_filter_new (GTK_TREE_MODEL (view->priv->nodemodel),
+ NULL);
+ egg_tree_model_filter_set_visible_column (EGG_TREE_MODEL_FILTER (view->priv->filtermodel),
+ EPHY_TREE_MODEL_NODE_COL_VISIBLE);
+ view->priv->sortmodel = ephy_tree_model_sort_new (view->priv->filtermodel);
+ g_signal_connect_object (G_OBJECT (view->priv->sortmodel),
+ "node_from_iter",
+ G_CALLBACK (node_from_sort_iter_cb),
+ view,
+ 0);
+ view->priv->treeview = gtk_tree_view_new_with_model
+ (GTK_TREE_MODEL (view->priv->sortmodel));
+ gtk_widget_show (view->priv->treeview);
+ g_signal_connect_object (G_OBJECT (view->priv->treeview),
+ "button_press_event",
+ G_CALLBACK (ephy_node_view_button_press_cb),
+ view,
+ 0);
+ g_signal_connect_object (G_OBJECT (view->priv->treeview),
+ "row_activated",
+ G_CALLBACK (ephy_node_view_row_activated_cb),
+ view,
+ 0);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+ g_signal_connect_object (G_OBJECT (selection),
+ "changed",
+ G_CALLBACK (ephy_node_view_selection_changed_cb),
+ view,
+ 0);
+
+ gtk_container_add (GTK_CONTAINER (view), view->priv->treeview);
+}
+
+EphyNodeView *
+ephy_node_view_new (EphyNode *root,
+ EphyNodeFilter *filter)
+{
+ EphyNodeView *view;
+
+ view = EPHY_NODE_VIEW (g_object_new (EPHY_TYPE_NODE_VIEW,
+ "filter", filter,
+ "hadjustment", NULL,
+ "vadjustment", NULL,
+ "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
+ "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
+ "shadow_type", GTK_SHADOW_IN,
+ "root", root,
+ NULL));
+
+ ephy_node_view_construct (view);
+
+ g_return_val_if_fail (view->priv != NULL, NULL);
+
+ return view;
+}
+
+void
+ephy_node_view_add_column (EphyNodeView *view,
+ const char *title,
+ EphyTreeModelNodeColumn column,
+ gboolean sortable)
+{
+ GtkTreeViewColumn *gcolumn;
+ GtkCellRenderer *renderer;
+
+ gcolumn = (GtkTreeViewColumn *) gtk_tree_view_column_new ();
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (gcolumn, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (gcolumn, renderer,
+ "text", column,
+ NULL);
+ gtk_tree_view_column_set_sizing (gcolumn,
+ GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_column_set_title (gcolumn, title);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview),
+ gcolumn);
+ if (sortable)
+ {
+ gtk_tree_view_column_set_sort_column_id (gcolumn, column);
+ }
+}
+
+static void
+ephy_node_view_init (EphyNodeView *view)
+{
+ view->priv = g_new0 (EphyNodeViewPrivate, 1);
+}
+
+static void
+get_selection (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ void **data)
+{
+ GtkTreeModelSort *sortmodel = GTK_TREE_MODEL_SORT (model);
+ EggTreeModelFilter *filtermodel = EGG_TREE_MODEL_FILTER (sortmodel->child_model);
+ EphyTreeModelNode *nodemodel = EPHY_TREE_MODEL_NODE (filtermodel->child_model);
+ GList **list = (GList **) data;
+ GtkTreeIter *iter2 = gtk_tree_iter_copy (iter);
+ GtkTreeIter iter3;
+ GtkTreeIter iter4;
+ EphyNode *node;
+
+ gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model),
+ &iter3, iter2);
+ egg_tree_model_filter_convert_iter_to_child_iter (filtermodel, &iter4, &iter3);
+
+ node = ephy_tree_model_node_node_from_iter (nodemodel, &iter4);
+
+ gtk_tree_iter_free (iter2);
+
+ *list = g_list_prepend (*list, node);
+}
+
+GList *
+ephy_node_view_get_selection (EphyNodeView *view)
+{
+ GList *list = NULL;
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection
+ (GTK_TREE_VIEW (view->priv->treeview));
+
+ gtk_tree_selection_selected_foreach (selection,
+ (GtkTreeSelectionForeachFunc) get_selection,
+ (void **) &list);
+
+ return list;
+}
+
+void
+ephy_node_view_remove (EphyNodeView *view)
+{
+ GList *list;
+
+ list = ephy_node_view_get_selection (view);
+
+ for (; list != NULL; list = list->next)
+ {
+ ephy_node_unref (EPHY_NODE (list->data));
+ }
+
+ g_list_free (list);
+}
+
+void
+ephy_node_view_set_browse_mode (EphyNodeView *view)
+{
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+}
+
+void
+ephy_node_view_select_node (EphyNodeView *view,
+ EphyNode *node)
+{
+ GtkTreeIter iter, iter2;
+ GValue val = { 0, };
+ gboolean visible;
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
+
+ g_return_if_fail (node != NULL);
+
+ ephy_tree_model_node_iter_from_node (EPHY_TREE_MODEL_NODE (view->priv->nodemodel),
+ node, &iter);
+ gtk_tree_model_get_value (GTK_TREE_MODEL (view->priv->nodemodel), &iter,
+ EPHY_TREE_MODEL_NODE_COL_VISIBLE, &val);
+ visible = g_value_get_boolean (&val);
+ g_value_unset (&val);
+ if (visible == FALSE) return;
+
+ egg_tree_model_filter_convert_child_iter_to_iter (EGG_TREE_MODEL_FILTER (view->priv->filtermodel),
+ &iter2, &iter);
+ gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT (view->priv->sortmodel),
+ &iter, &iter2);
+
+ gtk_tree_selection_select_iter (selection, &iter);
+}
+
+void
+ephy_node_view_enable_drag_source (EphyNodeView *view)
+{
+ g_return_if_fail (view != NULL);
+
+ egg_tree_multi_drag_add_drag_support (GTK_TREE_VIEW (view->priv->treeview));
+ ephy_dnd_enable_model_drag_source (GTK_WIDGET (view->priv->treeview));
+}
diff --git a/src/bookmarks/ephy-node-view.h b/src/bookmarks/ephy-node-view.h
new file mode 100644
index 000000000..71f19d7c7
--- /dev/null
+++ b/src/bookmarks/ephy-node-view.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef __EPHY_NODE_VIEW_H
+#define __EPHY_NODE_VIEW_H
+
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtkdnd.h>
+
+#include "ephy-tree-model-node.h"
+#include "ephy-node-filter.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_NODE_VIEW (ephy_node_view_get_type ())
+#define EPHY_NODE_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_NODE_VIEW, EphyNodeView))
+#define EPHY_NODE_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_NODE_VIEW, EphyNodeViewClass))
+#define EPHY_IS_NODE_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_NODE_VIEW))
+#define EPHY_IS_NODE_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_NODE_VIEW))
+#define EPHY_NODE_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_NODE_VIEW, EphyNodeViewClass))
+
+typedef struct EphyNodeViewPrivate EphyNodeViewPrivate;
+
+typedef struct
+{
+ GtkScrolledWindow parent;
+
+ EphyNodeViewPrivate *priv;
+} EphyNodeView;
+
+typedef struct
+{
+ GtkScrolledWindowClass parent;
+
+ void (*node_activated) (EphyNodeView *view, EphyNode *node);
+ void (*node_selected) (EphyNodeView *view, EphyNode *node);
+ void (*show_popup) (EphyNodeView *view);
+} EphyNodeViewClass;
+
+GType ephy_node_view_get_type (void);
+
+EphyNodeView *ephy_node_view_new (EphyNode *root,
+ EphyNodeFilter *filter);
+
+void ephy_node_view_enable_dnd (EphyNodeView *view);
+
+void ephy_node_view_add_column (EphyNodeView *view,
+ const char *title,
+ EphyTreeModelNodeColumn column,
+ gboolean sortable);
+
+void ephy_node_view_remove (EphyNodeView *view);
+
+GList *ephy_node_view_get_selection (EphyNodeView *view);
+
+void ephy_node_view_set_browse_mode (EphyNodeView *view);
+
+void ephy_node_view_select_node (EphyNodeView *view,
+ EphyNode *node);
+
+void ephy_node_view_enable_drag_source (EphyNodeView *view);
+
+G_END_DECLS
+
+#endif /* EPHY_NODE_VIEW_H */
diff --git a/src/bookmarks/ephy-tree-model-node.c b/src/bookmarks/ephy-tree-model-node.c
new file mode 100644
index 000000000..d1030d5da
--- /dev/null
+++ b/src/bookmarks/ephy-tree-model-node.c
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#include <config.h>
+#include <gtk/gtktreeview.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libgnome/gnome-i18n.h>
+#include <time.h>
+#include <string.h>
+
+#include "ephy-node-filter.h"
+#include "ephy-bookmarks.h"
+#include "ephy-tree-model-node.h"
+#include "ephy-stock-icons.h"
+#include "ephy-node.h"
+
+static void ephy_tree_model_node_class_init (EphyTreeModelNodeClass *klass);
+static void ephy_tree_model_node_init (EphyTreeModelNode *model);
+static void ephy_tree_model_node_finalize (GObject *object);
+static void ephy_tree_model_node_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void ephy_tree_model_node_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+static guint ephy_tree_model_node_get_flags (GtkTreeModel *tree_model);
+static int ephy_tree_model_node_get_n_columns (GtkTreeModel *tree_model);
+static GType ephy_tree_model_node_get_column_type (GtkTreeModel *tree_model,
+ int index);
+static gboolean ephy_tree_model_node_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+static GtkTreePath *ephy_tree_model_node_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static void ephy_tree_model_node_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ int column,
+ GValue *value);
+static gboolean ephy_tree_model_node_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean ephy_tree_model_node_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+static gboolean ephy_tree_model_node_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static int ephy_tree_model_node_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean ephy_tree_model_node_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ int n);
+static gboolean ephy_tree_model_node_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+static void ephy_tree_model_node_tree_model_init (GtkTreeModelIface *iface);
+static void root_child_removed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyTreeModelNode *model);
+static void root_child_added_cb (EphyNode *node,
+ EphyNode *child,
+ EphyTreeModelNode *model);
+static void root_child_changed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyTreeModelNode *model);
+static inline void ephy_tree_model_node_update_node (EphyTreeModelNode *model,
+ EphyNode *node,
+ int idx);
+static void root_destroyed_cb (EphyNode *node,
+ EphyTreeModelNode *model);
+static inline GtkTreePath *get_path_real (EphyTreeModelNode *model,
+ EphyNode *node);
+
+struct EphyTreeModelNodePrivate
+{
+ EphyNode *root;
+
+ EphyNodeFilter *filter;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ROOT,
+ PROP_FILTER
+};
+
+static GObjectClass *parent_class = NULL;
+
+GType
+ephy_tree_model_node_get_type (void)
+{
+ static GType ephy_tree_model_node_type = 0;
+
+ if (ephy_tree_model_node_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EphyTreeModelNodeClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_tree_model_node_class_init,
+ NULL,
+ NULL,
+ sizeof (EphyTreeModelNode),
+ 0,
+ (GInstanceInitFunc) ephy_tree_model_node_init
+ };
+
+ static const GInterfaceInfo tree_model_info =
+ {
+ (GInterfaceInitFunc) ephy_tree_model_node_tree_model_init,
+ NULL,
+ NULL
+ };
+
+ ephy_tree_model_node_type = g_type_register_static (G_TYPE_OBJECT,
+ "EphyTreeModelNode",
+ &our_info, 0);
+
+ g_type_add_interface_static (ephy_tree_model_node_type,
+ GTK_TYPE_TREE_MODEL,
+ &tree_model_info);
+ }
+
+ return ephy_tree_model_node_type;
+}
+
+static void
+ephy_tree_model_node_class_init (EphyTreeModelNodeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_tree_model_node_finalize;
+
+ object_class->set_property = ephy_tree_model_node_set_property;
+ object_class->get_property = ephy_tree_model_node_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ROOT,
+ g_param_spec_object ("root",
+ "Root node",
+ "Root node",
+ EPHY_TYPE_NODE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_FILTER,
+ g_param_spec_object ("filter",
+ "Filter object",
+ "Filter object",
+ EPHY_TYPE_NODE_FILTER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+ephy_tree_model_node_init (EphyTreeModelNode *model)
+{
+ GtkWidget *dummy;
+
+ do
+ {
+ model->stamp = g_random_int ();
+ }
+ while (model->stamp == 0);
+
+ model->priv = g_new0 (EphyTreeModelNodePrivate, 1);
+
+ dummy = gtk_tree_view_new ();
+
+ gtk_widget_destroy (dummy);
+}
+
+static void
+ephy_tree_model_node_finalize (GObject *object)
+{
+ EphyTreeModelNode *model;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (EPHY_IS_TREE_MODEL_NODE (object));
+
+ model = EPHY_TREE_MODEL_NODE (object);
+
+ g_return_if_fail (model->priv != NULL);
+
+ g_free (model->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+filter_changed_cb (EphyNodeFilter *filter,
+ EphyTreeModelNode *model)
+{
+ GPtrArray *kids;
+ int i;
+
+ kids = ephy_node_get_children (model->priv->root);
+
+ for (i = 0; i < kids->len; i++)
+ {
+ ephy_tree_model_node_update_node (model,
+ g_ptr_array_index (kids, i),
+ i);
+ }
+
+ ephy_node_thaw (model->priv->root);
+}
+
+static void
+ephy_tree_model_node_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ model->priv->root = g_value_get_object (value);
+
+ g_signal_connect_object (G_OBJECT (model->priv->root),
+ "child_added",
+ G_CALLBACK (root_child_added_cb),
+ G_OBJECT (model),
+ 0);
+ g_signal_connect_object (G_OBJECT (model->priv->root),
+ "child_removed",
+ G_CALLBACK (root_child_removed_cb),
+ G_OBJECT (model),
+ 0);
+ g_signal_connect_object (G_OBJECT (model->priv->root),
+ "child_changed",
+ G_CALLBACK (root_child_changed_cb),
+ G_OBJECT (model),
+ 0);
+ g_signal_connect_object (G_OBJECT (model->priv->root),
+ "destroyed",
+ G_CALLBACK (root_destroyed_cb),
+ G_OBJECT (model),
+ 0);
+
+ break;
+ case PROP_FILTER:
+ model->priv->filter = g_value_get_object (value);
+
+ if (model->priv->filter != NULL)
+ {
+ g_signal_connect_object (G_OBJECT (model->priv->filter),
+ "changed",
+ G_CALLBACK (filter_changed_cb),
+ G_OBJECT (model),
+ 0);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_tree_model_node_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ g_value_set_object (value, model->priv->root);
+ break;
+ case PROP_FILTER:
+ g_value_set_object (value, model->priv->filter);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+EphyTreeModelNode *
+ephy_tree_model_node_new (EphyNode *root,
+ EphyNodeFilter *filter)
+{
+ EphyTreeModelNode *model;
+
+ model = EPHY_TREE_MODEL_NODE (g_object_new (EPHY_TYPE_TREE_MODEL_NODE,
+ "filter", filter,
+ "root", root,
+ NULL));
+
+ g_return_val_if_fail (model->priv != NULL, NULL);
+
+ return model;
+}
+
+static void
+ephy_tree_model_node_tree_model_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = ephy_tree_model_node_get_flags;
+ iface->get_n_columns = ephy_tree_model_node_get_n_columns;
+ iface->get_column_type = ephy_tree_model_node_get_column_type;
+ iface->get_iter = ephy_tree_model_node_get_iter;
+ iface->get_path = ephy_tree_model_node_get_path;
+ iface->get_value = ephy_tree_model_node_get_value;
+ iface->iter_next = ephy_tree_model_node_iter_next;
+ iface->iter_children = ephy_tree_model_node_iter_children;
+ iface->iter_has_child = ephy_tree_model_node_iter_has_child;
+ iface->iter_n_children = ephy_tree_model_node_iter_n_children;
+ iface->iter_nth_child = ephy_tree_model_node_iter_nth_child;
+ iface->iter_parent = ephy_tree_model_node_iter_parent;
+}
+
+static guint
+ephy_tree_model_node_get_flags (GtkTreeModel *tree_model)
+{
+ return 0;
+}
+
+static int
+ephy_tree_model_node_get_n_columns (GtkTreeModel *tree_model)
+{
+ return EPHY_TREE_MODEL_NODE_NUM_COLUMNS;
+}
+
+static GType
+ephy_tree_model_node_get_column_type (GtkTreeModel *tree_model,
+ int index)
+{
+ g_return_val_if_fail (EPHY_IS_TREE_MODEL_NODE (tree_model), G_TYPE_INVALID);
+ g_return_val_if_fail ((index < EPHY_TREE_MODEL_NODE_NUM_COLUMNS) && (index >= 0), G_TYPE_INVALID);
+
+ switch (index)
+ {
+ case EPHY_TREE_MODEL_NODE_COL_BOOKMARK:
+ case EPHY_TREE_MODEL_NODE_COL_LOCATION:
+ case EPHY_TREE_MODEL_NODE_COL_KEYWORD:
+ return G_TYPE_STRING;
+ case EPHY_TREE_MODEL_NODE_COL_VISIBLE:
+ return G_TYPE_BOOLEAN;
+ default:
+ g_assert_not_reached ();
+ return G_TYPE_INVALID;
+ }
+}
+
+static gboolean
+ephy_tree_model_node_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+ int i;
+
+ g_return_val_if_fail (EPHY_IS_TREE_MODEL_NODE (model), FALSE);
+ g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+
+ if (model->priv->root == NULL)
+ return FALSE;
+
+ i = gtk_tree_path_get_indices (path)[0];
+
+ iter->stamp = model->stamp;
+ iter->user_data = ephy_node_get_nth_child (model->priv->root, i);
+
+ if (iter->user_data == NULL)
+ {
+ iter->stamp = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static inline GtkTreePath *
+get_path_real (EphyTreeModelNode *model,
+ EphyNode *node)
+{
+ GtkTreePath *retval;
+
+ retval = gtk_tree_path_new ();
+ gtk_tree_path_append_index (retval, ephy_node_get_child_index (model->priv->root, node));
+
+ return retval;
+}
+
+static GtkTreePath *
+ephy_tree_model_node_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+ EphyNode *node;
+
+ g_return_val_if_fail (EPHY_IS_TREE_MODEL_NODE (tree_model), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+ g_return_val_if_fail (iter->user_data != NULL, NULL);
+ g_return_val_if_fail (iter->stamp == model->stamp, NULL);
+
+ if (model->priv->root == NULL)
+ return NULL;
+
+ node = EPHY_NODE (iter->user_data);
+
+ if (node == model->priv->root)
+ return gtk_tree_path_new ();
+
+ return get_path_real (model, node);
+}
+
+static void
+ephy_tree_model_node_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ int column,
+ GValue *value)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+ EphyNode *node;
+
+ g_return_if_fail (EPHY_IS_TREE_MODEL_NODE (tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->stamp == model->stamp);
+ g_return_if_fail (EPHY_IS_NODE (iter->user_data));
+ g_return_if_fail (column < EPHY_TREE_MODEL_NODE_NUM_COLUMNS);
+
+ if (model->priv->root == NULL)
+ return;
+
+ node = EPHY_NODE (iter->user_data);
+
+ switch (column)
+ {
+ case EPHY_TREE_MODEL_NODE_COL_BOOKMARK:
+ ephy_node_get_property (node,
+ EPHY_NODE_BMK_PROP_TITLE,
+ value);
+ break;
+ case EPHY_TREE_MODEL_NODE_COL_LOCATION:
+ ephy_node_get_property (node,
+ EPHY_NODE_BMK_PROP_LOCATION,
+ value);
+ break;
+ case EPHY_TREE_MODEL_NODE_COL_KEYWORD:
+ ephy_node_get_property (node,
+ EPHY_NODE_KEYWORD_PROP_NAME,
+ value);
+ break;
+ case EPHY_TREE_MODEL_NODE_COL_VISIBLE:
+ g_value_init (value, G_TYPE_BOOLEAN);
+
+ if (model->priv->filter != NULL)
+ {
+ g_value_set_boolean (value,
+ ephy_node_filter_evaluate (model->priv->filter, node));
+ }
+ else
+ {
+ g_value_set_boolean (value, TRUE);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+ephy_tree_model_node_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+ EphyNode *node;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (iter->user_data != NULL, FALSE);
+ g_return_val_if_fail (iter->stamp == EPHY_TREE_MODEL_NODE (tree_model)->stamp, FALSE);
+
+ if (model->priv->root == NULL)
+ return FALSE;
+
+ node = EPHY_NODE (iter->user_data);
+
+ if (node == model->priv->root)
+ return FALSE;
+
+ iter->user_data = ephy_node_get_next_child (model->priv->root, node);
+
+ return (iter->user_data != NULL);
+}
+
+static gboolean
+ephy_tree_model_node_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+
+ if (model->priv->root == NULL)
+ return FALSE;
+
+ if (parent != NULL)
+ return FALSE;
+
+ iter->stamp = model->stamp;
+ iter->user_data = model->priv->root;
+
+ return TRUE;
+}
+
+static gboolean
+ephy_tree_model_node_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ return FALSE;
+}
+
+static int
+ephy_tree_model_node_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+
+ g_return_val_if_fail (EPHY_IS_TREE_MODEL_NODE (tree_model), -1);
+
+ if (model->priv->root == NULL)
+ return 0;
+
+ if (iter == NULL)
+ return ephy_node_get_n_children (model->priv->root);
+
+ g_return_val_if_fail (model->stamp == iter->stamp, -1);
+
+ return 0;
+}
+
+static gboolean
+ephy_tree_model_node_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ int n)
+{
+ EphyTreeModelNode *model = EPHY_TREE_MODEL_NODE (tree_model);
+ EphyNode *node;
+
+ g_return_val_if_fail (EPHY_IS_TREE_MODEL_NODE (tree_model), FALSE);
+
+ if (model->priv->root == NULL)
+ return FALSE;
+
+ if (parent != NULL)
+ return FALSE;
+
+ node = ephy_node_get_nth_child (model->priv->root, n);
+
+ if (node != NULL)
+ {
+ iter->stamp = model->stamp;
+ iter->user_data = node;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+ephy_tree_model_node_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ return FALSE;
+}
+
+EphyNode *
+ephy_tree_model_node_node_from_iter (EphyTreeModelNode *model,
+ GtkTreeIter *iter)
+{
+ return EPHY_NODE (iter->user_data);
+}
+
+void
+ephy_tree_model_node_iter_from_node (EphyTreeModelNode *model,
+ EphyNode *node,
+ GtkTreeIter *iter)
+{
+ iter->stamp = model->stamp;
+ iter->user_data = node;
+}
+
+static void
+root_child_removed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyTreeModelNode *model)
+{
+ GtkTreePath *path;
+
+ path = get_path_real (model, child);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+}
+
+static void
+root_child_added_cb (EphyNode *node,
+ EphyNode *child,
+ EphyTreeModelNode *model)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ ephy_tree_model_node_iter_from_node (model, child, &iter);
+
+ path = get_path_real (model, child);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+}
+
+static inline void
+ephy_tree_model_node_update_node (EphyTreeModelNode *model,
+ EphyNode *node,
+ int idx)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ ephy_tree_model_node_iter_from_node (model, node, &iter);
+
+ if (idx >= 0)
+ {
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, idx);
+ }
+ else
+ {
+ path = get_path_real (model, node);
+ }
+
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
+ gtk_tree_path_free (path);
+}
+
+static void
+root_child_changed_cb (EphyNode *node,
+ EphyNode *child,
+ EphyTreeModelNode *model)
+{
+ ephy_tree_model_node_update_node (model, child, -1);
+}
+
+static void
+root_destroyed_cb (EphyNode *node,
+ EphyTreeModelNode *model)
+{
+ model->priv->root = NULL;
+
+ /* no need to do other stuff since we should have had a bunch of child_removed
+ * signals already */
+}
+
+GType
+ephy_tree_model_node_column_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0)
+ {
+ static const GEnumValue values[] =
+ {
+ { EPHY_TREE_MODEL_NODE_COL_BOOKMARK, "EPHY_TREE_MODEL_NODE_COL_BOOKMARK", "bookmark" },
+ { EPHY_TREE_MODEL_NODE_COL_LOCATION, "EPHY_TREE_MODEL_NODE_COL_LOCATION", "location" },
+ { EPHY_TREE_MODEL_NODE_COL_KEYWORD, "EPHY_TREE_MODEL_NODE_COL_KEYWORD", "keyword" },
+ { EPHY_TREE_MODEL_NODE_COL_VISIBLE, "EPHY_TREE_MODEL_NODE_COL_VISIBLE", "visible" },
+
+ { 0, 0, 0 }
+ };
+
+ etype = g_enum_register_static ("EphyTreeModelNodeColumn", values);
+ }
+
+ return etype;
+}
+
diff --git a/src/bookmarks/ephy-tree-model-node.h b/src/bookmarks/ephy-tree-model-node.h
new file mode 100644
index 000000000..a48bc6ad5
--- /dev/null
+++ b/src/bookmarks/ephy-tree-model-node.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
+ *
+ * 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.
+ *
+ * $Id$
+ */
+
+#ifndef __EPHY_TREE_MODEL_NODE_H
+#define __EPHY_TREE_MODEL_NODE_H
+
+#include <gtk/gtktreemodel.h>
+
+#include "ephy-node.h"
+#include "ephy-node-filter.h"
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_TREE_MODEL_NODE (ephy_tree_model_node_get_type ())
+#define EPHY_TREE_MODEL_NODE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_TREE_MODEL_NODE, EphyTreeModelNode))
+#define EPHY_TREE_MODEL_NODE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_TREE_MODEL_NODE, EphyTreeModelNodeClass))
+#define EPHY_IS_TREE_MODEL_NODE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_TREE_MODEL_NODE))
+#define EPHY_IS_TREE_MODEL_NODE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_TREE_MODEL_NODE))
+#define EPHY_TREE_MODEL_NODE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_TREE_MODEL_NODE, EphyTreeModelNodeClass))
+
+typedef enum
+{
+ EPHY_TREE_MODEL_NODE_COL_BOOKMARK,
+ EPHY_TREE_MODEL_NODE_COL_LOCATION,
+ EPHY_TREE_MODEL_NODE_COL_KEYWORD,
+ EPHY_TREE_MODEL_NODE_COL_VISIBLE,
+ EPHY_TREE_MODEL_NODE_NUM_COLUMNS
+} EphyTreeModelNodeColumn;
+
+GType ephy_tree_model_node_column_get_type (void);
+
+#define EPHY_TYPE_TREE_MODEL_NODE_COLUMN (ephy_tree_model_node_column_get_type ())
+
+typedef struct EphyTreeModelNodePrivate EphyTreeModelNodePrivate;
+
+typedef struct
+{
+ GObject parent;
+
+ EphyTreeModelNodePrivate *priv;
+
+ int stamp;
+} EphyTreeModelNode;
+
+typedef struct
+{
+ GObjectClass parent;
+} EphyTreeModelNodeClass;
+
+GType ephy_tree_model_node_get_type (void);
+
+EphyTreeModelNode *ephy_tree_model_node_new (EphyNode *root,
+ EphyNodeFilter *filter);
+
+EphyNode *ephy_tree_model_node_node_from_iter (EphyTreeModelNode *model,
+ GtkTreeIter *iter);
+
+void ephy_tree_model_node_iter_from_node (EphyTreeModelNode *model,
+ EphyNode *node,
+ GtkTreeIter *iter);
+
+G_END_DECLS
+
+#endif /* EPHY_TREE_MODEL_NODE_H */