--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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__ */
+
--- /dev/null
+/*
+ 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);
+}
+
--- /dev/null
+/*
+ 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__ */
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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__ */
+
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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__ */
+
--- /dev/null
+/*
+ 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;
+}