Initial version of the Feed Panel extension

This commit is contained in:
Dale Whittaker 2009-04-25 18:15:08 +02:00 committed by Christian Dywan
parent e58550f0c0
commit 7419d17fb6
9 changed files with 2310 additions and 0 deletions

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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__ */

View file

@ -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;
}