]> spindle.queued.net Git - midori/commitdiff
Initial version of the Feed Panel extension
authorDale Whittaker <dayul@users.sf.net>
Sat, 25 Apr 2009 16:15:08 +0000 (18:15 +0200)
committerChristian Dywan <christian@twotoasts.de>
Sat, 25 Apr 2009 16:15:08 +0000 (18:15 +0200)
extensions/feed-panel/feed-atom.c [new file with mode: 0644]
extensions/feed-panel/feed-atom.h [new file with mode: 0644]
extensions/feed-panel/feed-panel.c [new file with mode: 0644]
extensions/feed-panel/feed-panel.h [new file with mode: 0644]
extensions/feed-panel/feed-parse.c [new file with mode: 0644]
extensions/feed-panel/feed-parse.h [new file with mode: 0644]
extensions/feed-panel/feed-rss.c [new file with mode: 0644]
extensions/feed-panel/feed-rss.h [new file with mode: 0644]
extensions/feed-panel/main.c [new file with mode: 0644]

diff --git a/extensions/feed-panel/feed-atom.c b/extensions/feed-panel/feed-atom.c
new file mode 100644 (file)
index 0000000..9731d68
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#include "feed-atom.h"
+
+#define atom_get_link_attribute(item, attribute) \
+    (gchar*)g_object_get_data (G_OBJECT (item), attribute)
+
+#define atom_set_link_attribute(item, attribute, value) \
+    g_object_set_data (G_OBJECT (item), attribute, value)
+
+static gboolean
+atom_is_valid (FeedParser* fparser)
+{
+    xmlNodePtr node;
+
+    node = fparser->node;
+
+    if (!(xmlStrcmp (node->name, BAD_CAST "feed")) &&
+        !(xmlStrcmp (node->ns->href, BAD_CAST "http://www.w3.org/2005/Atom"))
+       )
+        return TRUE;
+
+    return FALSE;
+}
+
+static gboolean
+atom_update (FeedParser* fparser)
+{
+    xmlNodePtr node;
+    xmlNodePtr child;
+    gint64 date;
+    gint64 newdate;
+
+    date = katze_item_get_added (fparser->item);
+
+    node = fparser->node;
+    child = node->children;
+    while (child)
+    {
+        if (child->type == XML_ELEMENT_NODE)
+        {
+            if (!(xmlStrcmp (child->name, BAD_CAST "updated")))
+            {
+                fparser->node = child;
+                newdate = feed_get_element_date (fparser);
+                fparser->node = node;
+                return (date != newdate);
+            }
+        }
+        child = child->next;
+    }
+    return TRUE;
+}
+
+static gboolean
+atom_preferred_link (const gchar* old,
+                     const gchar* new)
+{
+    guint i;
+    gint iold;
+    gint inew;
+    gchar* rels[5] =
+    {
+        "enclosure",
+        "via",
+        "related",
+        "alternate",
+        "self",
+    };
+
+    iold = inew = -1;
+    for (i = 0; i < 5; i++)
+    {
+        if (old && g_str_equal (old, rels[i]))
+            iold = i;
+        if (new && g_str_equal (new, rels[i]))
+            inew = i;
+    }
+    return (inew > iold);
+}
+
+static void
+atom_get_link (KatzeItem* item,
+               xmlNodePtr node)
+{
+    gchar* oldtype;
+    gchar* newtype;
+    gchar* oldrel;
+    gchar* newrel;
+    gchar* href;
+    gboolean oldishtml;
+    gboolean newishtml;
+    gboolean newlink;
+
+    newlink = FALSE;
+    oldtype = atom_get_link_attribute (item, "linktype");
+    oldrel = atom_get_link_attribute (item, "linkrel");
+
+    newtype = (gchar*)xmlGetProp (node, BAD_CAST "type");
+    newrel = (gchar*)xmlGetProp (node, BAD_CAST "rel");
+    href = (gchar*)xmlGetProp (node, BAD_CAST "href");
+
+    if (!newrel)
+        newrel = g_strdup ("alternate");
+
+    oldishtml = (oldtype && g_str_equal (oldtype, "text/html"));
+    newishtml = (newtype && g_str_equal (newtype, "text/html"));
+
+    /* prefer HTML links over anything else.
+     * if the previous link was already HTML, decide which link
+     * we prefer.
+     */
+    if ((newishtml && oldishtml) || (!newishtml && !oldishtml))
+        newlink = atom_preferred_link (oldrel, newrel);
+    else
+        newlink = newishtml;
+
+    if (newlink)
+    {
+        katze_item_set_uri (item, href);
+        atom_set_link_attribute (item, "linkrel", newrel);
+        atom_set_link_attribute (item, "linktype", newrel);
+    }
+    else
+    {
+        xmlFree (href);
+        xmlFree (newrel);
+        xmlFree (newtype);
+    }
+}
+
+static void
+atom_preparse_entry (FeedParser* fparser)
+{
+    fparser->item = katze_item_new ();
+}
+
+static void
+atom_parse_entry (FeedParser* fparser)
+{
+    xmlNodePtr node;
+    gchar* content;
+    gint64 date;
+
+    node = fparser->node;
+    content = NULL;
+
+    if (!xmlStrcmp (node->name, BAD_CAST "id"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_token (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "title"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_name (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "summary"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_text (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "updated"))
+    {
+        date = feed_get_element_date (fparser);
+        katze_item_set_added (fparser->item, date);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "icon"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_icon (fparser->item, content);
+    }
+    /* FIXME content can be used in some cases where there
+     * is no summary, but it needs additional work,
+     * as it can be HTML, or base64 encoded.
+     * see the spec.
+     */
+    else if (!xmlStrcmp (node->name, BAD_CAST "content"))
+    {
+        /* Only retrieve content if there is no summary */
+        if (!katze_item_get_text (fparser->item))
+        {
+            content = feed_get_element_string (fparser);
+            katze_item_set_text (fparser->item, content);
+        }
+    }
+    else if (!(xmlStrcmp (node->name, BAD_CAST "link")))
+        atom_get_link (fparser->item, node);
+
+    g_free (content);
+}
+
+static void
+atom_postparse_entry (FeedParser* fparser)
+{
+    if (!*fparser->error)
+    {
+        /*
+        * Verify that the required Atom elements are added
+        * (as per the spec)
+        */
+        if (!katze_item_get_token (fparser->item) ||
+            !katze_item_get_name (fparser->item) ||
+            !katze_item_get_uri (fparser->item) ||
+            !katze_item_get_added (fparser->item))
+        {
+            feed_parser_set_error (fparser, FEED_PARSE_ERROR_MISSING_ELEMENT,
+                                   _("Failed to find required Atom entry elements in XML data."));
+        }
+    }
+
+    if (KATZE_IS_ITEM (fparser->item))
+    {
+        atom_set_link_attribute (fparser->item, "linkrel", NULL);
+        atom_set_link_attribute (fparser->item, "linktype", NULL);
+
+        if (*fparser->error)
+        {
+            g_object_unref (fparser->item);
+            fparser->item = NULL;
+        }
+    }
+}
+
+static void
+atom_parse_feed (FeedParser* fparser)
+{
+    FeedParser* eparser;
+    xmlNodePtr node;
+    gchar* content;
+    gint64 date;
+
+    node = fparser->node;
+    content = NULL;
+
+    if (!xmlStrcmp (node->name, BAD_CAST "id"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_token (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "title"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_name (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "subtitle"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_text (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "updated"))
+    {
+        date = feed_get_element_date (fparser);
+        katze_item_set_added (fparser->item, date);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "icon"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_icon (fparser->item, content);
+    }
+    else if (!(xmlStrcmp (node->name, BAD_CAST "link")))
+    {
+        atom_get_link (fparser->item, node);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "entry"))
+    {
+        eparser = g_new0 (FeedParser, 1);
+        eparser->doc = fparser->doc;
+        eparser->node = fparser->node;
+        eparser->error = fparser->error;
+        eparser->preparse = atom_preparse_entry;
+        eparser->parse = atom_parse_entry;
+        eparser->postparse = atom_postparse_entry;
+
+        feed_parse_node (eparser);
+
+        if (KATZE_IS_ITEM (eparser->item))
+        {
+            KatzeItem* item;
+            if (!(item = feed_item_exists (KATZE_ARRAY (fparser->item), eparser->item)))
+                katze_array_add_item (KATZE_ARRAY (fparser->item), eparser->item);
+            else
+            {
+                g_object_unref (eparser->item);
+                katze_array_move_item (KATZE_ARRAY (fparser->item), item, 0);
+            }
+        }
+        g_free (eparser);
+
+    }
+    g_free (content);
+}
+
+static void
+atom_postparse_feed (FeedParser* fparser)
+{
+    if (KATZE_IS_ARRAY (fparser->item))
+    {
+        atom_set_link_attribute (fparser->item, "linkrel", NULL);
+        atom_set_link_attribute (fparser->item, "linktype", NULL);
+    }
+
+    if (!*fparser->error)
+    {
+        /*
+         * Verify that the required Atom elements are added
+         * (as per the spec)
+         */
+        if (!katze_item_get_token (fparser->item) ||
+            !katze_item_get_name (fparser->item) ||
+            !katze_item_get_added (fparser->item))
+        {
+            feed_parser_set_error (fparser, FEED_PARSE_ERROR_MISSING_ELEMENT,
+                                   _("Failed to find required Atom feed elements in XML data."));
+        }
+    }
+}
+
+FeedParser*
+atom_init_parser (void)
+{
+    FeedParser* fparser;
+
+    fparser = g_new0 (FeedParser, 1);
+    g_return_val_if_fail (fparser, NULL);
+
+    fparser->isvalid = atom_is_valid;
+    fparser->update = atom_update;
+    fparser->parse = atom_parse_feed;
+    fparser->postparse = atom_postparse_feed;
+
+    return fparser;
+}
+
diff --git a/extensions/feed-panel/feed-atom.h b/extensions/feed-panel/feed-atom.h
new file mode 100644 (file)
index 0000000..df73c8b
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#ifndef __FEED_ATOM_H__
+#define __FEED_ATOM_H__
+
+#include "feed-parse.h"
+
+G_BEGIN_DECLS
+
+FeedParser*
+atom_init_parser (void);
+
+G_END_DECLS
+
+#endif /* __FEED_ATOM_H__ */
+
diff --git a/extensions/feed-panel/feed-panel.c b/extensions/feed-panel/feed-panel.c
new file mode 100644 (file)
index 0000000..0abf8be
--- /dev/null
@@ -0,0 +1,850 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#include "feed-panel.h"
+
+#include <midori/midori.h>
+#include <midori/sokoke.h>
+#include <time.h>
+
+#if HAVE_CONFIG_H
+    #include <config.h>
+#endif
+
+#define STOCK_FEED_PANEL "feed-panel"
+
+struct _FeedPanel
+{
+    GtkVBox parent_instance;
+
+    GtkWidget* toolbar;
+    GtkWidget* treeview;
+    GtkWidget* webview;
+    GtkWidget* delete;
+    GdkPixbuf* pixbuf;
+    KatzeNet* net;
+};
+
+struct _FeedPanelClass
+{
+    GtkVBoxClass parent_class;
+};
+
+static void
+feed_panel_viewable_iface_init (MidoriViewableIface* iface);
+
+static void
+feed_panel_insert_item (FeedPanel*    panel,
+                        GtkTreeStore* treestore,
+                        GtkTreeIter*  parent,
+                        KatzeItem*    item);
+
+static void
+feed_panel_disconnect_feed (FeedPanel*  panel,
+                            KatzeArray* feed);
+
+G_DEFINE_TYPE_WITH_CODE (FeedPanel, feed_panel, GTK_TYPE_VBOX,
+                         G_IMPLEMENT_INTERFACE (MIDORI_TYPE_VIEWABLE,
+                            feed_panel_viewable_iface_init));
+
+enum
+{
+    ADD_FEED,
+    REMOVE_FEED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+feed_panel_treeview_render_icon_cb (GtkTreeViewColumn* column,
+                                    GtkCellRenderer*   renderer,
+                                    GtkTreeModel*      model,
+                                    GtkTreeIter*       iter,
+                                    FeedPanel*         panel)
+{
+    GdkPixbuf* pixbuf;
+    KatzeItem* item;
+    KatzeItem* pitem;
+    const gchar* uri;
+
+    gtk_tree_model_get (model, iter, 0, &item, -1);
+    g_assert (KATZE_IS_ITEM (item));
+
+    if (!KATZE_IS_ARRAY (item))
+    {
+        pitem = katze_item_get_parent (item);
+        g_assert (KATZE_IS_ITEM (pitem));
+    }
+    else
+        pitem = item;
+
+    uri = katze_item_get_uri (pitem);
+    pixbuf = katze_net_load_icon (panel->net, uri, NULL, NULL, NULL);
+    if (!pixbuf)
+        pixbuf = panel->pixbuf;
+
+    g_object_set (renderer, "pixbuf", pixbuf, NULL);
+
+    if (pixbuf != panel->pixbuf)
+        g_object_unref (pixbuf);
+}
+
+static void
+feed_panel_treeview_render_text_cb (GtkTreeViewColumn* column,
+                                    GtkCellRenderer*   renderer,
+                                    GtkTreeModel*      model,
+                                    GtkTreeIter*       iter,
+                                    GtkWidget*         treeview)
+{
+    KatzeItem* item;
+    const gchar* title;
+
+    gtk_tree_model_get (model, iter, 0, &item, -1);
+    g_assert (KATZE_IS_ITEM (item));
+
+    title = katze_item_get_name (item);
+    if (!title)
+        title = katze_item_get_text (item);
+    if (!title)
+        title = katze_item_get_uri (item);
+
+    g_object_set (renderer, "text", title, NULL);
+    g_object_unref (item);
+}
+
+static void
+feed_panel_add_item_cb (KatzeArray* feed,
+                        KatzeItem*  child,
+                        FeedPanel*  panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+    GtkTreeIter child_iter;
+    KatzeItem* item;
+    gint i;
+
+    g_return_if_fail (KATZE_IS_ARRAY (feed));
+    g_return_if_fail (KATZE_IS_ITEM (child));
+
+    model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview));
+
+    if (KATZE_IS_ARRAY (child))
+    {
+        gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &child_iter,
+            NULL, G_MAXINT, 0, child, -1);
+    }
+    else
+    {
+
+        i = 0;
+        while (gtk_tree_model_iter_nth_child (model, &iter, NULL, i++))
+        {
+            gtk_tree_model_get (model, &iter, 0, &item, -1);
+            if (item == KATZE_ITEM (feed))
+            {
+                gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &child_iter,
+                    &iter, 0, 0, child, -1);
+
+                g_object_unref (child);
+                g_object_unref (item);
+                break;
+            }
+            g_object_unref (item);
+        }
+    }
+    feed_panel_insert_item (panel, GTK_TREE_STORE (model), &child_iter, child);
+}
+
+static void
+feed_panel_remove_iter (GtkTreeModel* model,
+                        KatzeItem*    removed_item)
+{
+    guint i;
+    GtkTreeIter iter;
+
+    i = 0;
+    while (gtk_tree_model_iter_nth_child (model, &iter, NULL, i))
+    {
+        KatzeItem* item;
+
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+
+        if (item == removed_item)
+        {
+            gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+            g_object_unref (item);
+            break;
+        }
+        g_object_unref (item);
+        i++;
+    }
+}
+
+static void
+feed_panel_remove_item_cb (KatzeArray* feed,
+                           KatzeItem*  child,
+                           FeedPanel*  panel)
+{
+    GtkTreeModel* model;
+
+    g_assert (KATZE_IS_ARRAY (feed));
+    g_assert (KATZE_IS_ITEM (child));
+
+    if (KATZE_IS_ARRAY (child))
+        feed_panel_disconnect_feed (panel, KATZE_ARRAY (child));
+
+    model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview));
+    feed_panel_remove_iter (model, child);
+    g_object_unref (child);
+}
+
+static void
+feed_panel_move_item_cb (KatzeArray* feed,
+                         KatzeItem*  child,
+                         FeedPanel*  panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+    guint i;
+
+    g_assert (KATZE_IS_ARRAY (feed));
+    g_assert (KATZE_IS_ITEM (child));
+
+    model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview));
+
+    i = 0;
+    while (gtk_tree_model_iter_nth_child (model, &iter, NULL, i))
+    {
+        KatzeItem* item;
+
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+
+        if (item == child)
+        {
+            gtk_tree_store_move_after (GTK_TREE_STORE (model), &iter, NULL);
+            g_object_unref (item);
+            break;
+        }
+        g_object_unref (item);
+        i++;
+    }
+}
+
+static void
+feed_panel_disconnect_feed (FeedPanel*  panel,
+                            KatzeArray* feed)
+{
+    KatzeItem* item;
+    guint i;
+
+    g_return_if_fail (KATZE_IS_ARRAY (feed));
+
+    g_signal_handlers_disconnect_by_func (feed,
+            feed_panel_add_item_cb, panel);
+    g_signal_handlers_disconnect_by_func (feed,
+            feed_panel_remove_item_cb, panel);
+    g_signal_handlers_disconnect_by_func (feed,
+            feed_panel_move_item_cb, panel);
+
+    i = 0;
+    while ((item = katze_array_get_nth_item (feed, i++)))
+    {
+        if (KATZE_IS_ARRAY (item))
+            feed_panel_disconnect_feed (panel, KATZE_ARRAY (item));
+        g_object_unref (item);
+    }
+}
+
+static void
+feed_panel_insert_item (FeedPanel*    panel,
+                        GtkTreeStore* treestore,
+                        GtkTreeIter*  parent,
+                        KatzeItem*    item)
+{
+    g_return_if_fail (KATZE_IS_ITEM (item));
+
+    if (KATZE_IS_ARRAY (item))
+    {
+        g_signal_connect_after (item, "add-item",
+            G_CALLBACK (feed_panel_add_item_cb), panel);
+        g_signal_connect_after (item, "move-item",
+            G_CALLBACK (feed_panel_move_item_cb), panel);
+
+        if (!parent)
+        {
+            g_signal_connect (item, "remove-item",
+                G_CALLBACK (feed_panel_remove_item_cb), panel);
+        }
+    }
+}
+
+static void
+feed_panel_row_activated_cb (GtkTreeView*       treeview,
+                             GtkTreePath*       path,
+                             GtkTreeViewColumn* column,
+                             FeedPanel*         panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+    KatzeItem* item;
+    const gchar* uri;
+
+    model = gtk_tree_view_get_model (treeview);
+
+    if (gtk_tree_model_get_iter (model, &iter, path))
+    {
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+        uri = katze_item_get_uri (item);
+        if (uri && *uri)
+        {
+            MidoriWebSettings* settings;
+            MidoriBrowser* browser;
+            gint n;
+
+            browser = midori_browser_get_for_widget (GTK_WIDGET (panel));
+            n = midori_browser_add_item (browser, item);
+
+            settings = katze_object_get_object (browser, "settings");
+            if (!katze_object_get_boolean (settings, "open-tabs-in-the-background"))
+                midori_browser_set_current_page (browser, n);
+            g_object_unref (settings);
+
+        }
+        g_object_unref (item);
+    }
+}
+
+static void
+feed_panel_cursor_or_row_changed_cb (GtkTreeView* treeview,
+                                     FeedPanel*   panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+    KatzeItem* item;
+    gboolean sensitive = FALSE;
+
+    if (katze_tree_view_get_selected_iter (treeview, &model, &iter))
+    {
+        const gchar* uri;
+        const gchar* text;
+
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+
+        uri = katze_item_get_uri (item);
+
+        if (uri)
+        {
+            if (KATZE_IS_ARRAY (item))
+            {
+                gint64 date;
+                SoupDate* sdate;
+
+                text = NULL;
+                date = katze_item_get_added (item);
+                if (date)
+                {
+                    sdate = soup_date_new_from_time_t ((time_t) date);
+                    text = g_strdup_printf ("Last updated %s.", soup_date_to_string (sdate, SOUP_DATE_HTTP));
+                    soup_date_free (sdate);
+                }
+                webkit_web_view_load_html_string (
+                    WEBKIT_WEB_VIEW (panel->webview), text ? text : "", uri);
+                g_free ((gchar*) text);
+
+                sensitive = TRUE;
+            }
+            else
+            {
+                text = katze_item_get_text (item);
+                if (text)
+                {
+                    webkit_web_view_load_html_string (
+                        WEBKIT_WEB_VIEW (panel->webview), text, uri);
+                }
+            }
+            g_object_unref (item);
+        }
+    }
+    if (GTK_IS_WIDGET (panel->delete))
+        gtk_widget_set_sensitive (panel->delete, sensitive);
+}
+
+static void
+feed_panel_popup_item (GtkWidget*     menu,
+                       const gchar*   stock_id,
+                       const gchar*   label,
+                       KatzeItem*     item,
+                       gpointer       callback,
+                       FeedPanel*     panel)
+{
+    const gchar* uri;
+    GtkWidget* menuitem;
+
+    uri = katze_item_get_uri (item);
+
+    menuitem = gtk_image_menu_item_new_from_stock (stock_id, NULL);
+    if (label)
+        gtk_label_set_text_with_mnemonic (GTK_LABEL (gtk_bin_get_child (
+        GTK_BIN (menuitem))), label);
+    g_object_set_data (G_OBJECT (menuitem), "KatzeItem", item);
+    g_signal_connect (menuitem, "activate", G_CALLBACK (callback), panel);
+    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+    gtk_widget_show (menuitem);
+}
+
+static void
+feed_panel_open_activate_cb (GtkWidget* menuitem,
+                             FeedPanel* panel)
+{
+    KatzeItem* item;
+    const gchar* uri;
+
+    item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem");
+    uri = katze_item_get_uri (item);
+
+    if (uri && *uri)
+    {
+        MidoriBrowser* browser;
+
+        browser = midori_browser_get_for_widget (GTK_WIDGET (panel));
+        midori_browser_set_current_uri (browser, uri);
+    }
+}
+
+static void
+feed_panel_open_in_tab_activate_cb (GtkWidget* menuitem,
+                                    FeedPanel* panel)
+{
+    KatzeItem* item;
+    const gchar* uri;
+    guint n;
+
+    item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem");
+
+    if ((uri = katze_item_get_uri (item)) && *uri)
+    {
+        MidoriWebSettings* settings;
+        MidoriBrowser* browser;
+
+        browser = midori_browser_get_for_widget (GTK_WIDGET (panel));
+        n = midori_browser_add_item (browser, item);
+
+        settings = katze_object_get_object (browser, "settings");
+        if (!katze_object_get_boolean (settings, "open-tabs-in-the-background"))
+            midori_browser_set_current_page (browser, n);
+        g_object_unref (settings);
+    }
+}
+
+static void
+feed_panel_open_in_window_activate_cb (GtkWidget* menuitem,
+                                       FeedPanel* panel)
+{
+    KatzeItem* item;
+    const gchar* uri;
+
+    item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem");
+    uri = katze_item_get_uri (item);
+
+    if (uri && *uri)
+    {
+        MidoriBrowser* browser;
+        browser = midori_browser_get_for_widget (GTK_WIDGET (panel));
+        g_signal_emit_by_name (browser, "new-window", uri);
+    }
+}
+
+static void
+feed_panel_delete_activate_cb (GtkWidget* menuitem,
+                               FeedPanel* panel)
+{
+    KatzeItem* item;
+
+    g_return_if_fail (FEED_IS_PANEL (panel));
+
+    item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem");
+    g_signal_emit (panel, signals[REMOVE_FEED], 0, item);
+}
+
+static void
+feed_panel_popup (GtkWidget*      widget,
+                  GdkEventButton* event,
+                  KatzeItem*      item,
+                  FeedPanel*      panel)
+{
+    GtkWidget* menu;
+
+    menu = gtk_menu_new ();
+    if (!KATZE_IS_ARRAY (item))
+    {
+        feed_panel_popup_item (menu, GTK_STOCK_OPEN, NULL,
+            item, feed_panel_open_activate_cb, panel);
+        feed_panel_popup_item (menu, STOCK_TAB_NEW, _("Open in New _Tab"),
+            item, feed_panel_open_in_tab_activate_cb, panel);
+        feed_panel_popup_item (menu, STOCK_WINDOW_NEW, _("Open in New _Window"),
+            item, feed_panel_open_in_window_activate_cb, panel);
+    }
+    else
+    {
+        feed_panel_popup_item (menu, GTK_STOCK_DELETE, NULL,
+            item, feed_panel_delete_activate_cb, panel);
+    }
+
+    sokoke_widget_popup (widget, GTK_MENU (menu),
+                         event, SOKOKE_MENU_POSITION_CURSOR);
+}
+
+static gboolean
+feed_panel_button_release_event_cb (GtkWidget*      widget,
+                                    GdkEventButton* event,
+                                    FeedPanel*      panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+
+    if (event->button != 2 && event->button != 3)
+        return FALSE;
+
+    if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (widget), &model, &iter))
+    {
+        KatzeItem* item;
+
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+
+        if (event->button == 2)
+        {
+            const gchar* uri = katze_item_get_uri (item);
+
+            if (uri && *uri)
+            {
+                MidoriWebSettings* settings;
+                MidoriBrowser* browser;
+                gint n;
+
+                browser = midori_browser_get_for_widget (GTK_WIDGET (panel));
+                n = midori_browser_add_item (browser, item);
+
+                settings = katze_object_get_object (browser, "settings");
+                if (!katze_object_get_boolean (settings, "open-tabs-in-the-background"))
+                    midori_browser_set_current_page (browser, n);
+                g_object_unref (settings);
+            }
+        }
+        else
+            feed_panel_popup (widget, event, item, panel);
+
+        g_object_unref (item);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static void
+feed_panel_popup_menu_cb (GtkWidget* widget,
+                          FeedPanel* panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+    KatzeItem* item;
+
+    if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (widget), &model, &iter))
+    {
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+        feed_panel_popup (widget, NULL, item, panel);
+        g_object_unref (item);
+    }
+}
+
+void
+feed_panel_add_feeds (FeedPanel* panel,
+                      KatzeItem* feed)
+{
+    GtkTreeModel* model;
+
+    model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview));
+    g_assert (GTK_IS_TREE_MODEL (model));
+
+    feed_panel_insert_item (panel, GTK_TREE_STORE (model), NULL, feed);
+}
+
+static gboolean
+webview_button_press_event_cb (GtkWidget*      widget,
+                               GdkEventButton* event)
+{
+    /* Disable the popup menu */
+    return (event->button == 3);
+}
+
+static gboolean
+webview_navigation_request_cb (WebKitWebView*             web_view,
+                               WebKitWebFrame*            frame,
+                               WebKitNetworkRequest*      request,
+                               WebKitWebNavigationAction* navigation_action,
+                               WebKitWebPolicyDecision*   policy_decision,
+                               FeedPanel*                 panel)
+{
+    if (webkit_web_navigation_action_get_reason (navigation_action) ==
+        WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)
+    {
+        MidoriBrowser* browser;
+        const gchar* uri;
+        gint n;
+
+        browser = midori_browser_get_for_widget (GTK_WIDGET (panel));
+        uri = webkit_network_request_get_uri (request);
+        n = midori_browser_add_uri (browser, uri);
+        midori_browser_set_current_page (browser, n);
+    }
+    return TRUE;
+}
+
+static const gchar*
+feed_panel_get_label (MidoriViewable* viewable)
+{
+    return _("Feeds");
+}
+
+static const gchar*
+feed_panel_get_stock_id (MidoriViewable* viewable)
+{
+    return STOCK_FEED_PANEL;
+}
+
+static void
+feed_panel_add_clicked_cb (GtkWidget* toolitem,
+                           FeedPanel* panel)
+{
+    g_return_if_fail (FEED_IS_PANEL (panel));
+
+    g_signal_emit (panel, signals[ADD_FEED], 0);
+}
+
+static void
+feed_panel_delete_clicked_cb (GtkWidget* toolitem,
+                              FeedPanel* panel)
+{
+    GtkTreeModel* model;
+    GtkTreeIter iter;
+
+    g_return_if_fail (FEED_IS_PANEL (panel));
+
+    if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (panel->treeview),
+                                           &model, &iter))
+    {
+        KatzeItem* item;
+
+        gtk_tree_model_get (model, &iter, 0, &item, -1);
+        g_signal_emit (panel, signals[REMOVE_FEED], 0, item);
+        g_object_unref (item);
+    }
+}
+
+static GtkWidget*
+feed_panel_get_toolbar (MidoriViewable* viewable)
+{
+    FeedPanel* panel = FEED_PANEL (viewable);
+
+    if (!panel->toolbar)
+    {
+        GtkWidget* toolbar;
+        GtkToolItem* toolitem;
+
+        toolbar = gtk_toolbar_new ();
+        gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ);
+        gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON);
+        panel->toolbar = toolbar;
+        toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_ADD);
+        gtk_widget_set_tooltip_text (GTK_WIDGET (toolitem), _("Add new feed"));
+        gtk_tool_item_set_is_important (toolitem, TRUE);
+        g_signal_connect (toolitem, "clicked",
+                G_CALLBACK (feed_panel_add_clicked_cb), panel);
+        gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1);
+        gtk_widget_show (GTK_WIDGET (toolitem));
+        toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_DELETE);
+        gtk_widget_set_tooltip_text (GTK_WIDGET (toolitem), _("Delete feed"));
+        g_signal_connect (toolitem, "clicked",
+                G_CALLBACK (feed_panel_delete_clicked_cb), panel);
+        gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1);
+        gtk_widget_show (GTK_WIDGET (toolitem));
+        panel->delete = GTK_WIDGET (toolitem);;
+
+        feed_panel_cursor_or_row_changed_cb (
+                GTK_TREE_VIEW (panel->treeview), panel);
+        g_signal_connect (panel->delete, "destroy",
+                G_CALLBACK (gtk_widget_destroyed), &panel->delete);
+    }
+
+    return panel->toolbar;
+}
+
+static void
+feed_panel_finalize (GObject* object)
+{
+    FeedPanel* panel = FEED_PANEL (object);
+
+    g_object_unref (panel->pixbuf);
+    g_object_unref (panel->net);
+}
+
+static void
+feed_panel_viewable_iface_init (MidoriViewableIface* iface)
+{
+    iface->get_stock_id = feed_panel_get_stock_id;
+    iface->get_label = feed_panel_get_label;
+    iface->get_toolbar = feed_panel_get_toolbar;
+}
+
+static void
+feed_panel_class_init (FeedPanelClass* class)
+{
+    GObjectClass* gobject_class;
+
+    signals[ADD_FEED] = g_signal_new (
+        "add-feed",
+        G_TYPE_FROM_CLASS (class),
+        (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
+        0,
+        0,
+        NULL,
+        g_cclosure_marshal_VOID__VOID,
+        G_TYPE_NONE, 0);
+
+    signals[REMOVE_FEED] = g_signal_new (
+        "remove-feed",
+        G_TYPE_FROM_CLASS (class),
+        (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
+        0,
+        0,
+        NULL,
+        g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE, 1,
+        G_TYPE_POINTER);
+
+    gobject_class = G_OBJECT_CLASS (class);
+    gobject_class->finalize = feed_panel_finalize;
+}
+
+static void
+feed_panel_init (FeedPanel* panel)
+{
+    GtkTreeStore* model;
+    GtkWidget* treewin;
+    GtkWidget* treeview;
+    GtkWidget* webview;
+    GtkWidget* paned;
+    GtkTreeViewColumn* column;
+    GtkCellRenderer* renderer_pixbuf;
+    GtkCellRenderer* renderer_text;
+    GtkIconFactory *factory;
+    GtkIconSource *icon_source;
+    GtkIconSet *icon_set;
+    WebKitWebSettings* settings;
+    PangoFontDescription* font_desc;
+    const gchar* family;
+    gint size;
+    GtkStockItem items[] =
+    {
+        { STOCK_FEED_PANEL, N_("_Feeds"), 0, 0, NULL }
+    };
+
+    factory = gtk_icon_factory_new ();
+    gtk_stock_add (items, G_N_ELEMENTS (items));
+    icon_set = gtk_icon_set_new ();
+    icon_source = gtk_icon_source_new ();
+    gtk_icon_source_set_icon_name (icon_source, STOCK_NEWS_FEED);
+    gtk_icon_set_add_source (icon_set, icon_source);
+    gtk_icon_source_free (icon_source);
+    gtk_icon_factory_add (factory, STOCK_FEED_PANEL, icon_set);
+    gtk_icon_set_unref (icon_set);
+    gtk_icon_factory_add_default (factory);
+    g_object_unref (factory);
+
+    panel->net = katze_net_new ();
+
+    model = gtk_tree_store_new (1, KATZE_TYPE_ITEM);
+    treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
+    panel->treeview = treeview;
+    gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+    column = gtk_tree_view_column_new ();
+    renderer_pixbuf = gtk_cell_renderer_pixbuf_new ();
+    gtk_tree_view_column_pack_start (column, renderer_pixbuf, FALSE);
+    gtk_tree_view_column_set_cell_data_func (column, renderer_pixbuf,
+            (GtkTreeCellDataFunc)feed_panel_treeview_render_icon_cb,
+            panel, NULL);
+    renderer_text = gtk_cell_renderer_text_new ();
+    gtk_tree_view_column_pack_start (column, renderer_text, FALSE);
+    gtk_tree_view_column_set_cell_data_func (column, renderer_text,
+            (GtkTreeCellDataFunc)feed_panel_treeview_render_text_cb,
+            treeview, NULL);
+    gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+    g_object_unref (model);
+    g_object_connect (treeview,
+                      "signal::row-activated",
+                      feed_panel_row_activated_cb, panel,
+                      "signal::cursor-changed",
+                      feed_panel_cursor_or_row_changed_cb, panel,
+                      "signal::columns-changed",
+                      feed_panel_cursor_or_row_changed_cb, panel,
+                      "signal::button-release-event",
+                      feed_panel_button_release_event_cb, panel,
+                      "signal::popup-menu",
+                      feed_panel_popup_menu_cb, panel,
+                      NULL);
+    gtk_widget_show (treeview);
+
+    webview = webkit_web_view_new ();
+    font_desc = treeview->style->font_desc;
+    family = pango_font_description_get_family (font_desc);
+    size = pango_font_description_get_size (font_desc) / PANGO_SCALE;
+    settings = webkit_web_settings_new ();
+    g_object_set (settings, "default-font-family", family,
+                            "default-font-size", size, NULL);
+    g_object_set (webview, "settings", settings, NULL);
+    gtk_widget_set_size_request (webview, -1, 50);
+    g_object_connect (webview,
+                      "signal::navigation-policy-decision-requested",
+                      webview_navigation_request_cb, panel,
+                      "signal::button-press-event",
+                      webview_button_press_event_cb, NULL,
+                      "signal::button-release-event",
+                      webview_button_press_event_cb, NULL,
+                      NULL);
+    panel->webview = webview;
+
+    treewin = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (treewin),
+            GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (treewin),
+            GTK_SHADOW_IN);
+    gtk_container_add (GTK_CONTAINER (treewin), treeview);
+    gtk_widget_show (treewin);
+
+    paned = gtk_vpaned_new ();
+    gtk_paned_pack1 (GTK_PANED (paned), treewin, TRUE, FALSE);
+    gtk_paned_pack2 (GTK_PANED (paned), webview, TRUE, FALSE);
+    gtk_box_pack_start (GTK_BOX (panel), paned, TRUE, TRUE, 0);
+    gtk_widget_show (webview);
+    gtk_widget_show (paned);
+
+    panel->pixbuf = gtk_widget_render_icon (treeview,
+                     STOCK_NEWS_FEED, GTK_ICON_SIZE_MENU, NULL);
+}
+
+GtkWidget*
+feed_panel_new (void)
+{
+    FeedPanel* panel = g_object_new (FEED_TYPE_PANEL, NULL);
+
+    return GTK_WIDGET (panel);
+}
+
diff --git a/extensions/feed-panel/feed-panel.h b/extensions/feed-panel/feed-panel.h
new file mode 100644 (file)
index 0000000..2530feb
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#ifndef __FEED_PANEL_H__
+#define __FEED_PANEL_H__
+
+#include <midori/midori.h>
+
+G_BEGIN_DECLS
+
+#define FEED_TYPE_PANEL \
+    (feed_panel_get_type ())
+#define FEED_PANEL(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST ((obj), FEED_TYPE_PANEL, FeedPanel))
+#define FEED_PANEL_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST ((klass), FEED_TYPE_PANEL, FeedPanelClass))
+#define FEED_IS_PANEL(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FEED_TYPE_PANEL))
+#define FEED_IS_PANEL_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE ((klass), FEED_TYPE_PANEL))
+#define FEED_PANEL_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS ((obj), FEED_TYPE_PANEL, FeedPanelClass))
+
+typedef struct _FeedPanel                FeedPanel;
+typedef struct _FeedPanelClass           FeedPanelClass;
+
+void
+feed_panel_add_feeds                     (FeedPanel*  panel,
+                                          KatzeItem* feed);
+
+void
+feed_panel_set_editable                  (FeedPanel* panel,
+                                          gboolean   editable);
+
+GType
+feed_panel_get_type                      (void);
+
+GtkWidget*
+feed_panel_new                           (void);
+
+G_END_DECLS
+
+#endif /* __FEED_PANEL_H__ */
diff --git a/extensions/feed-panel/feed-parse.c b/extensions/feed-panel/feed-parse.c
new file mode 100644 (file)
index 0000000..442e5ed
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#include "feed-parse.h"
+#include <time.h>
+
+gchar*
+feed_get_element_string (FeedParser* fparser)
+{
+    xmlNodePtr node;
+
+    node = fparser->node;
+
+    if (!node->children ||
+        xmlIsBlankNode (node->children) ||
+        node->children->type != XML_TEXT_NODE
+        )
+    {
+        /* Some servers add required elements with no content,
+         * create a dummy string to handle it.
+         */
+        return g_strdup (" ");
+    }
+
+    return (gchar* )xmlNodeListGetString (fparser->doc, node->children, 1);
+}
+
+gint64
+feed_get_element_date (FeedParser* fparser)
+{
+    time_t date;
+    gchar* content;
+
+    date = 0;
+    content = feed_get_element_string (fparser);
+
+    if (content)
+    {
+        SoupDate* sdate;
+
+        sdate = soup_date_new_from_string (content);
+        date = soup_date_to_time_t (sdate);
+        soup_date_free (sdate);
+        g_free (content);
+    }
+    return ((gint64)date);
+}
+
+KatzeItem*
+feed_item_exists (KatzeArray* array,
+                  KatzeItem*  item)
+{
+    const gchar* guid;
+    gchar* hstr;
+    guint hash;
+
+    guid = katze_item_get_token (item);
+    if (!guid)
+    {
+        hstr = g_strjoin (NULL,
+                          katze_item_get_name (item),
+                          katze_item_get_uri (item),
+                          katze_item_get_text (item),
+                          NULL);
+        hash = g_str_hash (hstr);
+        g_free (hstr);
+
+        hstr = g_strdup_printf ("%u", hash);
+        katze_item_set_token (item, hstr);
+        g_free (hstr);
+
+        guid = katze_item_get_token (item);
+    }
+
+    return (katze_array_find_token (array, guid));
+}
+
+void
+feed_parse_node (FeedParser* fparser)
+{
+    xmlNodePtr node;
+    xmlNodePtr child;
+
+    if (!*fparser->error)
+    {
+        if (fparser->preparse)
+            (*fparser->preparse) (fparser);
+
+        if (fparser->parse)
+        {
+            node = fparser->node;
+            child = node->last;
+
+            while (child)
+            {
+                if (child->type == XML_ELEMENT_NODE)
+                {
+                    fparser->node = child;
+
+                    (*fparser->parse) (fparser);
+
+                    if (*fparser->error)
+                        break;
+                }
+                child = child->prev;
+            }
+            fparser->node = node;
+        }
+
+        if (fparser->postparse)
+            (*fparser->postparse) (fparser);
+    }
+}
+
+static void
+feed_parse_doc (xmlDocPtr   doc,
+                GSList*     parsers,
+                KatzeArray* array,
+                GError**    error)
+{
+    FeedParser* fparser;
+    xmlNodePtr root;
+    gboolean isvalid;
+
+    root = xmlDocGetRootElement (doc);
+
+    if (!root)
+    {
+        *error = g_error_new (FEED_PARSE_ERROR,
+                              FEED_PARSE_ERROR_MISSING_ELEMENT,
+                              _("Failed to find root element in feed XML data."));
+        return;
+    }
+
+    while (parsers)
+    {
+        fparser = (FeedParser*)parsers->data;
+        fparser->error = error;
+        fparser->doc = doc;
+        fparser->node = root;
+
+        if (fparser && fparser->isvalid)
+        {
+            isvalid = (*fparser->isvalid) (fparser);
+
+            if (*fparser->error)
+                return;
+
+            if (isvalid)
+            {
+                fparser->item = KATZE_ITEM (array);
+
+                if (fparser->update &&
+                    (*fparser->update) (fparser))
+                    feed_parse_node (fparser);
+            }
+        }
+
+        fparser->error = NULL;
+        fparser->doc = NULL;
+        fparser->node = NULL;
+
+        if (isvalid)
+            return;
+
+        parsers = g_slist_next (parsers);
+    }
+
+    *error = g_error_new (FEED_PARSE_ERROR,
+                          FEED_PARSE_ERROR_INVALID_FORMAT,
+                          _("Unsupported feed format."));
+}
+
+gboolean
+parse_feed (gchar*      data,
+            gint64      length,
+            GSList*     parsers,
+            KatzeArray* array,
+            GError**    error)
+{
+    xmlDocPtr doc;
+    xmlErrorPtr xerror;
+
+    LIBXML_TEST_VERSION
+
+    doc = xmlReadMemory (
+                data, length, "feedfile.xml", NULL,
+                XML_PARSE_NOWARNING | XML_PARSE_NOERROR /*| XML_PARSE_RECOVER*/
+                );
+
+    if (doc)
+    {
+        feed_parse_doc (doc, parsers, array, error);
+        xmlFreeDoc (doc);
+    }
+    else
+    {
+        xerror = xmlGetLastError ();
+        *error = g_error_new (FEED_PARSE_ERROR,
+                              FEED_PARSE_ERROR_PARSE,
+                              _("Failed to parse XML feed: %s"),
+                              xerror->message);
+        xmlResetLastError ();
+    }
+    xmlCleanupParser ();
+    xmlMemoryDump ();
+
+    return *error ? FALSE : TRUE;
+}
+
diff --git a/extensions/feed-panel/feed-parse.h b/extensions/feed-panel/feed-parse.h
new file mode 100644 (file)
index 0000000..493b7ef
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#ifndef __FEED_PARSE_H__
+#define __FEED_PARSE_H__
+
+#include <midori/midori.h>
+
+#if HAVE_CONFIG_H
+    #include <config.h>
+#endif
+
+#include <libsoup/soup.h>
+#include <libxml/parser.h>
+
+G_BEGIN_DECLS
+
+#define FEED_PARSE_ERROR g_quark_from_string("FEED_PARSE_ERROR")
+
+typedef enum
+{
+    FEED_PARSE_ERROR_PARSE,
+    FEED_PARSE_ERROR_INVALID_FORMAT,
+    FEED_PARSE_ERROR_INVALID_VERSION,
+    FEED_PARSE_ERROR_MISSING_ELEMENT
+
+} FeedBarError;
+
+typedef struct _FeedParser
+{
+    xmlDocPtr   doc;   /* The XML document */
+    xmlNodePtr  node;  /* The XML node at a specific point */
+    KatzeItem*  item;
+    GError**    error;
+
+    gboolean (*isvalid)   (struct _FeedParser* fparser);
+    gboolean (*update)    (struct _FeedParser* fparser);
+    void     (*preparse)  (struct _FeedParser* fparser);
+    void     (*parse)     (struct _FeedParser* fparser);
+    void     (*postparse) (struct _FeedParser* fparser);
+
+} FeedParser;
+
+#define feed_parser_set_error(fparser, err, msg) \
+    *(fparser)->error = g_error_new ( \
+            FEED_PARSE_ERROR, (err), (msg))
+gchar*
+feed_get_element_string (FeedParser* fparser);
+
+gint64
+feed_get_element_date   (FeedParser* fparser);
+
+KatzeItem*
+feed_item_exists        (KatzeArray* array,
+                         KatzeItem*  item);
+void
+feed_parse_node         (FeedParser* fparser);
+
+gboolean
+parse_feed              (gchar*      data,
+                         gint64      length,
+                         GSList*     parsers,
+                         KatzeArray* array,
+                         GError**    error);
+
+G_END_DECLS
+
+#endif /* __FEED_PARSE_H__ */
+
diff --git a/extensions/feed-panel/feed-rss.c b/extensions/feed-panel/feed-rss.c
new file mode 100644 (file)
index 0000000..d830806
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#include "feed-rss.h"
+
+static gboolean
+rss_is_valid (FeedParser* fparser)
+{
+    xmlNodePtr node;
+    xmlNodePtr child;
+    xmlChar* str;
+    gboolean valid;
+
+    node = fparser->node;
+
+    if (!(xmlStrcmp (node->name, BAD_CAST "rss")))
+    {
+        if ((str = xmlGetProp (node, BAD_CAST "version")))
+        {
+            valid = !xmlStrcmp (str, BAD_CAST "2.0");
+            xmlFree (str);
+
+            if (valid)
+            {
+                child = node->children;
+                while (child)
+                {
+                    if (child->type == XML_ELEMENT_NODE &&
+                        !(xmlStrcmp (child->name, BAD_CAST "channel")))
+                    {
+                        fparser->node = child;
+                        return TRUE;
+                    }
+                    child = child->next;
+                }
+
+                feed_parser_set_error (fparser, FEED_PARSE_ERROR_MISSING_ELEMENT,
+                                       _("Failed to find channel element in RSS XML data."));
+            }
+            else
+            {
+                feed_parser_set_error (fparser, FEED_PARSE_ERROR_INVALID_VERSION,
+                                       _("Unsupported RSS version found."));
+            }
+        }
+    }
+    return FALSE;
+}
+
+static gboolean
+rss_update (FeedParser* fparser)
+{
+    xmlNodePtr node;
+    xmlNodePtr child;
+    gint64 date;
+    gint64 newdate;
+
+    date = katze_item_get_added (fparser->item);
+
+    node = fparser->node;
+    child = node->children;
+    while (child)
+    {
+        if (child->type == XML_ELEMENT_NODE)
+        {
+            if (!(xmlStrcmp (child->name, BAD_CAST "lastBuildDate")))
+            {
+                fparser->node = child;
+                newdate = feed_get_element_date (fparser);
+                fparser->node = node;
+                return (date != newdate);
+            }
+        }
+        child = child->next;
+    }
+    return TRUE;
+}
+
+static void
+rss_preparse_item (FeedParser* fparser)
+{
+    fparser->item = katze_item_new ();
+}
+
+static void
+rss_parse_item (FeedParser* fparser)
+{
+    xmlNodePtr node;
+    gchar* content;
+    gint64 date;
+
+    node = fparser->node;
+    content = NULL;
+
+    if (!xmlStrcmp (node->name, BAD_CAST "guid"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_token (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "title"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_name (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "description"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_text (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "pubDate"))
+    {
+        date = feed_get_element_date (fparser);
+        katze_item_set_added (fparser->item, date);
+    }
+    else if (!(xmlStrcmp (node->name, BAD_CAST "link")))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_uri (fparser->item, content);
+    }
+    g_free (content);
+}
+
+static void
+rss_postparse_item (FeedParser* fparser)
+{
+    if (!*fparser->error)
+    {
+        /*
+        * Verify that the required Atom elements are added
+        * (as per the spec)
+        */
+        if (!katze_item_get_name (fparser->item) &&
+            !katze_item_get_text (fparser->item))
+        {
+            feed_parser_set_error (fparser, FEED_PARSE_ERROR_MISSING_ELEMENT,
+                                   _("Failed to find required RSS item elements in XML data."));
+        }
+    }
+
+    if (*fparser->error && KATZE_IS_ITEM (fparser->item))
+    {
+        g_object_unref (fparser->item);
+        fparser->item = NULL;
+    }
+}
+
+static void
+rss_parse_channel (FeedParser* fparser)
+{
+    FeedParser* eparser;
+    xmlNodePtr node;
+    gchar* content;
+    gint64 date;
+
+    node = fparser->node;
+    content = NULL;
+
+    if (!xmlStrcmp (node->name, BAD_CAST "title"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_name (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "description"))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_text (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "lastBuildDate"))
+    {
+        date = feed_get_element_date (fparser);
+        katze_item_set_added (fparser->item, date);
+    }
+    else if (!(xmlStrcmp (node->name, BAD_CAST "link")))
+    {
+        content = feed_get_element_string (fparser);
+        katze_item_set_uri (fparser->item, content);
+    }
+    else if (!xmlStrcmp (node->name, BAD_CAST "item"))
+    {
+        eparser = g_new0 (FeedParser, 1);
+        eparser->doc = fparser->doc;
+        eparser->node = fparser->node;
+        eparser->error = fparser->error;
+        eparser->preparse = rss_preparse_item;
+        eparser->parse = rss_parse_item;
+        eparser->postparse = rss_postparse_item;
+
+        feed_parse_node (eparser);
+
+        if (KATZE_IS_ITEM (eparser->item))
+        {
+            KatzeItem* item;
+            if (!(item = feed_item_exists (KATZE_ARRAY (fparser->item), eparser->item)))
+                katze_array_add_item (KATZE_ARRAY (fparser->item), eparser->item);
+            else
+            {
+                g_object_unref (eparser->item);
+                katze_array_move_item (KATZE_ARRAY (fparser->item), item, 0);
+            }
+        }
+        g_free (eparser);
+
+    }
+    g_free (content);
+}
+
+static void
+rss_postparse_channel (FeedParser* fparser)
+{
+    if (!*fparser->error)
+    {
+        /*
+         * Verify that the required Atom elements are added
+         * (as per the spec)
+         */
+        if (!katze_item_get_name (fparser->item) ||
+            !katze_item_get_text (fparser->item) ||
+            !katze_item_get_uri (fparser->item))
+        {
+            feed_parser_set_error (fparser, FEED_PARSE_ERROR_MISSING_ELEMENT,
+                                   _("Failed to find required RSS channel elements in XML data."));
+        }
+    }
+}
+
+FeedParser*
+rss_init_parser (void)
+{
+    FeedParser* fparser;
+
+    fparser = g_new0 (FeedParser, 1);
+    g_return_val_if_fail (fparser, NULL);
+
+    fparser->isvalid = rss_is_valid;
+    fparser->update = rss_update;
+    fparser->parse = rss_parse_channel;
+    fparser->postparse = rss_postparse_channel;
+
+    return fparser;
+}
+
diff --git a/extensions/feed-panel/feed-rss.h b/extensions/feed-panel/feed-rss.h
new file mode 100644 (file)
index 0000000..d5ccc04
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dayul@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#ifndef __FEED_RSS_H__
+#define __FEED_RSS_H__
+
+#include "feed-parse.h"
+
+G_BEGIN_DECLS
+
+FeedParser*
+rss_init_parser (void);
+
+G_END_DECLS
+
+#endif /* __FEED_RSS_H__ */
+
diff --git a/extensions/feed-panel/main.c b/extensions/feed-panel/main.c
new file mode 100644 (file)
index 0000000..e1c4d72
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ Copyright (C) 2009 Dale Whittaker <dale@users.sf.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+#include "feed-panel.h"
+#include "feed-atom.h"
+#include "feed-rss.h"
+
+#include <midori/midori.h>
+
+#define EXTENSION_NAME "FeedPanel"
+#define UPDATE_FREQ 10
+
+#define feed_get_flags(feed) \
+    GPOINTER_TO_INT (g_object_get_data (G_OBJECT ((feed)), "flags"))
+
+#define feed_set_flags(feed, flags) \
+    g_object_set_data (G_OBJECT ((feed)), "flags", \
+        GINT_TO_POINTER ((flags)))
+
+#define feed_has_flags(feed, flags) \
+    ((flags) & feed_get_flags ((feed)))
+
+#define feed_add_flags(feed, flags) \
+    feed_set_flags ((feed), (feed_get_flags ((feed)) | (flags)))
+
+#define feed_remove_flags(feed, flags) \
+    feed_set_flags ((feed), (feed_get_flags ((feed)) & ~(flags)))
+
+typedef struct
+{
+    MidoriBrowser* browser;
+    MidoriExtension* extension;
+    GtkWidget* panel;
+    KatzeArray* feeds;
+    KatzeNet* net;
+    GSList* parsers;
+
+    guint source_id;
+    gboolean is_running;
+
+} FeedPrivate;
+
+typedef struct
+{
+    MidoriExtension* extension;
+    GSList* parsers;
+    KatzeArray* feed;
+
+} FeedNetPrivate;
+
+enum
+{
+    FEED_NEW,
+    FEED_READ,
+    FEED_REMOVE
+};
+
+static void
+feed_app_add_browser_cb (MidoriApp*       app,
+                         MidoriBrowser*   browser,
+                         MidoriExtension* extension);
+
+static void
+feed_deactivate_cb (MidoriExtension* extension,
+                    FeedPrivate*     priv)
+{
+    if (priv)
+    {
+        MidoriApp* app = midori_extension_get_app (extension);
+
+        g_signal_handlers_disconnect_by_func (app,
+                feed_app_add_browser_cb, extension);
+        g_signal_handlers_disconnect_by_func (extension,
+                feed_deactivate_cb, priv);
+
+        if (priv->source_id)
+            g_source_remove (priv->source_id);
+        g_slist_foreach (priv->parsers, (GFunc)g_free, NULL);
+        g_slist_free (priv->parsers);
+        if (priv->feeds)
+            g_object_unref (priv->net);
+        if (priv->feeds)
+            g_object_unref (priv->feeds);
+        gtk_widget_destroy (priv->panel);
+        g_free (priv);
+    }
+}
+
+static KatzeArray*
+feed_add_item (KatzeArray*  feeds,
+               const gchar* uri)
+{
+    KatzeArray* feed;
+
+    feed = NULL;
+
+    if (uri)
+    {
+        feed = katze_array_new (KATZE_TYPE_ITEM);
+        g_object_set_data_full (G_OBJECT (feed), "feeduri",
+            (gpointer) g_strdup ((uri)), g_free);
+        katze_array_add_item (feeds, feed);
+    }
+    return feed;
+}
+
+static void
+feed_save_items (MidoriExtension* extension,
+                 KatzeArray*      feed)
+{
+    KatzeItem* item;
+    gchar** sfeeds;
+    gint i;
+    gint n;
+
+    g_return_if_fail (KATZE_IS_ARRAY (feed));
+
+    n = katze_array_get_length (feed);
+    sfeeds = g_new (gchar*, n + 1);
+
+    for (i = 0; i < n; i++)
+    {
+        item = katze_array_get_nth_item (feed, i);
+        sfeeds[i]  = (gchar*) g_object_get_data (G_OBJECT (item), "feeduri");
+    }
+    sfeeds[n] = NULL;
+
+    midori_extension_set_string_list (extension, "feeds", sfeeds, n);
+    g_free (sfeeds);
+}
+
+static void
+feed_handle_net_error (FeedNetPrivate* netpriv,
+                       const gchar*    msg)
+{
+    GtkWidget* dialog;
+
+    dialog = gtk_message_dialog_new (
+            NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+            _("Error"));
+    gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+           "%s", msg);
+    gtk_window_set_title (GTK_WINDOW (dialog), EXTENSION_NAME);
+    gtk_widget_show (dialog);
+    g_signal_connect_swapped (dialog, "response",
+           G_CALLBACK (gtk_widget_destroy), dialog);
+
+    if (feed_has_flags (netpriv->feed, FEED_NEW))
+    {
+        KatzeArray* parent;
+        KatzeItem* child;
+
+        child = KATZE_ITEM (netpriv->feed);
+        parent = katze_item_get_parent (child);
+        katze_array_remove_item (parent, child);
+        feed_save_items (netpriv->extension, parent);
+    }
+    feed_remove_flags (netpriv->feed, FEED_READ);
+}
+
+static gboolean
+feed_status_cb (KatzeNetRequest*  request,
+                FeedNetPrivate*   netpriv)
+{
+    if (request->status == KATZE_NET_FAILED ||
+        request->status == KATZE_NET_NOT_FOUND)
+    {
+        gchar* msg;
+
+        msg = g_strdup_printf (_("Error loading feed %s"),
+                        katze_item_get_uri (KATZE_ITEM (netpriv->feed)));
+        feed_handle_net_error (netpriv, msg);
+        g_free (msg);
+
+        return FALSE;
+    }
+    return TRUE;
+}
+
+static void
+feed_transfer_cb (KatzeNetRequest* request,
+                  FeedNetPrivate*  netpriv)
+{
+    GError* error;
+
+    if (request->status == KATZE_NET_MOVED)
+        return;
+
+    g_return_if_fail (KATZE_IS_ARRAY (netpriv->feed));
+
+    error = NULL;
+
+    if (request->data)
+    {
+        if (!parse_feed (request->data, request->length,
+             netpriv->parsers, netpriv->feed, &error))
+        {
+            feed_handle_net_error (netpriv, error->message);
+            g_error_free (error);
+        }
+        else
+        {
+            if (feed_has_flags (netpriv->feed, FEED_REMOVE))
+            {
+                KatzeArray* parent;
+
+                /* deferred remove */
+                parent = katze_item_get_parent (KATZE_ITEM (netpriv->feed));
+                katze_array_remove_item (parent, netpriv->feed);
+                feed_save_items (netpriv->extension, parent);
+            }
+            else
+                feed_set_flags (netpriv->feed, 0);
+        }
+    }
+
+    netpriv->parsers = NULL;
+    netpriv->feed = NULL;
+    g_free (netpriv);
+}
+
+static void
+update_feed (FeedPrivate* priv,
+             KatzeItem*   feed)
+{
+    if (!(feed_has_flags (feed, FEED_READ)))
+    {
+        FeedNetPrivate* netpriv;
+        gchar* uri;
+
+        uri = (gchar*) g_object_get_data (G_OBJECT (feed), "feeduri");
+        feed_add_flags (feed, FEED_READ);
+        katze_item_set_uri (KATZE_ITEM (feed), uri);
+        netpriv = g_new0 (FeedNetPrivate, 1);
+        netpriv->parsers = priv->parsers;
+        netpriv->extension = priv->extension;
+        netpriv->feed = KATZE_ARRAY (feed);
+
+        katze_net_load_uri (priv->net,
+                            katze_item_get_uri (feed),
+                            (KatzeNetStatusCb) feed_status_cb,
+                            (KatzeNetTransferCb) feed_transfer_cb,
+                            netpriv);
+    }
+}
+
+static gboolean
+update_feeds (FeedPrivate* priv)
+{
+    KatzeItem* feed;
+    gint i;
+    gint n;
+
+    if (!priv->is_running)
+    {
+        priv->is_running = TRUE;
+        n = katze_array_get_length (priv->feeds);
+
+        for (i = 0; i < n; i++)
+        {
+            feed = katze_array_get_nth_item (priv->feeds, i);
+            update_feed (priv, feed);
+        }
+    }
+    priv->is_running = FALSE;
+    return TRUE;
+}
+
+static void
+secondary_icon_released_cb (GtkAction*     action,
+                            GtkWidget*     widget,
+                            FeedPrivate*   priv)
+{
+    const gchar* uri;
+
+    g_assert (KATZE_IS_ARRAY (priv->feeds));
+
+    uri = midori_location_action_get_uri (MIDORI_LOCATION_ACTION (action));
+
+    if (uri && *uri)
+    {
+        KatzeArray* feed;
+
+        feed = feed_add_item (priv->feeds, uri);
+        feed_save_items (priv->extension, priv->feeds);
+        feed_add_flags (feed, FEED_NEW);
+        update_feed (priv, KATZE_ITEM (feed));
+    }
+}
+
+static void
+panel_add_feed_cb (FeedPanel*   panel,
+                   FeedPrivate* priv)
+{
+    GtkWidget* dialog;
+    GtkSizeGroup* sizegroup;
+    GtkWidget* hbox;
+    GtkWidget* label;
+    GtkWidget* entry;
+
+    dialog = gtk_dialog_new_with_buttons (
+            _("New feed"), GTK_WINDOW (priv->browser),
+            GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
+            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+            GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
+            NULL);
+    gtk_window_set_icon_name (GTK_WINDOW (dialog), GTK_STOCK_ADD);
+    gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+    gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), 5);
+    sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+    hbox = gtk_hbox_new (FALSE, 8);
+    gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
+    label = gtk_label_new_with_mnemonic (_("_Address:"));
+    gtk_size_group_add_widget (sizegroup, label);
+    gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+    entry = gtk_entry_new ();
+    gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+    gtk_entry_set_text (GTK_ENTRY (entry), "");
+    gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
+    gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox);
+    gtk_widget_show_all (hbox);
+
+    gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+    {
+        const gchar* uri;
+
+        g_assert (KATZE_IS_ARRAY (priv->feeds));
+
+        uri = gtk_entry_get_text (GTK_ENTRY (entry));
+        if (uri && *uri)
+        {
+            KatzeArray* feed;
+
+            feed = feed_add_item (priv->feeds, uri);
+            feed_save_items (priv->extension, priv->feeds);
+            feed_add_flags (feed, FEED_NEW);
+            update_feed (priv, KATZE_ITEM (feed));
+        }
+    }
+    gtk_widget_destroy (dialog);
+}
+
+static void
+panel_remove_feed_cb (FeedPanel*   panel,
+                      KatzeArray*  feed,
+                      FeedPrivate* priv)
+{
+    g_assert (KATZE_IS_ARRAY (priv->feeds));
+
+    if (feed_has_flags (feed, FEED_READ))
+        feed_add_flags (feed, FEED_REMOVE);
+    else
+    {
+        feed_add_flags (feed, FEED_READ);
+        katze_array_remove_item (priv->feeds, feed);
+        feed_save_items (priv->extension, priv->feeds);
+    }
+}
+
+static void
+feed_app_add_browser_cb (MidoriApp*       app,
+                         MidoriBrowser*   browser,
+                         MidoriExtension* extension)
+{
+    GtkWidget* panel;
+    GtkWidget* addon;
+    GtkActionGroup* action_group;
+    GtkAction* action;
+    KatzeNet* net;
+    KatzeArray* feeds;
+    KatzeArray* feed;
+    FeedPrivate* priv;
+    gchar** sfeeds;
+    gsize i;
+    gsize n;
+
+    priv = g_new0 (FeedPrivate, 1);
+
+    panel = katze_object_get_object (browser, "panel");
+    addon = feed_panel_new ();
+    gtk_widget_show (addon);
+    midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (addon));
+    g_object_unref (panel);
+
+    net = katze_net_new ();
+    feeds = katze_array_new (KATZE_TYPE_ARRAY);
+    feed_panel_add_feeds (FEED_PANEL (addon), KATZE_ITEM (feeds));
+
+    priv->extension = extension;
+    priv->browser = browser;
+    priv->panel = addon;
+    priv->net = net;
+    priv->feeds = feeds;
+    priv->parsers = g_slist_prepend (priv->parsers, atom_init_parser ());
+    priv->parsers = g_slist_prepend (priv->parsers, rss_init_parser ());
+
+    sfeeds = midori_extension_get_string_list (extension, "feeds", &n);
+    g_assert (n == 0 || sfeeds);
+
+    for (i = 0; i < n; i++)
+    {
+        if (sfeeds[i])
+        {
+            feed = feed_add_item (feeds, sfeeds[i]);
+            update_feed (priv, KATZE_ITEM (feed));
+        }
+    }
+    action_group = midori_browser_get_action_group (browser);
+    action = gtk_action_group_get_action (action_group, "Location");
+
+    g_signal_connect (addon, "add-feed",
+        G_CALLBACK (panel_add_feed_cb), priv);
+    g_signal_connect (addon, "remove-feed",
+        G_CALLBACK (panel_remove_feed_cb), priv);
+    g_signal_connect (action, "secondary-icon-released",
+        G_CALLBACK (secondary_icon_released_cb), priv);
+    g_signal_connect (extension, "deactivate",
+        G_CALLBACK (feed_deactivate_cb), priv);
+
+    priv->source_id = g_timeout_add_seconds (UPDATE_FREQ * 60,
+                            (GSourceFunc) update_feeds, priv);
+}
+
+static void
+feed_activate_cb (MidoriExtension* extension,
+                  MidoriApp*       app)
+{
+    KatzeArray* browsers;
+    MidoriBrowser* browser;
+    guint i;
+
+    browsers = katze_object_get_object (app, "browsers");
+    i = 0;
+    while ((browser = katze_array_get_nth_item (browsers, i++)))
+        feed_app_add_browser_cb (app, browser, extension);
+    g_object_unref (browsers);
+
+    g_signal_connect (app, "add-browser",
+        G_CALLBACK (feed_app_add_browser_cb), extension);
+}
+
+MidoriExtension*
+extension_init (void)
+{
+    MidoriExtension* extension;
+    gchar* sfeed[2];
+
+    extension = g_object_new (MIDORI_TYPE_EXTENSION,
+        "name", _("Feed Panel"),
+        "description", _("Read Atom/ RSS feeds"),
+        "version", "0.1",
+        "authors", "Dale Whittaker <dayul@users.sf.net>",
+        NULL);
+
+    sfeed[0] = NULL;
+    midori_extension_install_string_list (extension, "feeds", sfeed, 1);
+
+    g_signal_connect (extension, "activate",
+        G_CALLBACK (feed_activate_cb), NULL);
+
+    return extension;
+}