summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog42
-rw-r--r--components/news/.cvsignore7
-rw-r--r--components/news/Makefile.am50
-rw-r--r--components/news/Nautilus_View_news.oaf.in25
-rw-r--r--components/news/Nautilus_View_news.server.in25
-rw-r--r--components/news/nautilus-news.c1697
-rw-r--r--components/news/news_bullet.pngbin0 -> 350 bytes
-rw-r--r--components/news/news_channels.xml25
-rw-r--r--components/news/pixmaps.h55
-rw-r--r--configure.in1
10 files changed, 1927 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index cacf9971a..faf80b9ea 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,45 @@
+2001-04-20 Andy Hertzfeld <andy@eazel.com>
+
+ first check-in of "news" sidebar view to display news from selected
+ sites that support an rss feed. It's around 80% completed now, and
+ should be quite usable; I just need to finish the remaining 80%.
+
+ * components/news/.cvsignore:
+ * components/news/Makefile.am:
+ * components/news/Nautilus_View_news.oaf.in:
+
+ * components/news/nautilus-news.c: (get_bonobo_properties),
+ (set_bonobo_properties), (do_destroy), (pixbuf_composite),
+ (draw_triangle), (draw_rss_logo_image), (draw_rss_title),
+ (draw_rss_items), (nautilus_news_draw_channel),
+ (nautilus_news_update_display), (nautilus_news_configure_event),
+ (nautilus_news_expose_event), (nautilus_news_set_prelight_index),
+ (go_to_uri), (toggle_open_state), (item_hit_test),
+ (nautilus_news_button_release_event),
+ (nautilus_news_motion_notify_event),
+ (nautilus_news_leave_notify_event), (nautilus_news_set_title),
+ (free_rss_data_item), (free_rss_channel_items), (free_channel),
+ (nautilus_news_free_channel_list), (bool_to_text),
+ (nautilus_news_make_channel_document),
+ (nautilus_news_save_channel_state), (rss_logo_callback),
+ (extract_items), (update_size_and_redraw),
+ (rss_read_done_callback), (nautilus_news_load_channel),
+ (nautilus_news_make_new_channel), (nautilus_news_add_channels),
+ (get_xml_path), (read_channel_list), (check_for_updates),
+ (news_get_indicator_image), (load_xpm_image),
+ (nautilus_news_load_images), (configure_button_clicked),
+ (add_site_button_clicked), (add_site_from_fields),
+ (add_command_buttons), (get_channel_from_name),
+ (check_button_toggled_callback), (nautilus_news_load_location),
+ (add_channel_entry), (add_channels_to_configure_list),
+ (set_up_add_widgets), (set_up_configure_widgets),
+ (set_up_main_widgets), (make_news_view), (main):
+
+ * components/news/news_bullet.png:
+ * components/news/news_channels.xml:
+ * components/news/pixmaps.h:
+ * configure.in:
+
2001-04-20 Ramiro Estrugo <ramiro@eazel.com>
* ChangeLog: rolled over to ChangeLog-20010420.
diff --git a/components/news/.cvsignore b/components/news/.cvsignore
new file mode 100644
index 000000000..a5f6024de
--- /dev/null
+++ b/components/news/.cvsignore
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+nautilus-news
+nautilus-news.o
+Nautilus_View_news.oaf
diff --git a/components/news/Makefile.am b/components/news/Makefile.am
new file mode 100644
index 000000000..6b98db781
--- /dev/null
+++ b/components/news/Makefile.am
@@ -0,0 +1,50 @@
+NULL =
+
+bin_PROGRAMS=nautilus-news
+
+INCLUDES=\
+ -I$(top_srcdir) \
+ -I$(top_builddir) \
+ -I$(top_builddir)/libnautilus \
+ -DNAUTILUS_DATADIR=\""$(datadir)/nautilus"\" \
+ -DNAUTILUS_PIXMAPDIR=\""$(datadir)/pixmaps/nautilus"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
+ $(EEL_INCLUDEDIR) \
+ $(LIBRSVG_INCLUDEDIR) \
+ $(GNOMEUI_CFLAGS) \
+ $(GCONF_CFLAGS) \
+ $(BONOBO_CFLAGS) \
+ $(VFS_CFLAGS)
+
+LDADD=\
+ $(top_builddir)/libnautilus/libnautilus.la \
+ $(top_builddir)/libnautilus-extensions/libnautilus-extensions.la \
+ $(EEL_LIBS) \
+ $(LIBRSVG_LIBS) \
+ $(BONOBO_LIBS) \
+ $(GCONF_LIBS) \
+ $(GNOMEUI_LIBS)
+
+nautilus_news_SOURCES=nautilus-news.c
+
+nautilusdir = $(datadir)/nautilus
+nautilus_DATA = news_channels.xml
+
+nautiluspixmapdir = $(datadir)/pixmaps/nautilus
+nautiluspixmap_DATA = news_bullet.png
+
+oafdir = $(datadir)/oaf
+oaf_in_files = \
+ Nautilus_View_news.oaf.in \
+ $(NULL)
+oaf_DATA = $(oaf_in_files:.oaf.in=.oaf)
+
+@XML_I18N_MERGE_OAF_RULE@
+
+EXTRA_DIST= \
+ $(nautilus_DATA) \
+ $(nautiluspixmap_DATA) \
+ $(oaf_DATA)\
+ $(oaf_in_files) \
+ $(NULL)
diff --git a/components/news/Nautilus_View_news.oaf.in b/components/news/Nautilus_View_news.oaf.in
new file mode 100644
index 000000000..330904326
--- /dev/null
+++ b/components/news/Nautilus_View_news.oaf.in
@@ -0,0 +1,25 @@
+<oaf_info>
+
+<oaf_server iid="OAFIID:nautilus_news_view_factory:041601" type="exe" location="nautilus-news">
+ <oaf_attribute name="repo_ids" type="stringv">
+ <item value="IDL:GNOME/ObjectFactory:1.0"/>
+ </oaf_attribute>
+ <oaf_attribute name="description" type="string" _value="Factory for news view"/>
+</oaf_server>
+
+<oaf_server iid="OAFIID:nautilus_news_view:041601" type="factory" location="OAFIID:nautilus_news_view_factory:041601">
+ <oaf_attribute name="repo_ids" type="stringv">
+ <item value="IDL:Bonobo/Unknown:1.0"/>
+ <item value="IDL:Bonobo/Control:1.0"/>
+ <item value="IDL:Nautilus/View:1.0"/>
+ </oaf_attribute>
+
+ <oaf_attribute name="description" type="string" _value="News sidebar panel fetches and displays RSS feeds"/>
+ <oaf_attribute name="name" type="string" _value="News sidebar panel"/>
+ <oaf_attribute name="nautilus:sidebar_panel_name" type="string" _value="News"/>
+ <oaf_attribute name="nautilus:recommended_uri_schemes" type="stringv">
+ <item value="*"/>
+ </oaf_attribute>
+</oaf_server>
+
+</oaf_info>
diff --git a/components/news/Nautilus_View_news.server.in b/components/news/Nautilus_View_news.server.in
new file mode 100644
index 000000000..330904326
--- /dev/null
+++ b/components/news/Nautilus_View_news.server.in
@@ -0,0 +1,25 @@
+<oaf_info>
+
+<oaf_server iid="OAFIID:nautilus_news_view_factory:041601" type="exe" location="nautilus-news">
+ <oaf_attribute name="repo_ids" type="stringv">
+ <item value="IDL:GNOME/ObjectFactory:1.0"/>
+ </oaf_attribute>
+ <oaf_attribute name="description" type="string" _value="Factory for news view"/>
+</oaf_server>
+
+<oaf_server iid="OAFIID:nautilus_news_view:041601" type="factory" location="OAFIID:nautilus_news_view_factory:041601">
+ <oaf_attribute name="repo_ids" type="stringv">
+ <item value="IDL:Bonobo/Unknown:1.0"/>
+ <item value="IDL:Bonobo/Control:1.0"/>
+ <item value="IDL:Nautilus/View:1.0"/>
+ </oaf_attribute>
+
+ <oaf_attribute name="description" type="string" _value="News sidebar panel fetches and displays RSS feeds"/>
+ <oaf_attribute name="name" type="string" _value="News sidebar panel"/>
+ <oaf_attribute name="nautilus:sidebar_panel_name" type="string" _value="News"/>
+ <oaf_attribute name="nautilus:recommended_uri_schemes" type="stringv">
+ <item value="*"/>
+ </oaf_attribute>
+</oaf_server>
+
+</oaf_info>
diff --git a/components/news/nautilus-news.c b/components/news/nautilus-news.c
new file mode 100644
index 000000000..7d614856b
--- /dev/null
+++ b/components/news/nautilus-news.c
@@ -0,0 +1,1697 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+
+/*
+ * Nautilus
+ *
+ * Copyright (C) 2001 Eazel, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ */
+
+/* This is the News sidebar panel, which displays current news headlines from
+ * a variety of web sites, by fetching and displaying RSS files
+ */
+
+#include <config.h>
+#include <gnome.h>
+
+#include <gnome-xml/parser.h>
+#include <gnome-xml/xmlmemory.h>
+
+#include <eel/eel-background.h>
+#include <eel/eel-debug.h>
+#include <eel/eel-gdk-pixbuf-extensions.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-extensions.h>
+#include <eel/eel-label.h>
+#include <eel/eel-scalable-font.h>
+#include <eel/eel-smooth-text-layout.h>
+#include <eel/eel-string.h>
+#include <eel/eel-vfs-extensions.h>
+#include <eel/eel-xml-extensions.h>
+
+#include <libnautilus-extensions/nautilus-entry.h>
+#include <libnautilus-extensions/nautilus-file-attributes.h>
+#include <libnautilus-extensions/nautilus-file.h>
+#include <libnautilus-extensions/nautilus-file-utilities.h>
+#include <libnautilus-extensions/nautilus-font-factory.h>
+#include <libnautilus-extensions/nautilus-global-preferences.h>
+#include <libnautilus-extensions/nautilus-metadata.h>
+#include <libnautilus-extensions/nautilus-theme.h>
+#include <libnautilus-extensions/nautilus-undo-signal-handlers.h>
+
+#include <libnautilus/nautilus-clipboard.h>
+#include <libnautilus/nautilus-view-standard-main.h>
+
+#include "pixmaps.h"
+
+/* property bag getting and setting routines */
+enum {
+ TAB_IMAGE,
+};
+
+/* data structure for the news view */
+typedef struct {
+ NautilusView *view;
+ BonoboPropertyBag *property_bag;
+
+ char *uri; /* keep track of where content view is at */
+
+ GList *channel_list;
+
+ GdkPixbuf *pixbuf; /* offscreen buffer for news display */
+
+ GdkPixbuf *closed_triangle;
+ GdkPixbuf *open_triangle;
+ GdkPixbuf *bullet;
+
+ GtkWidget *main_box;
+ GtkWidget *news_display;
+
+ GtkWidget *configure_box;
+ GtkWidget *checkbox_list;
+
+ GtkWidget *add_site_box;
+ GtkWidget *item_name_field;
+ GtkWidget *item_location_field;
+
+ EelScalableFont *font;
+
+ gboolean news_changed;
+ gboolean always_display_title;
+ gboolean configure_mode;
+
+ guint timer_task;
+} News;
+
+/* per item structure for rss items */
+typedef struct {
+ char *item_title;
+ char *item_url;
+ int item_start_y;
+ int item_end_y;
+} RSSItemData;
+
+/* per channel structure for rss channel */
+typedef struct {
+ char *name;
+ char *uri;
+ char *link_uri;
+ News *owner;
+
+ char *title;
+ GdkPixbuf *logo_image;
+
+ GList *items;
+
+ EelReadFileHandle *load_file_handle;
+ EelPixbufLoadHandle *load_image_handle;
+
+ int prelight_index;
+
+ int logo_start_y;
+ int logo_end_y;
+ int items_start_y;
+ int items_end_y;
+
+ time_t last_update;
+ uint update_interval;
+
+ gboolean is_open;
+ gboolean is_showing;
+
+ gboolean update_in_progress;
+ gboolean error_flag;
+} RSSChannelData;
+
+/* pixel and drawing constant defines */
+
+#define RSS_ITEM_HEIGHT 12
+#define CHANNEL_GAP_SIZE 12
+#define LOGO_GAP_SIZE 2
+#define DISCLOSURE_RIGHT_POSITION 16
+#define LOGO_LEFT_OFFSET 20
+#define ITEM_POSITION 30
+#define ITEM_FONT_SIZE 11
+#define TIME_FONT_SIZE 9
+#define TIME_MARGIN_OFFSET 2
+#define TITLE_FONT_SIZE 18
+#define MINIMUM_DRAW_SIZE 16
+#define NEWS_BACKGROUND_RGBA 0xFFFFFFFF
+
+static char *news_get_indicator_image (News *news_data);
+static void nautilus_news_free_channel_list (News *news_data);
+static gboolean nautilus_news_save_channel_state (News* news_data);
+static void update_size_and_redraw (News* news_data);
+static char* get_xml_path (const char *file_name, gboolean force_local);
+static int check_for_updates (gpointer callback_data);
+static void add_channel_entry (News *news_data, const char *channel_name,
+ int index, gboolean is_showing);
+static RSSChannelData*
+nautilus_news_make_new_channel (News *news_data,
+ const char *name,
+ const char* channel_uri,
+ gboolean is_open,
+ gboolean is_showing);
+
+static void
+get_bonobo_properties (BonoboPropertyBag *bag,
+ BonoboArg *arg,
+ guint arg_id,
+ CORBA_Environment *ev,
+ gpointer callback_data)
+{
+ char *indicator_image;
+ News *news;
+
+ news = (News *) callback_data;
+
+ switch (arg_id) {
+ case TAB_IMAGE: {
+ /* if there is a note, return the name of the indicator image,
+ otherwise, return NULL */
+ indicator_image = news_get_indicator_image (news);
+ BONOBO_ARG_SET_STRING (arg, indicator_image);
+ g_free (indicator_image);
+ break;
+ }
+
+ default:
+ g_warning ("Unhandled arg %d", arg_id);
+ break;
+ }
+}
+
+static void
+set_bonobo_properties (BonoboPropertyBag *bag,
+ const BonoboArg *arg,
+ guint arg_id,
+ CORBA_Environment *ev,
+ gpointer callback_data)
+{
+ g_warning ("Can't set note property %u", arg_id);
+}
+
+
+static void
+do_destroy (GtkObject *obj, News *news)
+{
+ /* If the widget is being destroyed first, make sure the bonobo object
+ * that owns it is not destroyed half-way through the widget destroy
+ * process by reffing the bonobo object and only unreffing it at idle
+ * time. If the bonobo object is being destroyed first, then don't do
+ * this because it exposes a bonobo bug.
+ */
+ if (!GTK_OBJECT_DESTROYED (GTK_OBJECT (news->view))) {
+ bonobo_object_ref (BONOBO_OBJECT (news->view));
+ bonobo_object_idle_unref (BONOBO_OBJECT (news->view));
+ }
+
+ g_free (news->uri);
+
+ if (news->font) {
+ gtk_object_unref (GTK_OBJECT (news->font));
+ }
+
+ if (news->timer_task != 0) {
+ gtk_timeout_remove (news->timer_task);
+ news->timer_task = 0;
+ }
+
+ if (news->closed_triangle != NULL) {
+ gdk_pixbuf_unref (news->closed_triangle);
+ }
+
+ if (news->open_triangle != NULL) {
+ gdk_pixbuf_unref (news->open_triangle);
+ }
+
+ if (news->bullet != NULL) {
+ gdk_pixbuf_unref (news->bullet);
+ }
+
+ /* free all the channel data */
+ nautilus_news_free_channel_list (news);
+
+ g_free (news);
+}
+
+
+/* drawing routines start here... */
+
+/* convenience routine to composite an image with the proper clipping */
+static void
+pixbuf_composite (GdkPixbuf *source, GdkPixbuf *destination, int x_offset, int y_offset, int alpha)
+{
+ int source_width, source_height, dest_width, dest_height;
+ double float_x_offset, float_y_offset;
+
+ source_width = gdk_pixbuf_get_width (source);
+ source_height = gdk_pixbuf_get_height (source);
+ dest_width = gdk_pixbuf_get_width (destination);
+ dest_height = gdk_pixbuf_get_height (destination);
+
+ float_x_offset = x_offset;
+ float_y_offset = y_offset;
+
+ /* clip to the destination size */
+ if ((x_offset + source_width) > dest_width) {
+ source_width = dest_width - x_offset;
+ }
+ if ((y_offset + source_height) > dest_height) {
+ source_height = dest_height - y_offset;
+ }
+
+ gdk_pixbuf_composite (source, destination, x_offset, y_offset, source_width, source_height,
+ float_x_offset, float_y_offset, 1.0, 1.0, GDK_PIXBUF_ALPHA_BILEVEL, alpha);
+}
+
+/* utility to draw the disclosure triangle */
+static void
+draw_triangle (GdkPixbuf *pixbuf, RSSChannelData *channel_data, int v_offset)
+{
+ GdkPixbuf *triangle;
+ int v_delta, triangle_position;
+ int logo_height;
+ if (channel_data->is_open) {
+ triangle = channel_data->owner->open_triangle;
+ } else {
+ triangle = channel_data->owner->closed_triangle;
+ }
+
+ if (channel_data->logo_image == NULL) {
+ logo_height = TITLE_FONT_SIZE;
+ } else {
+ logo_height = gdk_pixbuf_get_height (channel_data->logo_image);
+ }
+
+ v_delta = logo_height - gdk_pixbuf_get_height (triangle);
+ triangle_position = v_offset + (v_delta / 2);
+ pixbuf_composite (triangle, pixbuf, 2, triangle_position, 255);
+}
+
+/* draw the logo image */
+static int
+draw_rss_logo_image (RSSChannelData *channel_data,
+ GdkPixbuf *pixbuf,
+ int offset,
+ gboolean measure_only)
+{
+ char time_str[16];
+ int logo_width, logo_height;
+ int v_offset, pixbuf_width;
+ int time_x_pos, time_y_pos;
+ EelDimensions time_dimensions;
+
+ v_offset = offset;
+
+ if (channel_data->logo_image != NULL) {
+ logo_width = gdk_pixbuf_get_width (channel_data->logo_image);
+ logo_height = gdk_pixbuf_get_height (channel_data->logo_image);
+
+ if (!measure_only) {
+ draw_triangle (pixbuf, channel_data, v_offset);
+ pixbuf_composite (channel_data->logo_image, pixbuf, LOGO_LEFT_OFFSET, v_offset, 255);
+ }
+ v_offset += logo_height + 2;
+
+ /* also, draw the update time in the upper right corner */
+ if (channel_data->last_update != 0 && !measure_only) {
+ strftime (&time_str[0], 16, "%I:%M %p", localtime (&channel_data->last_update));
+
+ time_dimensions = eel_scalable_font_measure_text (channel_data->owner->font, 9, time_str, strlen (time_str));
+
+ pixbuf_width = gdk_pixbuf_get_width (pixbuf);
+ time_x_pos = pixbuf_width - time_dimensions.width - TIME_MARGIN_OFFSET;
+ time_y_pos = offset + ((gdk_pixbuf_get_height (channel_data->logo_image) - time_dimensions.height) / 2);
+ eel_scalable_font_draw_text (channel_data->owner->font, pixbuf,
+ time_x_pos, time_y_pos,
+ NULL, TIME_FONT_SIZE, time_str, strlen (time_str),
+ EEL_RGB_COLOR_BLACK, EEL_OPACITY_FULLY_OPAQUE);
+ }
+ }
+ return v_offset;
+}
+
+/* draw the title */
+static int
+draw_rss_title (RSSChannelData *channel_data,
+ GdkPixbuf *pixbuf,
+ int v_offset,
+ gboolean measure_only)
+{
+ EelDimensions title_dimensions;
+ int label_offset;
+
+ if (channel_data->title == NULL || channel_data->owner->font == NULL) {
+ return v_offset;
+ }
+
+ /* first, measure the text */
+ title_dimensions = eel_scalable_font_measure_text (channel_data->owner->font,
+ TITLE_FONT_SIZE,
+ channel_data->title, strlen (channel_data->title));
+
+ /* if there is no image, draw the disclosure triangle */
+ if (channel_data->logo_image == NULL && !measure_only) {
+ draw_triangle (pixbuf, channel_data, v_offset);
+ label_offset = LOGO_LEFT_OFFSET;
+ } else {
+ label_offset = 4;
+ }
+
+ /* draw the name into the pixbuf using anti-aliased text */
+ if (!measure_only) {
+ eel_scalable_font_draw_text (channel_data->owner->font, pixbuf,
+ label_offset, v_offset,
+ NULL,
+ 18,
+ channel_data->title, strlen (channel_data->title),
+ EEL_RGB_COLOR_BLACK,
+ EEL_OPACITY_FULLY_OPAQUE);
+ }
+
+ return v_offset + title_dimensions.height;
+}
+
+/* draw the items */
+static int
+draw_rss_items (RSSChannelData *channel_data,
+ GdkPixbuf *pixbuf,
+ int v_offset,
+ gboolean measure_only)
+{
+ GList *current_item;
+ RSSItemData *item_data;
+ int bullet_width, bullet_height, font_size;
+ int item_index, bullet_alpha;
+ int bullet_x_pos, bullet_y_pos;
+ int line_width;
+ guint32 text_color;
+ ArtIRect dest_bounds;
+ EelSmoothTextLayout *smooth_text_layout;
+ EelDimensions text_dimensions;
+
+ if (channel_data->owner->bullet) {
+ bullet_width = gdk_pixbuf_get_width (channel_data->owner->bullet);
+ bullet_height = gdk_pixbuf_get_height (channel_data->owner->bullet);
+ } else {
+ bullet_width = 0;
+ bullet_height = 0;
+ }
+
+ current_item = channel_data->items;
+ item_index = 0;
+
+ while (current_item != NULL) {
+ /* draw the text */
+ item_data = (RSSItemData*) current_item->data;
+ bullet_alpha = 255;
+
+ if (eel_strcasecmp (item_data->item_url, channel_data->owner->uri) == 0) {
+ text_color = EEL_RGBA_COLOR_PACK (160, 0, 160, 255);
+ }
+ else if (item_index == channel_data->prelight_index) {
+ text_color = EEL_RGBA_COLOR_PACK (0, 0, 128, 255);
+ } else {
+ text_color = EEL_RGB_COLOR_BLACK;
+ bullet_alpha = 192;
+ }
+
+ font_size = ITEM_FONT_SIZE;
+ item_data->item_start_y = v_offset;
+ smooth_text_layout = eel_smooth_text_layout_new (
+ item_data->item_title, strlen(item_data->item_title),
+ channel_data->owner->font, font_size, TRUE);
+
+ line_width = channel_data->owner->news_display->allocation.width - ITEM_POSITION;
+ if (line_width > 0) {
+ eel_smooth_text_layout_set_line_wrap_width (smooth_text_layout, line_width - 4);
+ }
+ text_dimensions = eel_smooth_text_layout_get_dimensions (smooth_text_layout);
+
+ if (!measure_only) {
+ dest_bounds.x0 = ITEM_POSITION;
+ dest_bounds.y0 = v_offset;
+ dest_bounds.x1 = gdk_pixbuf_get_width (pixbuf);
+ dest_bounds.y1 = gdk_pixbuf_get_height (pixbuf);
+
+ if (!art_irect_empty (&dest_bounds)) {
+ eel_smooth_text_layout_draw_to_pixbuf
+ (smooth_text_layout, pixbuf,
+ 0, 0, &dest_bounds, GTK_JUSTIFY_LEFT,
+ TRUE, text_color,
+ EEL_OPACITY_FULLY_OPAQUE);
+
+ /* draw the bullet */
+ if (channel_data->owner->bullet) {
+ bullet_x_pos = ITEM_POSITION - bullet_width - 2;
+ bullet_y_pos = v_offset + 2;
+ pixbuf_composite (channel_data->owner->bullet, pixbuf,
+ bullet_x_pos, bullet_y_pos, bullet_alpha);
+ }
+ }
+ }
+ gtk_object_destroy (GTK_OBJECT (smooth_text_layout));
+
+ item_data->item_end_y = item_data->item_start_y + text_dimensions.height;
+ v_offset += text_dimensions.height + 4;
+
+ item_index += 1;
+ current_item = current_item->next;
+
+ /* limit to five items max, for now */
+ if (item_index > 5) {
+ break;
+ }
+ }
+ return v_offset;
+}
+
+/* draw a single channel */
+static int
+nautilus_news_draw_channel (News *news_data,
+ RSSChannelData *channel,
+ int v_offset,
+ gboolean measure_only)
+{
+ channel->logo_start_y = v_offset;
+ v_offset = draw_rss_logo_image (channel, news_data->pixbuf, v_offset, measure_only);
+
+ if (news_data->always_display_title || channel->logo_image == NULL) {
+ v_offset = draw_rss_title (channel, news_data->pixbuf, v_offset, measure_only);
+ }
+
+ channel->logo_end_y = v_offset;
+ v_offset += LOGO_GAP_SIZE;
+
+ channel->items_start_y = v_offset;
+ if (channel->is_open) {
+ v_offset = draw_rss_items (channel, news_data->pixbuf, v_offset, measure_only);
+ }
+
+ channel->items_end_y = v_offset;
+ return v_offset;
+}
+
+/* main routine to render the channel list into the display pixbuf */
+static int
+nautilus_news_update_display (News *news_data, gboolean measure_only)
+{
+ int width, height, v_offset;
+ GList *channel_item;
+ RSSChannelData *channel_data;
+
+ v_offset = 2;
+
+ if (news_data->pixbuf == NULL && !measure_only) {
+ return v_offset;
+ }
+
+ /* don't draw if too small */
+ if (!measure_only) {
+ width = gdk_pixbuf_get_width (news_data->pixbuf);
+ height = gdk_pixbuf_get_height (news_data->pixbuf);
+
+ /* don't draw when too small, like during size negotiation */
+ if ((width < MINIMUM_DRAW_SIZE || height < MINIMUM_DRAW_SIZE) && !measure_only) {
+ return v_offset;
+ }
+
+ eel_gdk_pixbuf_fill_rectangle_with_color (news_data->pixbuf, NULL, NEWS_BACKGROUND_RGBA);
+ }
+
+ /* loop through the channel list, drawing one channel at a time */
+ channel_item = news_data->channel_list;
+ while (channel_item != NULL) {
+ channel_data = (RSSChannelData*) channel_item->data;
+ if (!channel_data->update_in_progress && channel_data->is_showing) {
+
+ v_offset = nautilus_news_draw_channel (news_data,
+ channel_data,
+ v_offset, measure_only);
+ if (channel_data->is_open) {
+ v_offset += CHANNEL_GAP_SIZE;
+ }
+ }
+ channel_item = channel_item->next;
+ }
+ return v_offset;
+}
+
+/* allocate the pixbuf to draw into */
+static gint
+nautilus_news_configure_event (GtkWidget *widget, GdkEventConfigure *event, News *news_data )
+{
+ if (news_data->pixbuf != NULL) {
+ gdk_pixbuf_unref (news_data->pixbuf);
+ }
+
+ news_data->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ widget->allocation.width, widget->allocation.height);
+
+ eel_gdk_pixbuf_fill_rectangle_with_color (news_data->pixbuf, NULL, NEWS_BACKGROUND_RGBA);
+ return TRUE;
+}
+
+/* handle the news display drawing */
+static gint
+nautilus_news_expose_event( GtkWidget *widget, GdkEventExpose *event, News *news_data )
+{
+ int pixbuf_width, pixbuf_height;
+
+ /* this shouldn't be necessary - remove it soon */
+ nautilus_news_update_display (news_data, FALSE);
+
+ pixbuf_width = gdk_pixbuf_get_width (news_data->pixbuf);
+ pixbuf_height = gdk_pixbuf_get_height (news_data->pixbuf);
+
+ gdk_pixbuf_render_to_drawable_alpha (news_data->pixbuf,
+ widget->window,
+ 0, 0,
+ widget->allocation.x, widget->allocation.y,
+ pixbuf_width, pixbuf_height,
+ GDK_PIXBUF_ALPHA_BILEVEL, 128,
+ GDK_RGB_DITHER_MAX,
+ 0, 0);
+ return FALSE;
+}
+
+/* utility to set the prelight index of a channel and redraw if necessary */
+static void
+nautilus_news_set_prelight_index (RSSChannelData *channel_data, int new_prelight_index)
+{
+ if (channel_data->prelight_index != new_prelight_index) {
+ channel_data->prelight_index = new_prelight_index;
+ nautilus_news_update_display (channel_data->owner, FALSE);
+ gtk_widget_queue_draw (GTK_WIDGET (channel_data->owner->news_display));
+ }
+}
+
+
+/* utility routine to tell Nautilus to navigate to the passed-in uri */
+static void
+go_to_uri (News* news_data, const char* uri)
+{
+ nautilus_view_open_location_in_this_window (news_data->view, uri);
+}
+
+/* utility routine to toggle the open state of the passed in channel */
+static void
+toggle_open_state (RSSChannelData *channel_data)
+{
+ channel_data->is_open = !channel_data->is_open;
+ update_size_and_redraw (channel_data->owner);
+ nautilus_news_save_channel_state (channel_data->owner);
+}
+
+/* handle item hit testing */
+static int
+item_hit_test (RSSChannelData *channel_data, int y_pos)
+{
+ RSSItemData *item_data;
+ GList *next_item;
+ int item_index;
+
+ item_index = 0;
+ next_item = channel_data->items;
+ while (next_item != NULL) {
+ item_data = (RSSItemData*) next_item->data;
+ if (y_pos >= item_data->item_start_y && y_pos <= item_data->item_end_y) {
+ return item_index;
+ }
+ item_index += 1;
+ next_item = next_item->next;
+ }
+ return -1;
+}
+
+/* handle the news display hit-testing */
+static gint
+nautilus_news_button_release_event (GtkWidget *widget, GdkEventButton *event, News *news_data )
+{
+ GList *current_channel;
+ GList *selected_item;
+ RSSChannelData *channel_data;
+ RSSItemData *item_data;
+ int which_item;
+
+ /* loop through all of the channels */
+ current_channel = news_data->channel_list;
+ while (current_channel != NULL) {
+ channel_data = (RSSChannelData*) current_channel->data;
+
+ /* if the channel isn't showing, skip all this */
+ if (!channel_data->is_showing) {
+ current_channel = current_channel->next;
+ continue;
+ }
+
+ /* see if the mouse went down in this channel */
+ if (event->y >= channel_data->logo_start_y && event->y <= channel_data->items_end_y) {
+
+ /* see if the user clicked on the logo or title area */
+ if (event->y <= channel_data->logo_end_y) {
+ /* distinguish between the disclosure triangle area and the logo proper */
+ if (event->x < DISCLOSURE_RIGHT_POSITION) {
+ toggle_open_state (channel_data);
+ } else {
+ go_to_uri (news_data, channel_data->link_uri);
+ }
+ return TRUE;
+ }
+
+
+ /* if it's open, determine which item was clicked */
+ if (channel_data->is_open && event->y >= channel_data->items_start_y) {
+ which_item = item_hit_test (channel_data, event->y);
+ if (which_item < (int) g_list_length (channel_data->items)) {
+ selected_item = g_list_nth (channel_data->items, which_item);
+ item_data = (RSSItemData*) selected_item->data;
+ go_to_uri (news_data, item_data->item_url);
+ return TRUE;
+ }
+ }
+ }
+ current_channel = current_channel->next;
+ }
+ return TRUE;
+}
+
+/* handle motion notify events by prelighting as appropriate */
+static gint
+nautilus_news_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, News *news_data )
+{
+ GList *current_channel;
+ RSSChannelData *channel_data;
+ int which_item;
+
+ /* loop through all of the channels to find the one the mouse is over */
+ current_channel = news_data->channel_list;
+ while (current_channel != NULL) {
+ channel_data = (RSSChannelData*) current_channel->data;
+
+ /* see if it's in the items for this channel */
+ if (event->y >= channel_data->items_start_y && event->y <= channel_data->items_end_y) {
+ which_item = item_hit_test (channel_data, event->y);
+ if (which_item < (int) g_list_length (channel_data->items)) {
+ nautilus_news_set_prelight_index (channel_data, which_item);
+ return TRUE;
+ }
+ } else {
+ nautilus_news_set_prelight_index (channel_data, -1);
+ }
+
+ current_channel = current_channel->next;
+ }
+ return TRUE;
+}
+
+/* handle leave notify events by turning off any prelighting */
+static gint
+nautilus_news_leave_notify_event (GtkWidget *widget, GdkEventMotion *event, News *news_data )
+{
+ GList *current_channel;
+ RSSChannelData *channel_data;
+
+ /* loop through all of the channels to turn off prelighting */
+ current_channel = news_data->channel_list;
+ while (current_channel != NULL) {
+ channel_data = (RSSChannelData*) current_channel->data;
+ nautilus_news_set_prelight_index (channel_data, -1);
+ current_channel = current_channel->next;
+ }
+ return TRUE;
+}
+
+static void
+nautilus_news_set_title (RSSChannelData *channel_data, const char *title)
+{
+ if (eel_strcmp (channel_data->title, title) == 0) {
+ return;
+ }
+
+ if (channel_data->title) {
+ g_free (channel_data->title);
+ }
+ if (title != NULL) {
+ channel_data->title = g_strdup (title);
+ } else {
+ channel_data->title = NULL;
+ }
+}
+
+static void
+free_rss_data_item (RSSItemData *item)
+{
+ g_free (item->item_title);
+ g_free (item->item_url);
+ g_free (item);
+}
+
+static void
+free_rss_channel_items (RSSChannelData *channel_data)
+{
+ eel_g_list_free_deep_custom (channel_data->items, (GFunc) free_rss_data_item, NULL);
+ channel_data->items = NULL;
+}
+
+/* this frees a single channel object */
+static void
+free_channel (RSSChannelData *channel_data)
+{
+ g_free (channel_data->name);
+ g_free (channel_data->uri);
+ g_free (channel_data->link_uri);
+ g_free (channel_data->title);
+
+ if (channel_data->logo_image != NULL) {
+ gdk_pixbuf_unref (channel_data->logo_image);
+ }
+
+ if (channel_data->load_file_handle != NULL) {
+ eel_read_file_cancel (channel_data->load_file_handle);
+ }
+
+ if (channel_data->load_image_handle != NULL) {
+ eel_cancel_gdk_pixbuf_load (channel_data->load_image_handle);
+ }
+
+ free_rss_channel_items (channel_data);
+
+ g_free (channel_data);
+}
+
+/* free the entire channel list */
+static void
+nautilus_news_free_channel_list (News *news_data)
+{
+ GList *current_item;
+
+ current_item = news_data->channel_list;
+ while (current_item != NULL) {
+ free_channel ((RSSChannelData*) current_item->data);
+ current_item = current_item->next;
+ }
+
+ g_list_free (news_data->channel_list);
+ news_data->channel_list = NULL;
+}
+
+/* utility to express boolean as a string */
+static char *
+bool_to_text (gboolean value)
+{
+ return value ? "true" : "false";
+}
+
+/* build a channels xml file from the current channels state */
+static xmlDocPtr
+nautilus_news_make_channel_document (News* news_data)
+{
+ xmlDoc *channel_doc;
+ xmlNode *root_node;
+ xmlNode *channel_node;
+ RSSChannelData *channel_data;
+ GList *next_channel;
+
+ channel_doc = xmlNewDoc ("1.0");
+
+ /* add the root node to the channel document */
+ root_node = xmlNewDocNode (channel_doc, NULL, "rss_news_channels", NULL);
+ xmlDocSetRootElement (channel_doc, root_node);
+
+ /* loop through the channels, adding a node for each channel */
+ next_channel = news_data->channel_list;
+ while (next_channel != NULL) {
+ channel_node = xmlNewChild (root_node, NULL, "rss_channel", NULL);
+ channel_data = (RSSChannelData*) next_channel->data;
+
+ xmlSetProp (channel_node, "name", channel_data->name);
+ xmlSetProp (channel_node, "uri", channel_data->uri);
+ xmlSetProp (channel_node, "show", bool_to_text (channel_data->is_showing));
+ xmlSetProp (channel_node, "open", bool_to_text (channel_data->is_open));
+
+ next_channel = next_channel->next;
+ }
+ return channel_doc;
+}
+
+/* save the current channel state to disk */
+static gboolean
+nautilus_news_save_channel_state (News* news_data)
+{
+ int result;
+ char *path;
+ xmlDoc *channel_doc;
+
+ path = get_xml_path ("news_channels.xml", TRUE);
+ channel_doc = nautilus_news_make_channel_document (news_data);
+
+ result = xmlSaveFile (path, channel_doc);
+
+ g_free (path);
+ xmlFreeDoc (channel_doc);
+
+ return result > 0;
+}
+
+static void
+rss_logo_callback (GnomeVFSResult error, GdkPixbuf *pixbuf, gpointer callback_data)
+{
+ RSSChannelData *channel_data;
+
+ channel_data = (RSSChannelData*) callback_data;
+ channel_data->load_image_handle = NULL;
+
+ if (channel_data->logo_image) {
+ gdk_pixbuf_unref (channel_data->logo_image);
+ }
+
+ if (pixbuf != NULL) {
+ gdk_pixbuf_ref (pixbuf);
+ pixbuf = eel_gdk_pixbuf_scale_down_to_fit (pixbuf, 192, 40);
+ channel_data->logo_image = pixbuf;
+ update_size_and_redraw (channel_data->owner);
+ }
+}
+
+/* utility routine to extract items from a node, returning the count of items found */
+static int
+extract_items (RSSChannelData *channel_data, xmlNodePtr container_node)
+{
+ RSSItemData *item_parameters;
+ xmlNodePtr current_node, title_node, temp_node;
+ int item_count;
+ char *title, *temp_str;
+
+ current_node = container_node->childs;
+ item_count = 0;
+ while (current_node != NULL) {
+ if (eel_strcmp (current_node->name, "item") == 0) {
+ title_node = eel_xml_get_child_by_name (current_node, "title");
+ if (title_node) {
+ item_parameters = (RSSItemData*) g_new0 (RSSItemData, 1);
+
+ title = xmlNodeGetContent (title_node);
+ item_parameters->item_title = g_strdup (title);
+ xmlFree (title);
+ temp_node = eel_xml_get_child_by_name (current_node, "link");
+
+ if (temp_node) {
+ temp_str = xmlNodeGetContent (temp_node);
+ item_parameters->item_url = g_strdup (temp_str);
+ xmlFree (temp_str);
+ }
+
+ channel_data->items = g_list_append (channel_data->items, item_parameters);
+ item_count += 1;
+ }
+ }
+ current_node = current_node->next;
+ }
+ return item_count;
+}
+
+/* utility routine to resize the news display and redraw it */
+static void
+update_size_and_redraw (News* news_data)
+{
+ int display_size;
+
+ display_size = nautilus_news_update_display (news_data, TRUE);
+ gtk_widget_set_usize (news_data->news_display, -1, display_size);
+
+ nautilus_news_update_display (news_data, FALSE);
+ gtk_widget_queue_resize (GTK_WIDGET (news_data->news_display));
+ gtk_widget_queue_draw (GTK_WIDGET (news_data->news_display));
+}
+
+/* completion routine invoked when we've loaded the rss file uri. Parse the xml document, and
+ * then extract the various elements that we require */
+static void
+rss_read_done_callback (GnomeVFSResult result,
+ GnomeVFSFileSize file_size,
+ char *file_contents,
+ gpointer callback_data)
+{
+ xmlDocPtr rss_document;
+ xmlNodePtr image_node, channel_node;
+ xmlNodePtr current_node, temp_node, uri_node;
+ char *image_uri, *title, *temp_str;
+ char *error_message;
+ int item_count;
+ RSSChannelData *channel_data;
+
+ char *buffer;
+
+ channel_data = (RSSChannelData*) callback_data;
+ channel_data->load_file_handle = NULL;
+
+ /* make sure the read was successful */
+ if (result != GNOME_VFS_OK) {
+ g_assert (file_contents == NULL);
+ channel_data->update_in_progress = FALSE;
+ error_message = g_strdup_printf ("Couldn't load %s", channel_data->name);
+ nautilus_news_set_title (channel_data, error_message);
+ channel_data->error_flag = TRUE;
+ g_free (error_message);
+ return;
+ }
+
+ /* flag the update time */
+ time (&channel_data->last_update);
+
+ /* Parse the rss file with gnome-xml. The gnome-xml parser requires a zero-terminated array. */
+ buffer = g_realloc (file_contents, file_size + 1);
+ buffer[file_size] = '\0';
+ rss_document = xmlParseMemory (buffer, file_size);
+ g_free (buffer);
+
+ /* make sure there wasn't in error parsing the document */
+ if (rss_document == NULL) {
+ channel_data->update_in_progress = FALSE;
+ return;
+ }
+
+ /* extract the title and set it */
+ channel_node = eel_xml_get_child_by_name (xmlDocGetRootElement (rss_document), "channel");
+ if (channel_node != NULL) {
+ temp_node = eel_xml_get_child_by_name (channel_node, "title");
+ if (temp_node != NULL) {
+ title = xmlNodeGetContent (temp_node);
+ if (title != NULL) {
+ nautilus_news_set_title (channel_data, title);
+ xmlFree (title);
+ }
+ }
+
+ temp_node = eel_xml_get_child_by_name (channel_node, "link");
+ if (temp_node != NULL) {
+ temp_str = xmlNodeGetContent (temp_node);
+ if (temp_str != NULL) {
+ g_free (channel_data->link_uri);
+ channel_data->link_uri = g_strdup (temp_str);
+ xmlFree (temp_str);
+ }
+ }
+
+ }
+
+ /* extract the image uri and, if found, load it asynchronously */
+ /* don't reload it if we already have one */
+ if (channel_data->logo_image == NULL) {
+ image_node = eel_xml_get_child_by_name (xmlDocGetRootElement (rss_document), "image");
+
+ /* if we can't find it at the top level, look inside the channel */
+ if (image_node == NULL && channel_node != NULL) {
+ image_node = eel_xml_get_child_by_name (channel_node, "image");
+ }
+
+ if (image_node != NULL) {
+ uri_node = eel_xml_get_child_by_name (image_node, "url");
+ if (uri_node != NULL) {
+ image_uri = xmlNodeGetContent (uri_node);
+ if (image_uri != NULL) {
+ channel_data->load_image_handle = eel_gdk_pixbuf_load_async (image_uri, rss_logo_callback, channel_data);
+ xmlFree (image_uri);
+ }
+ }
+ }
+ }
+
+ /* extract the items */
+ free_rss_channel_items (channel_data);
+
+ current_node = rss_document->root;
+ item_count = extract_items (channel_data, current_node);
+
+ /* if we couldn't find any items at the main level, look inside the channel node */
+ if (item_count == 0 && channel_node != NULL) {
+ item_count = extract_items (channel_data, channel_node);
+ }
+
+ /* we're done, so free everything up */
+ xmlFreeDoc (rss_document);
+ channel_data->update_in_progress = FALSE;
+
+ /* update the size of the display aread to reflect the new content and
+ * schedule a redraw.
+ */
+ update_size_and_redraw (channel_data->owner);
+}
+
+/* initiate the loading of a channel, by fetching the rss file through gnome-vfs */
+static void
+nautilus_news_load_channel (News *news_data, RSSChannelData *channel_data)
+{
+ char *title;
+ /* load the uri asynchrounously, calling a completion routine when completed */
+
+ channel_data->update_in_progress = TRUE;
+ channel_data->load_file_handle = eel_read_entire_file_async (channel_data->uri, rss_read_done_callback, channel_data);
+
+ /* put up a title that's displayed while we wait */
+ title = g_strdup_printf ("Loading %s", channel_data->uri);
+ nautilus_news_set_title (channel_data, title);
+ g_free (title);
+}
+
+/* create a new channel object and initialize it, and start loading the content */
+static RSSChannelData*
+nautilus_news_make_new_channel (News *news_data,
+ const char *name,
+ const char* channel_uri,
+ gboolean is_open,
+ gboolean is_showing)
+{
+ RSSChannelData *channel_data;
+
+ channel_data = g_new0 (RSSChannelData, 1);
+ channel_data->name = g_strdup (name);
+ channel_data->uri = g_strdup (channel_uri);
+ channel_data->owner = news_data;
+ channel_data->update_interval = 300;
+ channel_data->prelight_index = -1;
+ channel_data->is_open = is_open;
+ channel_data->is_showing = is_showing;
+
+ if (channel_data->is_showing) {
+ nautilus_news_load_channel (news_data, channel_data);
+ }
+ return channel_data;
+}
+
+/* add the channels defined in the passed in xml document to the channel list,
+ * and start fetching the actual channel data
+ */
+static void
+nautilus_news_add_channels (News *news_data, xmlDocPtr channels)
+{
+ xmlNodePtr current_channel;
+ RSSChannelData *channel_data;
+ char *uri, *name;
+ char *open_str, *show_str;
+ gboolean is_open, is_showing;
+
+ /* walk through the children of the root object, generating new channel
+ * objects and adding them to the channel list
+ */
+ current_channel = xmlDocGetRootElement (channels)->childs;
+ while (current_channel != NULL) {
+ if (eel_strcmp (current_channel->name, "rss_channel") == 0) {
+ name = xmlGetProp (current_channel, "name");
+ uri = xmlGetProp (current_channel, "uri");
+ open_str = xmlGetProp (current_channel, "open");
+ show_str = xmlGetProp (current_channel, "show");
+
+ if (uri != NULL) {
+ is_open = eel_strcasecmp (open_str, "true") == 0;
+ is_showing = eel_strcasecmp (show_str, "true") == 0;
+
+ channel_data = nautilus_news_make_new_channel (news_data, name, uri,
+ is_open, is_showing);
+ xmlFree (uri);
+ if (channel_data != NULL) {
+ news_data->channel_list = g_list_append (news_data->channel_list, channel_data);
+ }
+ }
+ xmlFree (open_str);
+ xmlFree (show_str);
+ xmlFree (name);
+ }
+ current_channel = current_channel->next;
+ }
+}
+
+static char*
+get_xml_path (const char *file_name, gboolean force_local)
+{
+ char *xml_path;
+ char *user_directory;
+
+ user_directory = nautilus_get_user_directory ();
+
+ /* first try the user's home directory */
+ xml_path = nautilus_make_path (user_directory,
+ file_name);
+ g_free (user_directory);
+ if (force_local || g_file_exists (xml_path)) {
+ return xml_path;
+ }
+ g_free (xml_path);
+
+ /* next try the shared directory */
+ xml_path = nautilus_make_path (NAUTILUS_DATADIR,
+ file_name);
+ if (g_file_exists (xml_path)) {
+ return xml_path;
+ }
+ g_free (xml_path);
+
+ return NULL;
+}
+
+/* read the channel definition xml file and load the channels */
+static void
+read_channel_list (News *news_data)
+{
+ char *path;
+ xmlDocPtr channel_doc;
+ /* free the old channel data, if any */
+ nautilus_news_free_channel_list (news_data);
+
+ /* get the path to the local copy of the channels file */
+ path = get_xml_path ("news_channels.xml", FALSE);
+ if (path != NULL) {
+ channel_doc = xmlParseFile (path);
+
+ if (channel_doc) {
+ nautilus_news_add_channels (news_data, channel_doc);
+ xmlFreeDoc (channel_doc);
+ }
+ g_free (path);
+ }
+}
+
+/* handle periodically updating the channels if necessary */
+static int
+check_for_updates (gpointer callback_data)
+{
+ News *news_data;
+ guint current_time, next_update_time;
+ GList *current_item;
+ RSSChannelData *channel_data;
+
+ news_data = (News*) callback_data;
+ current_time = time (NULL);
+
+ /* loop through the channel list, checking to see if any need updating */
+ current_item = news_data->channel_list;
+ while (current_item != NULL) {
+ channel_data = (RSSChannelData*) current_item->data;
+ next_update_time = channel_data->last_update + channel_data->update_interval;
+
+ if (current_time > next_update_time && !channel_data->update_in_progress && channel_data->is_showing) {
+ nautilus_news_load_channel (news_data, channel_data);
+ }
+
+ current_item = current_item->next;
+ }
+
+ return TRUE;
+}
+
+/* return an image if there is a new article since last viewing, otherwise return NULL */
+static char *
+news_get_indicator_image (News *news_data)
+{
+ if (news_data->news_changed) {
+ return g_strdup ("note-indicator.png");
+ }
+ return NULL;
+}
+
+/* utility to load an xpm image */
+static void
+load_xpm_image (GdkPixbuf** image_result, const char** image_name)
+{
+ if (*image_result != NULL) {
+ gdk_pixbuf_unref (*image_result);
+ }
+ *image_result = gdk_pixbuf_new_from_xpm_data (image_name);
+}
+
+/* utility routine to load images needed by the news view */
+static void
+nautilus_news_load_images (News *news_data)
+{
+ char *news_bullet_path;
+
+ load_xpm_image (&news_data->closed_triangle, (const char**) triangle_xpm);
+ load_xpm_image (&news_data->open_triangle, (const char**) open_triangle_xpm);
+
+ if (news_data->bullet != NULL) {
+ gdk_pixbuf_unref (news_data->bullet);
+ }
+ news_bullet_path = nautilus_theme_get_image_path ("news_bullet.png");
+ if (news_bullet_path != NULL) {
+ news_data->bullet = gdk_pixbuf_new_from_file (news_bullet_path);
+ g_free (news_bullet_path);
+ }
+}
+
+/* here's the button callback routine that toggles between display modes */
+static void
+configure_button_clicked (GtkWidget *widget, News *news)
+{
+ news->configure_mode = !news->configure_mode;
+ if (news->configure_mode) {
+ gtk_widget_show_all (news->configure_box);
+ gtk_widget_hide_all (news->main_box);
+ gtk_widget_hide_all (news->add_site_box);
+ } else {
+ /* when exiting configure mode, update everything */
+ gtk_widget_show_all (news->main_box);
+ gtk_widget_hide_all (news->configure_box);
+ gtk_widget_hide_all (news->add_site_box);
+
+ nautilus_news_save_channel_state (news);
+ check_for_updates (news);
+ update_size_and_redraw (news);
+ }
+}
+
+/* here's the button callback routine that handles the add new site button
+ * by showing the relevant widgets.
+ */
+static void
+add_site_button_clicked (GtkWidget *widget, News *news)
+{
+ news->configure_mode = FALSE;
+ gtk_widget_hide_all (news->configure_box);
+ gtk_widget_show_all (news->add_site_box);
+}
+
+/* handle adding a new site from the data in the "add site" fields */
+static void
+add_site_from_fields (GtkWidget *widget, News *news)
+{
+ char *site_name, *site_location;
+ RSSChannelData *channel_data;
+ int channel_count;
+
+ site_name = gtk_entry_get_text (GTK_ENTRY (news->item_name_field));
+ site_location = gtk_entry_get_text (GTK_ENTRY (news->item_location_field));
+
+ channel_data = nautilus_news_make_new_channel (news, site_name, site_location, TRUE, TRUE);
+ if (channel_data != NULL) {
+ news->channel_list = g_list_append (news->channel_list, channel_data);
+ channel_count = g_list_length (news->channel_list);
+ add_channel_entry (news, site_name, channel_count, TRUE);
+ }
+ /* back to configure mode */
+ configure_button_clicked (widget, news);
+}
+
+/* utility routine to create the button box and constituent buttons */
+static GtkWidget *
+add_command_buttons (News *news_data, const char* label, gboolean from_configure)
+{
+ GtkWidget *frame;
+ GtkWidget *button_box;
+ GtkWidget *button;
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type( GTK_FRAME (frame), GTK_SHADOW_OUT);
+
+ button_box = gtk_hbutton_box_new ();
+
+ gtk_container_set_border_width (GTK_CONTAINER (button_box), 2);
+ gtk_container_add (GTK_CONTAINER (frame), button_box);
+
+ /* Set the appearance of the Button Box */
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (button_box), GTK_BUTTONBOX_END);
+
+ gtk_button_box_set_spacing (GTK_BUTTON_BOX (button_box), 4);
+ gtk_button_box_set_child_size (GTK_BUTTON_BOX (button_box), 24, 14);
+
+ if (from_configure) {
+ button = gtk_button_new_with_label (_("Add Site"));
+ gtk_container_add (GTK_CONTAINER (button_box), button);
+
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ (GtkSignalFunc) add_site_button_clicked, news_data);
+ }
+
+ button = gtk_button_new_with_label (label);
+ gtk_container_add (GTK_CONTAINER (button_box), button);
+
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ (GtkSignalFunc) configure_button_clicked, news_data);
+
+ return frame;
+}
+
+/* utility routine to look up a channel from it's name */
+static RSSChannelData*
+get_channel_from_name (News *news_data, const char *channel_name)
+{
+ GList *channel_item;
+ RSSChannelData *channel_data;
+
+ channel_item = news_data->channel_list;
+ while (channel_item != NULL) {
+ channel_data = (RSSChannelData*) channel_item->data;
+ if (eel_strcasecmp (channel_data->name, channel_name) == 0) {
+ return channel_data;
+ }
+ channel_item = channel_item->next;
+ }
+ return NULL;
+}
+
+/* here's the handler for handling clicks in channel check boxes */
+static void
+check_button_toggled_callback (GtkToggleButton *toggle_button, gpointer user_data)
+{
+ News *news_data;
+ char *channel_name;
+ RSSChannelData *channel_data;
+
+ news_data = (News*) user_data;
+ channel_name = gtk_object_get_data (GTK_OBJECT (toggle_button), "channel_name");
+
+ channel_data = get_channel_from_name (news_data, channel_name);
+ if (channel_data != NULL) {
+ channel_data->is_showing = !channel_data->is_showing;
+ if (channel_data->is_showing) {
+ channel_data->is_open = TRUE;
+ }
+ }
+}
+
+/* callback to maintain the current location */
+static void
+nautilus_news_load_location (NautilusView *view, const char *location, News *news)
+{
+ g_free (news->uri);
+ news->uri = g_strdup (location);
+ update_size_and_redraw (news);
+}
+
+/* utility routine to add a check-box entry to the channel list */
+static void
+add_channel_entry (News *news_data, const char *channel_name, int index, gboolean is_showing)
+{
+ GtkWidget *check_button;
+
+ check_button = gtk_check_button_new_with_label (channel_name);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), is_showing);
+ gtk_box_pack_start (GTK_BOX (news_data->checkbox_list), check_button, FALSE, FALSE, 0);
+
+ gtk_signal_connect (GTK_OBJECT (check_button), "toggled",
+ GTK_SIGNAL_FUNC (check_button_toggled_callback),
+ news_data);
+
+ /* set up user data to use in toggle handler */
+ gtk_object_set_user_data (GTK_OBJECT (check_button), news_data);
+ gtk_object_set_data_full (GTK_OBJECT (check_button),
+ "channel_name",
+ g_strdup (channel_name),
+ (GtkDestroyNotify) g_free);
+}
+
+/* here's the routine that loads and parses the xml file, then iterates through it
+ * to add channels to the enable/disable list
+ */
+static void
+add_channels_to_configure_list (News* news_data)
+{
+ char *path;
+ char *channel_name, *show_str;
+ xmlDocPtr channel_doc;
+ xmlNodePtr current_channel;
+ int channel_index;
+ gboolean is_shown;
+
+ /* read the xml file and parse it */
+ path = get_xml_path ("news_channels.xml", FALSE);
+ if (path == NULL) {
+ return;
+ }
+
+ channel_doc = xmlParseFile (path);
+ g_free (path);
+ if (channel_doc == NULL) {
+ return;
+ }
+
+ /* loop through the channel entries, adding an entry to the configure
+ * list for each entry in the file
+ */
+ current_channel = xmlDocGetRootElement (channel_doc)->childs;
+ channel_index = 0;
+ while (current_channel != NULL) {
+ if (eel_strcmp (current_channel->name, "rss_channel") == 0) {
+ channel_name = xmlGetProp (current_channel, "name");
+ show_str = xmlGetProp (current_channel, "show");
+ is_shown = eel_strcasecmp (show_str, "true") == 0;
+
+ /* add an entry to the channel list */
+ if (channel_name != NULL) {
+ add_channel_entry (news_data, channel_name, channel_index, is_shown);
+ channel_index += 1;
+ }
+
+ xmlFree (show_str);
+ xmlFree (channel_name);
+ }
+ current_channel = current_channel->next;
+ }
+
+ xmlFreeDoc (channel_doc);
+}
+
+/* allocate the add/remove location widgets */
+static void
+set_up_add_widgets (News *news, GtkWidget *container)
+{
+ GtkWidget *label;
+ GtkWidget *temp_vbox;
+ GtkWidget *expand_box;
+ GtkWidget *button_box, *button;
+
+ news->add_site_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (container), news->add_site_box, TRUE, TRUE, 0);
+
+ /* add a descriptive label */
+ label = eel_label_new (_("Add A New Site:"));
+ eel_label_set_smooth_font_size (EEL_LABEL (label), 18);
+ eel_label_set_justify (EEL_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ gtk_box_pack_start (GTK_BOX (news->add_site_box), label, FALSE, FALSE, 0);
+
+ expand_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (news->add_site_box), expand_box, TRUE, TRUE, 0);
+
+ temp_vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (temp_vbox), 4);
+ gtk_box_pack_start (GTK_BOX (expand_box), temp_vbox, FALSE, FALSE, 0);
+
+ label = eel_label_new (_("Site Name:"));
+ eel_label_set_justify (EEL_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (temp_vbox), label, FALSE, FALSE, 0);
+
+ news->item_name_field = nautilus_entry_new ();
+ gtk_box_pack_start (GTK_BOX (temp_vbox), news->item_name_field, FALSE, FALSE, 0);
+ nautilus_undo_editable_set_undo_key (GTK_EDITABLE (news->item_name_field), TRUE);
+
+ /* allocate the location field */
+ temp_vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (temp_vbox), 4);
+ gtk_box_pack_start (GTK_BOX (expand_box), temp_vbox, FALSE, FALSE, 0);
+
+ label = eel_label_new (_("Site Location:"));
+ eel_label_set_justify (EEL_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (temp_vbox), label, FALSE, FALSE, 0);
+
+ news->item_location_field = nautilus_entry_new ();
+ gtk_box_pack_start (GTK_BOX (temp_vbox), news->item_location_field, FALSE, FALSE, 0);
+ nautilus_undo_editable_set_undo_key (GTK_EDITABLE (news->item_location_field), TRUE);
+
+ /* install the add buttons */
+ button_box = gtk_hbutton_box_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (button_box), 4);
+ gtk_box_pack_start (GTK_BOX (expand_box), button_box, FALSE, FALSE, 0);
+
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (button_box), GTK_BUTTONBOX_END);
+ gtk_button_box_set_spacing (GTK_BUTTON_BOX (button_box), 4);
+ gtk_button_box_set_child_size (GTK_BUTTON_BOX (button_box), 24, 14);
+
+ button = gtk_button_new_with_label (_("Add New Site"));
+ gtk_container_add (GTK_CONTAINER (button_box), button);
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ (GtkSignalFunc) add_site_from_fields, news);
+
+ /* add the button box at the bottom with a cancel button */
+ button_box = add_command_buttons (news, _("Cancel"), FALSE);
+ gtk_box_pack_start (GTK_BOX (news->add_site_box), button_box, FALSE, FALSE, 0);
+}
+
+/* allocate the widgets for the configure mode */
+static void
+set_up_configure_widgets (News *news, GtkWidget *container)
+{
+ GtkWidget *button_box;
+ GtkWidget *viewport;
+ GtkScrolledWindow *scrolled_window;
+ GtkWidget *label;
+
+ news->configure_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (container), news->configure_box, TRUE, TRUE, 0);
+
+ /* add a descriptive label */
+ label = eel_label_new (_("Select Sites:"));
+ eel_label_set_smooth_font_size (EEL_LABEL (label), 18);
+ eel_label_set_justify (EEL_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (news->configure_box), label, FALSE, FALSE, 0);
+
+ /* allocate a table to hold the check boxes */
+ news->checkbox_list = gtk_vbox_new (FALSE, 0);
+
+ scrolled_window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+ gtk_scrolled_window_set_policy (scrolled_window, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ viewport = gtk_viewport_new (gtk_scrolled_window_get_hadjustment (scrolled_window),
+ gtk_scrolled_window_get_vadjustment (scrolled_window));
+ gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+ gtk_container_add (GTK_CONTAINER (viewport), news->checkbox_list);
+ gtk_box_pack_start (GTK_BOX (news->configure_box), GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
+
+ /* allocate the button box for the done button */
+ button_box = add_command_buttons (news, _("Done"), TRUE);
+ gtk_box_pack_start (GTK_BOX (news->configure_box), button_box, FALSE, FALSE, 0);
+}
+
+/* allocate the widgets for the main display mode */
+static void
+set_up_main_widgets (News *news, GtkWidget *container)
+{
+ GtkWidget *button_box;
+ GtkWidget *scrolled_window;
+
+ /* allocate a vbox to hold all of the main UI elements elements */
+ news->main_box = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (container), news->main_box, TRUE, TRUE, 0);
+
+ /* create and install the display area */
+ news->news_display = gtk_drawing_area_new ();
+
+ /* put the display in a scrolled window so it can scroll */
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window), news->news_display);
+ gtk_box_pack_start (GTK_BOX (news->main_box), scrolled_window, TRUE, TRUE, 0);
+
+ /* connect the appropriate signals for drawing and event handling */
+ gtk_signal_connect (GTK_OBJECT (news->news_display), "expose_event",
+ (GtkSignalFunc) nautilus_news_expose_event, news);
+ gtk_signal_connect (GTK_OBJECT(news->news_display),"configure_event",
+ (GtkSignalFunc) nautilus_news_configure_event, news);
+
+ gtk_signal_connect (GTK_OBJECT (news->news_display), "motion_notify_event",
+ (GtkSignalFunc) nautilus_news_motion_notify_event, news);
+ gtk_signal_connect (GTK_OBJECT (news->news_display), "leave_notify_event",
+ (GtkSignalFunc) nautilus_news_leave_notify_event, news);
+ gtk_signal_connect (GTK_OBJECT (news->news_display), "button_release_event",
+ (GtkSignalFunc) nautilus_news_button_release_event, news);
+
+ gtk_widget_set_events (news->news_display, GDK_EXPOSURE_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK);
+ gtk_widget_add_events (news->news_display, GDK_POINTER_MOTION_MASK);
+
+ /* create a button box to hold the command buttons */
+ button_box = add_command_buttons (news, _("Select Sites"), FALSE);
+ gtk_box_pack_start (GTK_BOX (news->main_box), button_box, FALSE, FALSE, 0);
+}
+
+static NautilusView *
+make_news_view (const char *iid, gpointer callback_data)
+{
+ News *news;
+ GtkWidget *main_container;
+
+ /* create the private data for the news view */
+ news = g_new0 (News, 1);
+
+ /* allocate the main container */
+ main_container = gtk_vbox_new (FALSE, 0);
+
+ /* set up the widgets for the main,configure and add modes */
+ set_up_main_widgets (news, main_container);
+ set_up_configure_widgets (news, main_container);
+ set_up_add_widgets (news, main_container);
+
+ /* set up the font */
+ news->font = eel_scalable_font_get_default_font ();
+
+ /* default to the main mode */
+ gtk_widget_show (main_container);
+ gtk_widget_show_all (news->main_box);
+
+ /* load some images */
+ nautilus_news_load_images (news);
+
+ /* populate the configuration list */
+ add_channels_to_configure_list (news);
+
+ /* set up the update timeout */
+ news->timer_task = gtk_timeout_add (10000, check_for_updates, news);
+
+ /* Create the nautilus view CORBA object. */
+ news->view = nautilus_view_new (main_container);
+ gtk_signal_connect (GTK_OBJECT (news->view), "destroy", do_destroy, news);
+
+ gtk_signal_connect (GTK_OBJECT (news->view), "load_location",
+ nautilus_news_load_location, news);
+
+ /* allocate a property bag to reflect the TAB_IMAGE property */
+ news->property_bag = bonobo_property_bag_new (get_bonobo_properties, set_bonobo_properties, news);
+ bonobo_control_set_properties (nautilus_view_get_bonobo_control (news->view), news->property_bag);
+ bonobo_property_bag_add (news->property_bag, "tab_image", TAB_IMAGE, BONOBO_ARG_STRING, NULL,
+ "image indicating that the news has changed", 0);
+
+ /* read the channel definition file and start loading the channels */
+ read_channel_list (news);
+
+ /* return the nautilus view */
+ return news->view;
+}
+
+int
+main(int argc, char *argv[])
+{
+ /* Make criticals and warnings stop in the debugger if NAUTILUS_DEBUG is set.
+ * Unfortunately, this has to be done explicitly for each domain.
+ */
+ if (g_getenv("NAUTILUS_DEBUG") != NULL) {
+ eel_make_warnings_and_criticals_stop_in_debugger
+ (G_LOG_DOMAIN, g_log_domain_glib, "Gdk", "Gtk", "GnomeVFS", "GnomeUI", "Bonobo", NULL);
+ }
+
+ return nautilus_view_standard_main ("nautilus-news",
+ VERSION,
+ PACKAGE,
+ GNOMELOCALEDIR,
+ argc,
+ argv,
+ "OAFIID:nautilus_news_view_factory:041601",
+ "OAFIID:nautilus_news_view:041601",
+ make_news_view,
+ nautilus_global_preferences_initialize,
+ NULL);
+}
diff --git a/components/news/news_bullet.png b/components/news/news_bullet.png
new file mode 100644
index 000000000..22d2ffdbc
--- /dev/null
+++ b/components/news/news_bullet.png
Binary files differ
diff --git a/components/news/news_channels.xml b/components/news/news_channels.xml
new file mode 100644
index 000000000..e56f3942f
--- /dev/null
+++ b/components/news/news_channels.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<rss_news_channels>
+ <rss_channel name="Beyond 2000" uri="http://beyond2000.com/b2k.rdf" show="false" open="false"/>
+ <rss_channel name="CNN" uri="http://www.cnn.com/cnn.rss" show="false" open="false"/>
+ <rss_channel name="DVD Review" uri="http://www.dvdreview.com/rss/newschannel.rss" show="false" open="false"/>
+ <rss_channel name="Freshmeat" uri="http://freshmeat.net/backend/fm.rdf" show="false" open="false"/>
+ <rss_channel name="Kuro5hin" uri="http://www.kuro5hin.org/backend.rdf" show="false" open="false"/>
+ <rss_channel name="Linux Planet" uri="http://www.linuxplanet.com/rss" show="false" open="false"/>
+ <rss_channel name="Linux Today" uri="http://linuxtoday.com/backend/my-netscape.rdf" show="true" open="true"/>
+ <rss_channel name="Macintosh News" uri="http://www.macnn.com/macnn10.rdf" show="false" open="false"/>
+ <rss_channel name="Marijuana News" uri="http://my.marijuana.com/backend.php3" show="false" open="false"/>
+ <rss_channel name="Morons" uri="http://morons.org/morons.rss" show="false" open="false"/>
+ <rss_channel name="The Motley Fool" uri="http://www.fool.com/about/headlines/rss_headlines.asp" show="false" open="false"/>
+ <rss_channel name="Newsforge" uri="http://www.newsforge.com/newsforge.rss" show="false" open="false"/>
+ <rss_channel name="Nanotechnology News" uri="http://www.nanotechnews.com/nano/rdf" show="false" open="false"/>
+ <rss_channel name="Pigdog" uri="http://www.pigdog.org/pigdog.rdf" show="false" open="false"/>
+ <rss_channel name="Quotes of the Day" uri="http://www.quotationspage.com/data/mqotd.rss" show="false" open="false"/>
+ <rss_channel name="The Register" uri="http://www.theregister.co.uk/tonys/slashdot.rdf" show="false" open="false"/>
+ <rss_channel name="Root Prompt" uri="http://www.rootprompt.org/rss" show="false" open="false"/>
+ <rss_channel name="Salon" uri="http://www.salon.com/feed/RDF/salon_use.rdf" show="true" open="false"/>
+ <rss_channel name="Scripting News" uri="http://www.scriptingnews.userland.com/xml/scriptingnews2.xml" show="false" open="false"/>
+ <rss_channel name="Slashdot" uri="http://www.slashdot.org/slashdot.rdf" show="true" open="true"/>
+ <rss_channel name="Wired" uri="http://www.wired.com/news_drop/netcenter/netcenter.rdf" show="true" open="false"/>
+ <rss_channel name="Zope" uri="http://www.zope.org/SiteIndex/news.rss" show="false" open="false"/>
+</rss_news_channels>
diff --git a/components/news/pixmaps.h b/components/news/pixmaps.h
new file mode 100644
index 000000000..8cc7b4c26
--- /dev/null
+++ b/components/news/pixmaps.h
@@ -0,0 +1,55 @@
+/* XPM images used by the news sidebar panel */
+
+static char *triangle_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"12 12 11 1",
+" c None",
+". c #FFFFFF",
+"+ c #000000",
+"@ c #E5E5E5",
+"# c #929292",
+"$ c #D0D0D0",
+"% c #6D6D6D",
+"& c #BBBCBC",
+"* c #888888",
+"= c #BBBBBB",
+"- c #A7A7A7",
+"....+.......",
+"....++......",
+"....+@+.....",
+"....+@#+....",
+"....+@##+...",
+"....+$##%+..",
+"....+&#%+*=.",
+"....+-%+*=..",
+"....+#+*=...",
+"....++*=....",
+"....+*=.....",
+".....=......"};
+
+/* XPM */
+static char *open_triangle_xpm[] = {
+"12 12 11 1",
+" c None",
+". c #FFFFFF",
+"+ c #000000",
+"@ c #E5E5E5",
+"# c #D0D0D0",
+"$ c #BBBCBC",
+"% c #A7A7A7",
+"& c #929292",
+"* c #888888",
+"= c #BBBBBB",
+"- c #6D6D6D",
+"............",
+"............",
+"............",
+"+++++++++++.",
+".+@@@#$%&+*=",
+"..+&&&&-+*=.",
+"...+&&-+*=..",
+"....+-+*=...",
+".....+*=....",
+"......=.....",
+"............",
+"............"};
diff --git a/configure.in b/configure.in
index 6e6267228..8ad9f41c2 100644
--- a/configure.in
+++ b/configure.in
@@ -938,6 +938,7 @@ components/help/converters/gnome-info2html2/Makefile
components/help/converters/gnome-man2html2/Makefile
components/image-viewer/Makefile
components/music/Makefile
+components/news/Makefile
components/notes/Makefile
components/sample/Makefile
components/mozilla/Makefile