--- /dev/null
+INCLUDES = \
+ $(GLABELS_CFLAGS) \
+ -DG_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DGTK_DISABLE_DEPRECATED \
+ -DGDK_PIXBUF_DISABLE_DEPRECATED \
+ -DGNOME_DISABLE_DEPRECATED
+
+headerdir = $(prefix)/include/gedit-@GEDIT_MAJOR@/gedit/recent-files
+header_DATA = \
+ egg-recent-model.h \
+ egg-recent-item.h \
+ egg-recent-view.h \
+ egg-recent-view-bonobo.h \
+ egg-recent-view-gtk.h \
+ egg-recent-vfs-utils.h \
+ egg-recent-util.h
+
+EGG_FILES = \
+ egg-recent-model.c \
+ egg-recent-item.c \
+ egg-recent-view.c \
+ egg-recent-view-bonobo.c \
+ egg-recent-view-gtk.c \
+ egg-recent-vfs-utils.c \
+ egg-recent-util.c \
+ $(header_DATA)
+
+noinst_LTLIBRARIES = librecent.la
+
+librecent_la_SOURCES = $(EGG_FILES)
+
+EGGDIR=$(srcdir)/../../../libegg/libegg/recent-files
+regenerate-built-sources:
+ EGGFILES="$(EGG_FILES)" EGGDIR="$(EGGDIR)" $(srcdir)/update-from-egg.sh
+
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * James Willcox <jwillcox@cs.indiana.edu>
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <glib.h>
+#include <libgnomevfs/gnome-vfs.h>
+#include <libgnomevfs/gnome-vfs-mime-utils.h>
+#include "egg-recent-item.h"
+
+
+
+EggRecentItem *
+egg_recent_item_new (void)
+{
+ EggRecentItem *item;
+
+ item = g_new (EggRecentItem, 1);
+
+ item->groups = NULL;
+ item->private = FALSE;
+ item->uri = NULL;
+ item->mime_type = NULL;
+
+ item->refcount = 1;
+
+ return item;
+}
+
+static void
+egg_recent_item_free (EggRecentItem *item)
+{
+ if (item->uri)
+ g_free (item->uri);
+
+ if (item->mime_type)
+ g_free (item->mime_type);
+
+ if (item->groups) {
+ g_list_foreach (item->groups, (GFunc)g_free, NULL);
+ g_list_free (item->groups);
+ item->groups = NULL;
+ }
+
+ g_free (item);
+}
+
+EggRecentItem *
+egg_recent_item_ref (EggRecentItem *item)
+{
+ item->refcount++;
+ return item;
+}
+
+EggRecentItem *
+egg_recent_item_unref (EggRecentItem *item)
+{
+ item->refcount--;
+
+ if (item->refcount == 0) {
+ egg_recent_item_free (item);
+ }
+
+ return item;
+}
+
+
+EggRecentItem *
+egg_recent_item_new_from_uri (const gchar *uri)
+{
+ EggRecentItem *item;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ item = egg_recent_item_new ();
+
+ if (!egg_recent_item_set_uri (item ,uri)) {
+ egg_recent_item_free (item);
+ return NULL;
+ }
+
+ item->mime_type = gnome_vfs_get_mime_type (item->uri);
+
+ if (!item->mime_type)
+ item->mime_type = g_strdup (GNOME_VFS_MIME_TYPE_UNKNOWN);
+
+ return item;
+}
+
+/*
+static GList *
+egg_recent_item_copy_groups (const GList *list)
+{
+ GList *newlist = NULL;
+
+ while (list) {
+ gchar *group = (gchar *)list->data;
+
+ newlist = g_list_prepend (newlist, g_strdup (group));
+
+ list = list->next;
+ }
+
+ return newlist;
+}
+
+
+EggRecentItem *
+egg_recent_item_copy (const EggRecentItem *item)
+{
+ EggRecentItem *newitem;
+
+ newitem = egg_recent_item_new ();
+ newitem->uri = g_strdup (item->uri);
+ if (item->mime_type)
+ newitem->mime_type = g_strdup (item->mime_type);
+ newitem->timestamp = item->timestamp;
+ newitem->private = item->private;
+ newitem->groups = egg_recent_item_copy_groups (item->groups);
+
+ return newitem;
+}
+*/
+
+/*
+EggRecentItem *
+egg_recent_item_new_valist (const gchar *uri, va_list args)
+{
+ EggRecentItem *item;
+ EggRecentArg arg;
+ gchar *str1;
+ gchar *str2;
+ gboolean priv;
+
+ item = egg_recent_item_new ();
+
+ arg = va_arg (args, EggRecentArg);
+
+ while (arg != EGG_RECENT_ARG_NONE) {
+ switch (arg) {
+ case EGG_RECENT_ARG_MIME_TYPE:
+ str1 = va_arg (args, gchar*);
+
+ egg_recent_item_set_mime_type (item, str1);
+ break;
+ case EGG_RECENT_ARG_GROUP:
+ str1 = va_arg (args, gchar*);
+
+ egg_recent_item_add_group (item, str1);
+ break;
+ case EGG_RECENT_ARG_PRIVATE:
+ priv = va_arg (args, gboolean);
+
+ egg_recent_item_set_private (item, priv);
+ break;
+ default:
+ break;
+ }
+
+ arg = va_arg (args, EggRecentArg);
+ }
+
+ return item;
+}
+*/
+
+gboolean
+egg_recent_item_set_uri (EggRecentItem *item, const gchar *uri)
+{
+ gchar *utf8_uri;
+
+ /* if G_BROKEN_FILENAMES is not set, this should succede */
+ if (g_utf8_validate (uri, -1, NULL)) {
+ item->uri = gnome_vfs_make_uri_from_input (uri);
+ } else {
+ utf8_uri = g_filename_to_utf8 (uri, -1, NULL, NULL, NULL);
+
+ if (utf8_uri == NULL) {
+ g_warning ("Couldn't convert URI to UTF-8");
+ return FALSE;
+ }
+
+ if (g_utf8_validate (utf8_uri, -1, NULL)) {
+ item->uri = gnome_vfs_make_uri_from_input (utf8_uri);
+ } else {
+ g_free (utf8_uri);
+ return FALSE;
+ }
+
+ g_free (utf8_uri);
+ }
+
+ return TRUE;
+}
+
+gchar *
+egg_recent_item_get_uri (const EggRecentItem *item)
+{
+ return g_strdup (item->uri);
+}
+
+G_CONST_RETURN gchar *
+egg_recent_item_peek_uri (const EggRecentItem *item)
+{
+ return item->uri;
+}
+
+gchar *
+egg_recent_item_get_uri_utf8 (const EggRecentItem *item)
+{
+ /* this could fail, but it's not likely, since we've already done it
+ * once in set_uri()
+ */
+ return g_filename_to_utf8 (item->uri, -1, NULL, NULL, NULL);
+}
+
+gchar *
+egg_recent_item_get_uri_for_display (const EggRecentItem *item)
+{
+ return gnome_vfs_format_uri_for_display (item->uri);
+}
+
+void
+egg_recent_item_set_mime_type (EggRecentItem *item, const gchar *mime)
+{
+ item->mime_type = g_strdup (mime);
+}
+
+gchar *
+egg_recent_item_get_mime_type (const EggRecentItem *item)
+{
+ return g_strdup (item->mime_type);
+}
+
+void
+egg_recent_item_set_timestamp (EggRecentItem *item, time_t timestamp)
+{
+ if (timestamp == (time_t) -1)
+ time (×tamp);
+
+ item->timestamp = timestamp;
+}
+
+time_t
+egg_recent_item_get_timestamp (const EggRecentItem *item)
+{
+ return item->timestamp;
+}
+
+G_CONST_RETURN GList *
+egg_recent_item_get_groups (const EggRecentItem *item)
+{
+ return item->groups;
+}
+
+gboolean
+egg_recent_item_in_group (const EggRecentItem *item, const gchar *group_name)
+{
+ GList *tmp;
+
+ tmp = item->groups;
+ while (tmp != NULL) {
+ gchar *val = (gchar *)tmp->data;
+
+ if (strcmp (group_name, val) == 0)
+ return TRUE;
+
+ tmp = tmp->next;
+ }
+
+ return FALSE;
+}
+
+void
+egg_recent_item_add_group (EggRecentItem *item, const gchar *group_name)
+{
+ g_return_if_fail (group_name != NULL);
+
+ if (!egg_recent_item_in_group (item, group_name))
+ item->groups = g_list_append (item->groups, g_strdup (group_name));
+}
+
+void
+egg_recent_item_remove_group (EggRecentItem *item, const gchar *group_name)
+{
+ GList *tmp;
+
+ g_return_if_fail (group_name != NULL);
+
+ tmp = item->groups;
+ while (tmp != NULL) {
+ gchar *val = (gchar *)tmp->data;
+
+ if (strcmp (group_name, val) == 0) {
+ item->groups = g_list_remove (item->groups,
+ val);
+ g_free (val);
+ break;
+ }
+
+ tmp = tmp->next;
+ }
+}
+
+void
+egg_recent_item_set_private (EggRecentItem *item, gboolean priv)
+{
+ item->private = priv;
+}
+
+gboolean
+egg_recent_item_get_private (const EggRecentItem *item)
+{
+ return item->private;
+}
+
+GType
+egg_recent_item_get_type (void)
+{
+ static GType boxed_type = 0;
+
+ if (!boxed_type) {
+ boxed_type = g_boxed_type_register_static ("EggRecentItem",
+ (GBoxedCopyFunc)egg_recent_item_ref,
+ (GBoxedFreeFunc)egg_recent_item_unref);
+ }
+
+ return boxed_type;
+}
--- /dev/null
+
+#ifndef __EGG_RECENT_ITEM_H__
+#define __EGG_RECENT_ITEM_H__
+
+#include <time.h>
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_RECENT_ITEM (egg_recent_item_get_type ())
+
+#define EGG_RECENT_ITEM_LIST_UNREF(list) \
+ g_list_foreach (list, (GFunc)egg_recent_item_unref, NULL); \
+ g_list_free (list);
+
+typedef struct _EggRecentItem EggRecentItem;
+
+struct _EggRecentItem {
+ /* do not access any of these directly */
+ gchar *uri;
+ gchar *mime_type;
+ time_t timestamp;
+
+ gboolean private;
+
+ GList *groups;
+
+ int refcount;
+};
+
+GType egg_recent_item_get_type (void) G_GNUC_CONST;
+
+/* constructors */
+EggRecentItem * egg_recent_item_new (void);
+
+EggRecentItem * egg_recent_item_ref (EggRecentItem *item);
+EggRecentItem * egg_recent_item_unref (EggRecentItem *item);
+
+/* automatically fetches the mime type, etc */
+EggRecentItem * egg_recent_item_new_from_uri (const gchar *uri);
+
+gboolean egg_recent_item_set_uri (EggRecentItem *item, const gchar *uri);
+gchar * egg_recent_item_get_uri (const EggRecentItem *item);
+gchar * egg_recent_item_get_uri_utf8 (const EggRecentItem *item);
+gchar * egg_recent_item_get_uri_for_display (const EggRecentItem *item);
+
+void egg_recent_item_set_mime_type (EggRecentItem *item, const gchar *mime);
+gchar * egg_recent_item_get_mime_type (const EggRecentItem *item);
+
+void egg_recent_item_set_timestamp (EggRecentItem *item, time_t timestamp);
+time_t egg_recent_item_get_timestamp (const EggRecentItem *item);
+
+G_CONST_RETURN gchar *egg_recent_item_peek_uri (const EggRecentItem *item);
+
+
+/* groups */
+G_CONST_RETURN GList * egg_recent_item_get_groups (const EggRecentItem *item);
+
+gboolean egg_recent_item_in_group (const EggRecentItem *item,
+ const gchar *group_name);
+
+void egg_recent_item_add_group (EggRecentItem *item,
+ const gchar *group_name);
+
+void egg_recent_item_remove_group (EggRecentItem *item,
+ const gchar *group_name);
+
+void egg_recent_item_set_private (EggRecentItem *item,
+ gboolean priv);
+
+gboolean egg_recent_item_get_private (const EggRecentItem *item);
+
+
+G_END_DECLS
+
+#endif /* __EGG_RECENT_ITEM_H__ */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * James Willcox <jwillcox@cs.indiana.edu>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <time.h>
+#include <gtk/gtk.h>
+#include <libgnomevfs/gnome-vfs.h>
+#include <libgnomevfs/gnome-vfs-mime-utils.h>
+#include <gconf/gconf-client.h>
+#include "egg-recent-model.h"
+#include "egg-recent-item.h"
+
+#define EGG_RECENT_MODEL_FILE_PATH "/.recently-used"
+#define EGG_RECENT_MODEL_BUFFER_SIZE 8192
+
+#define EGG_RECENT_MODEL_MAX_ITEMS 500
+#define EGG_RECENT_MODEL_DEFAULT_LIMIT 10
+#define EGG_RECENT_MODEL_TIMEOUT_LENGTH 200
+
+#define EGG_RECENT_MODEL_KEY_DIR "/desktop/gnome/recent_files"
+#define EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY EGG_RECENT_MODEL_KEY_DIR "/default_limit"
+#define EGG_RECENT_MODEL_EXPIRE_KEY EGG_RECENT_MODEL_KEY_DIR "/expire"
+
+struct _EggRecentModelPrivate {
+ GSList *mime_filter_values; /* list of mime types we allow */
+ GSList *group_filter_values; /* list of groups we allow */
+ GSList *scheme_filter_values; /* list of URI schemes we allow */
+
+ EggRecentModelSort sort_type; /* type of sorting to be done */
+
+ int limit; /* soft limit for length of the list */
+ int expire_days; /* number of days to hold an item */
+
+ char *path; /* path to the file we store stuff in */
+
+ GHashTable *monitors;
+
+ GnomeVFSMonitorHandle *monitor;
+
+ GConfClient *client;
+ gboolean use_default_limit;
+
+ guint limit_change_notify_id;
+ guint expiration_change_notify_id;
+
+ guint changed_timeout;
+};
+
+/* signals */
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static GType model_signals[LAST_SIGNAL] = { 0 };
+
+/* properties */
+enum {
+ PROP_BOGUS,
+ PROP_MIME_FILTERS,
+ PROP_GROUP_FILTERS,
+ PROP_SCHEME_FILTERS,
+ PROP_SORT_TYPE,
+ PROP_LIMIT
+};
+
+typedef struct {
+ GSList *states;
+ GList *items;
+ EggRecentItem *current_item;
+}ParseInfo;
+
+typedef enum {
+ STATE_START,
+ STATE_RECENT_FILES,
+ STATE_RECENT_ITEM,
+ STATE_URI,
+ STATE_MIME_TYPE,
+ STATE_TIMESTAMP,
+ STATE_PRIVATE,
+ STATE_GROUPS,
+ STATE_GROUP
+} ParseState;
+
+typedef struct _ChangedData {
+ EggRecentModel *model;
+ GList *list;
+}ChangedData;
+
+#define TAG_RECENT_FILES "RecentFiles"
+#define TAG_RECENT_ITEM "RecentItem"
+#define TAG_URI "URI"
+#define TAG_MIME_TYPE "Mime-Type"
+#define TAG_TIMESTAMP "Timestamp"
+#define TAG_PRIVATE "Private"
+#define TAG_GROUPS "Groups"
+#define TAG_GROUP "Group"
+
+static void start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+
+static void end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+
+static void text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+static void error_handler (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data);
+
+static GMarkupParser parser = {start_element_handler, end_element_handler,
+ text_handler,
+ NULL,
+ error_handler};
+
+static gboolean
+egg_recent_model_string_match (const GSList *list, const gchar *str)
+{
+ const GSList *tmp;
+
+ if (list == NULL || str == NULL)
+ return TRUE;
+
+ tmp = list;
+
+ while (tmp) {
+ if (g_pattern_match_string (tmp->data, str))
+ return TRUE;
+
+ tmp = tmp->next;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+egg_recent_model_write_raw (EggRecentModel *model, FILE *file,
+ const gchar *content)
+{
+ int len;
+ int fd;
+ struct stat sbuf;
+
+ rewind (file);
+
+ len = strlen (content);
+ fd = fileno (file);
+
+ if (fstat (fd, &sbuf) < 0)
+ g_warning ("Couldn't stat XML document.");
+
+ if ((off_t)len < sbuf.st_size) {
+ ftruncate (fd, len);
+ }
+
+ if (fputs (content, file) == EOF)
+ return FALSE;
+
+ fsync (fd);
+ rewind (file);
+
+ return TRUE;
+}
+
+static GList *
+egg_recent_model_delete_from_list (GList *list,
+ const gchar *uri)
+{
+ GList *tmp;
+
+ if (!uri)
+ return list;
+
+ tmp = list;
+
+ while (tmp) {
+ EggRecentItem *item = tmp->data;
+ GList *next;
+
+ next = tmp->next;
+
+ if (!strcmp (egg_recent_item_peek_uri (item), uri)) {
+ egg_recent_item_unref (item);
+
+ list = g_list_remove_link (list, tmp);
+ g_list_free_1 (tmp);
+ }
+
+ tmp = next;
+ }
+
+ return list;
+}
+
+static void
+egg_recent_model_add_new_groups (EggRecentItem *item,
+ EggRecentItem *upd_item)
+{
+ const GList *tmp;
+
+ tmp = egg_recent_item_get_groups (upd_item);
+
+ while (tmp) {
+ char *group = tmp->data;
+
+ if (!egg_recent_item_in_group (item, group))
+ egg_recent_item_add_group (item, group);
+
+ tmp = tmp->next;
+ }
+}
+
+static gboolean
+egg_recent_model_update_item (GList *items, EggRecentItem *upd_item)
+{
+ GList *tmp;
+ const char *uri;
+
+ uri = egg_recent_item_peek_uri (upd_item);
+
+ tmp = items;
+
+ while (tmp) {
+ EggRecentItem *item = tmp->data;
+
+ if (gnome_vfs_uris_match (egg_recent_item_peek_uri (item), uri)) {
+ egg_recent_item_set_timestamp (item, (time_t) -1);
+
+ egg_recent_model_add_new_groups (item, upd_item);
+
+ return TRUE;
+ }
+
+ tmp = tmp->next;
+ }
+
+ return FALSE;
+}
+
+static gchar *
+egg_recent_model_read_raw (EggRecentModel *model, FILE *file)
+{
+ GString *string;
+ char buf[EGG_RECENT_MODEL_BUFFER_SIZE];
+
+ rewind (file);
+
+ string = g_string_new (NULL);
+ while (fgets (buf, EGG_RECENT_MODEL_BUFFER_SIZE, file)) {
+ string = g_string_append (string, buf);
+ }
+
+ rewind (file);
+
+ return g_string_free (string, FALSE);
+}
+
+
+
+static void
+parse_info_init (ParseInfo *info)
+{
+ info->states = g_slist_prepend (NULL, STATE_START);
+ info->items = NULL;
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+ g_slist_free (info->states);
+}
+
+static void
+push_state (ParseInfo *info,
+ ParseState state)
+{
+ info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+ g_return_if_fail (info->states != NULL);
+
+ info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+ g_return_val_if_fail (info->states != NULL, STATE_START);
+
+ return GPOINTER_TO_INT (info->states->data);
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = (ParseInfo *)user_data;
+
+ if (ELEMENT_IS (TAG_RECENT_FILES))
+ push_state (info, STATE_RECENT_FILES);
+ else if (ELEMENT_IS (TAG_RECENT_ITEM)) {
+ info->current_item = egg_recent_item_new ();
+ push_state (info, STATE_RECENT_ITEM);
+ } else if (ELEMENT_IS (TAG_URI))
+ push_state (info, STATE_URI);
+ else if (ELEMENT_IS (TAG_MIME_TYPE))
+ push_state (info, STATE_MIME_TYPE);
+ else if (ELEMENT_IS (TAG_TIMESTAMP))
+ push_state (info, STATE_TIMESTAMP);
+ else if (ELEMENT_IS (TAG_PRIVATE)) {
+ push_state (info, STATE_PRIVATE);
+ egg_recent_item_set_private (info->current_item, TRUE);
+ } else if (ELEMENT_IS (TAG_GROUPS))
+ push_state (info, STATE_GROUPS);
+ else if (ELEMENT_IS (TAG_GROUP))
+ push_state (info, STATE_GROUP);
+}
+
+static gint
+list_compare_func_mru (gpointer a, gpointer b)
+{
+ EggRecentItem *item_a = (EggRecentItem *)a;
+ EggRecentItem *item_b = (EggRecentItem *)b;
+
+ return item_a->timestamp < item_b->timestamp;
+}
+
+static gint
+list_compare_func_lru (gpointer a, gpointer b)
+{
+ EggRecentItem *item_a = (EggRecentItem *)a;
+ EggRecentItem *item_b = (EggRecentItem *)b;
+
+ return item_a->timestamp > item_b->timestamp;
+}
+
+
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = (ParseInfo *)user_data;
+
+ switch (peek_state (info)) {
+ case STATE_RECENT_ITEM:
+ info->items = g_list_append (info->items,
+ info->current_item);
+ if (info->current_item->uri == NULL ||
+ strlen (info->current_item->uri) == 0)
+ g_warning ("URI NOT LOADED");
+ break;
+ default:
+ break;
+ }
+
+ pop_state (info);
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = (ParseInfo *)user_data;
+
+ switch (peek_state (info)) {
+ case STATE_START:
+ case STATE_RECENT_FILES:
+ case STATE_RECENT_ITEM:
+ case STATE_PRIVATE:
+ case STATE_GROUPS:
+ break;
+ case STATE_URI:
+ egg_recent_item_set_uri (info->current_item, text);
+ break;
+ case STATE_MIME_TYPE:
+ egg_recent_item_set_mime_type (info->current_item,
+ text);
+ break;
+ case STATE_TIMESTAMP:
+ egg_recent_item_set_timestamp (info->current_item,
+ (time_t)atoi (text));
+ break;
+ case STATE_GROUP:
+ egg_recent_item_add_group (info->current_item,
+ text);
+ break;
+ }
+
+}
+
+static void
+error_handler (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ g_warning ("Error in parse: %s", error->message);
+}
+
+static void
+egg_recent_model_enforce_limit (GList *list, int limit)
+{
+ int len;
+ GList *end;
+
+ /* limit < 0 means unlimited */
+ if (limit <= 0)
+ return;
+
+ len = g_list_length (list);
+
+ if (len > limit) {
+ GList *next;
+
+ end = g_list_nth (list, limit-1);
+ next = end->next;
+
+ end->next = NULL;
+
+ EGG_RECENT_ITEM_LIST_UNREF (next);
+ }
+}
+
+static GList *
+egg_recent_model_sort (EggRecentModel *model, GList *list)
+{
+ switch (model->priv->sort_type) {
+ case EGG_RECENT_MODEL_SORT_MRU:
+ list = g_list_sort (list,
+ (GCompareFunc)list_compare_func_mru);
+ break;
+ case EGG_RECENT_MODEL_SORT_LRU:
+ list = g_list_sort (list,
+ (GCompareFunc)list_compare_func_lru);
+ break;
+ case EGG_RECENT_MODEL_SORT_NONE:
+ break;
+ }
+
+ return list;
+}
+
+static gboolean
+egg_recent_model_group_match (EggRecentItem *item, GSList *groups)
+{
+ GSList *tmp;
+
+ tmp = groups;
+
+ while (tmp != NULL) {
+ const gchar * group = (const gchar *)tmp->data;
+
+ if (egg_recent_item_in_group (item, group))
+ return TRUE;
+
+ tmp = tmp->next;
+ }
+
+ return FALSE;
+}
+
+static GList *
+egg_recent_model_filter (EggRecentModel *model,
+ GList *list)
+{
+ EggRecentItem *item;
+ GList *newlist = NULL;
+ gchar *mime_type;
+ gchar *uri;
+
+ g_return_val_if_fail (list != NULL, NULL);
+
+ while (list) {
+ gboolean pass_mime_test = FALSE;
+ gboolean pass_group_test = FALSE;
+ gboolean pass_scheme_test = FALSE;
+ item = (EggRecentItem *)list->data;
+ list = list->next;
+
+ uri = egg_recent_item_get_uri (item);
+
+ /* filter by mime type */
+ if (model->priv->mime_filter_values != NULL) {
+ mime_type = egg_recent_item_get_mime_type (item);
+
+ if (egg_recent_model_string_match
+ (model->priv->mime_filter_values,
+ mime_type))
+ pass_mime_test = TRUE;
+
+ g_free (mime_type);
+ } else
+ pass_mime_test = TRUE;
+
+ /* filter by group */
+ if (pass_mime_test && model->priv->group_filter_values != NULL) {
+ if (egg_recent_model_group_match
+ (item, model->priv->group_filter_values))
+ pass_group_test = TRUE;
+ } else if (egg_recent_item_get_private (item)) {
+ pass_group_test = FALSE;
+ } else
+ pass_group_test = TRUE;
+
+ /* filter by URI scheme */
+ if (pass_mime_test && pass_group_test &&
+ model->priv->scheme_filter_values != NULL) {
+ gchar *scheme;
+
+ scheme = gnome_vfs_get_uri_scheme (uri);
+
+ if (egg_recent_model_string_match
+ (model->priv->scheme_filter_values, scheme))
+ pass_scheme_test = TRUE;
+
+ g_free (scheme);
+ } else
+ pass_scheme_test = TRUE;
+
+ if (pass_mime_test && pass_group_test && pass_scheme_test)
+ newlist = g_list_prepend (newlist, item);
+
+ g_free (uri);
+ }
+
+ if (newlist) {
+ newlist = g_list_reverse (newlist);
+ g_list_free (list);
+ }
+
+
+ return newlist;
+}
+
+
+
+#if 0
+static void
+egg_recent_model_monitor_list_cb (GnomeVFSMonitorHandle *handle,
+ const gchar *monitor_uri,
+ const gchar *info_uri,
+ GnomeVFSMonitorEventType event_type,
+ gpointer user_data)
+{
+ EggRecentModel *model;
+
+ model = EGG_RECENT_MODEL (user_data);
+
+ if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED) {
+ egg_recent_model_delete (model, monitor_uri);
+ g_hash_table_remove (model->priv->monitors, monitor_uri);
+ }
+}
+
+
+
+static void
+egg_recent_model_monitor_list (EggRecentModel *model, GList *list)
+{
+ GList *tmp;
+
+ tmp = list;
+ while (tmp) {
+ EggRecentItem *item = (EggRecentItem *)tmp->data;
+ GnomeVFSMonitorHandle *handle;
+ GnomeVFSResult res;
+ gchar *uri;
+
+ tmp = tmp->next;
+
+ uri = egg_recent_item_get_uri (item);
+ if (g_hash_table_lookup (model->priv->monitors, uri)) {
+ /* already monitoring this one */
+ g_free (uri);
+ continue;
+ }
+
+ res = gnome_vfs_monitor_add (&handle, uri,
+ GNOME_VFS_MONITOR_FILE,
+ egg_recent_model_monitor_list_cb,
+ model);
+
+ if (res == GNOME_VFS_OK)
+ g_hash_table_insert (model->priv->monitors, uri, handle);
+ else
+ g_free (uri);
+ }
+}
+#endif
+
+
+static gboolean
+egg_recent_model_changed_timeout (EggRecentModel *model)
+{
+ egg_recent_model_changed (model);
+
+ return FALSE;
+}
+
+static void
+egg_recent_model_monitor_cb (GnomeVFSMonitorHandle *handle,
+ const gchar *monitor_uri,
+ const gchar *info_uri,
+ GnomeVFSMonitorEventType event_type,
+ gpointer user_data)
+{
+ EggRecentModel *model;
+
+ g_return_if_fail (user_data != NULL);
+ g_return_if_fail (EGG_IS_RECENT_MODEL (user_data));
+ model = EGG_RECENT_MODEL (user_data);
+
+ if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) {
+ if (model->priv->changed_timeout > 0) {
+ g_source_remove (model->priv->changed_timeout);
+ }
+
+ model->priv->changed_timeout = g_timeout_add (
+ EGG_RECENT_MODEL_TIMEOUT_LENGTH,
+ (GSourceFunc)egg_recent_model_changed_timeout,
+ model);
+ }
+}
+
+static void
+egg_recent_model_monitor (EggRecentModel *model, gboolean should_monitor)
+{
+ if (should_monitor && model->priv->monitor == NULL) {
+
+ gnome_vfs_monitor_add (&model->priv->monitor,
+ model->priv->path,
+ GNOME_VFS_MONITOR_FILE,
+ egg_recent_model_monitor_cb,
+ model);
+
+ /* if the above fails, don't worry about it.
+ * local notifications will still happen
+ */
+
+ } else if (!should_monitor && model->priv->monitor != NULL) {
+ gnome_vfs_monitor_cancel (model->priv->monitor);
+ model->priv->monitor = NULL;
+ }
+}
+
+static void
+egg_recent_model_set_limit_internal (EggRecentModel *model, int limit)
+{
+ model->priv->limit = limit;
+
+ if (limit <= 0)
+ egg_recent_model_monitor (model, FALSE);
+ else {
+ egg_recent_model_monitor (model, TRUE);
+ egg_recent_model_changed (model);
+ }
+}
+
+static GList *
+egg_recent_model_read (EggRecentModel *model, FILE *file)
+{
+ GList *list=NULL;
+ gchar *content;
+ GMarkupParseContext *ctx;
+ ParseInfo info;
+ GError *error;
+
+ content = egg_recent_model_read_raw (model, file);
+
+ if (strlen (content) <= 0) {
+ g_free (content);
+ return NULL;
+ }
+
+ parse_info_init (&info);
+
+ ctx = g_markup_parse_context_new (&parser, 0, &info, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (ctx, content, strlen (content),
+ &error)) {
+ g_warning (error->message);
+ g_error_free (error);
+ error = NULL;
+ goto out;
+ }
+
+ error = NULL;
+ if (!g_markup_parse_context_end_parse (ctx, &error))
+ goto out;
+
+ g_markup_parse_context_free (ctx);
+out:
+ list = info.items;
+
+ parse_info_free (&info);
+
+ g_free (content);
+
+ /*
+ g_print ("Total items: %d\n", g_list_length (list));
+ */
+
+ return list;
+}
+
+
+static gboolean
+egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list)
+{
+ GString *string;
+ gchar *data;
+ EggRecentItem *item;
+ const GList *groups;
+ int i;
+ int ret;
+
+ string = g_string_new ("<?xml version=\"1.0\"?>\n");
+ string = g_string_append (string, "<" TAG_RECENT_FILES ">\n");
+
+ i=0;
+ while (list) {
+ gchar *uri;
+ gchar *mime_type;
+ gchar *escaped_uri;
+ time_t timestamp;
+ item = (EggRecentItem *)list->data;
+
+
+ uri = egg_recent_item_get_uri_utf8 (item);
+ escaped_uri = g_markup_escape_text (uri,
+ strlen (uri));
+ g_free (uri);
+
+ mime_type = egg_recent_item_get_mime_type (item);
+ timestamp = egg_recent_item_get_timestamp (item);
+
+ string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n");
+
+ g_string_append_printf (string,
+ " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri);
+
+ if (mime_type)
+ g_string_append_printf (string,
+ " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type);
+ else
+ g_string_append_printf (string,
+ " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n");
+
+
+ g_string_append_printf (string,
+ " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp);
+
+ if (egg_recent_item_get_private (item))
+ string = g_string_append (string,
+ " <" TAG_PRIVATE "/>\n");
+
+ /* write the groups */
+ string = g_string_append (string,
+ " <" TAG_GROUPS ">\n");
+ groups = egg_recent_item_get_groups (item);
+
+ if (groups == NULL && egg_recent_item_get_private (item))
+ g_warning ("Item with URI \"%s\" marked as private, but"
+ " does not belong to any groups.\n", uri);
+
+ while (groups) {
+ const gchar *group = (const gchar *)groups->data;
+ gchar *escaped_group;
+
+ escaped_group = g_markup_escape_text (group, strlen(group));
+
+ g_string_append_printf (string,
+ " <" TAG_GROUP ">%s</" TAG_GROUP ">\n",
+ escaped_group);
+
+ g_free (escaped_group);
+
+ groups = groups->next;
+ }
+
+ string = g_string_append (string, " </" TAG_GROUPS ">\n");
+
+ string = g_string_append (string,
+ " </" TAG_RECENT_ITEM ">\n");
+
+ g_free (mime_type);
+ g_free (escaped_uri);
+
+ list = list->next;
+ i++;
+ }
+
+ string = g_string_append (string, "</" TAG_RECENT_FILES ">");
+
+ data = g_string_free (string, FALSE);
+
+ ret = egg_recent_model_write_raw (model, file, data);
+
+ g_free (data);
+
+ return ret;
+}
+
+static FILE *
+egg_recent_model_open_file (EggRecentModel *model)
+{
+ FILE *file;
+
+ file = fopen (model->priv->path, "r+");
+ if (file == NULL) {
+ /* be paranoid */
+ umask (077);
+
+ file = fopen (model->priv->path, "w+");
+
+ g_return_val_if_fail (file != NULL, NULL);
+ }
+
+ return file;
+}
+
+static gboolean
+egg_recent_model_lock_file (FILE *file)
+{
+ int fd;
+
+ rewind (file);
+ fd = fileno (file);
+
+ return lockf (fd, F_LOCK, 0) == 0 ? TRUE : FALSE;
+}
+
+static gboolean
+egg_recent_model_unlock_file (FILE *file)
+{
+ int fd;
+
+ rewind (file);
+ fd = fileno (file);
+
+ return lockf (fd, F_ULOCK, 0) < 0 ? FALSE : TRUE;
+}
+
+static void
+egg_recent_model_finalize (GObject *object)
+{
+ EggRecentModel *model = EGG_RECENT_MODEL (object);
+
+ egg_recent_model_monitor (model, FALSE);
+
+
+ g_slist_foreach (model->priv->mime_filter_values,
+ (GFunc) g_pattern_spec_free, NULL);
+ g_slist_free (model->priv->mime_filter_values);
+ model->priv->mime_filter_values = NULL;
+
+ g_slist_foreach (model->priv->scheme_filter_values,
+ (GFunc) g_pattern_spec_free, NULL);
+ g_slist_free (model->priv->scheme_filter_values);
+ model->priv->scheme_filter_values = NULL;
+
+ g_slist_foreach (model->priv->group_filter_values,
+ (GFunc) g_free, NULL);
+ g_slist_free (model->priv->group_filter_values);
+ model->priv->group_filter_values = NULL;
+
+
+ if (model->priv->limit_change_notify_id)
+ gconf_client_notify_remove (model->priv->client,
+ model->priv->limit_change_notify_id);
+ model->priv->expiration_change_notify_id = 0;
+
+ if (model->priv->expiration_change_notify_id)
+ gconf_client_notify_remove (model->priv->client,
+ model->priv->expiration_change_notify_id);
+ model->priv->expiration_change_notify_id = 0;
+
+ g_object_unref (model->priv->client);
+ model->priv->client = NULL;
+
+
+ g_free (model->priv->path);
+ model->priv->path = NULL;
+
+ g_hash_table_destroy (model->priv->monitors);
+ model->priv->monitors = NULL;
+
+
+ g_free (model->priv);
+}
+
+static void
+egg_recent_model_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggRecentModel *model = EGG_RECENT_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_MIME_FILTERS:
+ model->priv->mime_filter_values =
+ (GSList *)g_value_get_pointer (value);
+ break;
+
+ case PROP_GROUP_FILTERS:
+ model->priv->group_filter_values =
+ (GSList *)g_value_get_pointer (value);
+ break;
+
+ case PROP_SCHEME_FILTERS:
+ model->priv->scheme_filter_values =
+ (GSList *)g_value_get_pointer (value);
+ break;
+
+ case PROP_SORT_TYPE:
+ model->priv->sort_type = g_value_get_int (value);
+ break;
+
+ case PROP_LIMIT:
+ egg_recent_model_set_limit (model,
+ g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+egg_recent_model_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggRecentModel *model = EGG_RECENT_MODEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_MIME_FILTERS:
+ g_value_set_pointer (value, model->priv->mime_filter_values);
+ break;
+
+ case PROP_GROUP_FILTERS:
+ g_value_set_pointer (value, model->priv->group_filter_values);
+ break;
+
+ case PROP_SCHEME_FILTERS:
+ g_value_set_pointer (value, model->priv->scheme_filter_values);
+ break;
+
+ case PROP_SORT_TYPE:
+ g_value_set_int (value, model->priv->sort_type);
+ break;
+
+ case PROP_LIMIT:
+ g_value_set_int (value, model->priv->limit);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+egg_recent_model_class_init (EggRecentModelClass * klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->set_property = egg_recent_model_set_property;
+ object_class->get_property = egg_recent_model_get_property;
+ object_class->finalize = egg_recent_model_finalize;
+
+ model_signals[CHANGED] = g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggRecentModelClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+
+ g_object_class_install_property (object_class,
+ PROP_MIME_FILTERS,
+ g_param_spec_pointer ("mime-filters",
+ "Mime Filters",
+ "List of mime types to be allowed.",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_GROUP_FILTERS,
+ g_param_spec_pointer ("group-filters",
+ "Group Filters",
+ "List of groups to be allowed.",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SCHEME_FILTERS,
+ g_param_spec_pointer ("scheme-filters",
+ "Scheme Filters",
+ "List of URI schemes to be allowed.",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SORT_TYPE,
+ g_param_spec_int ("sort-type",
+ "Sort Type",
+ "Type of sorting to be done.",
+ 0, EGG_RECENT_MODEL_SORT_NONE,
+ EGG_RECENT_MODEL_SORT_MRU,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_LIMIT,
+ g_param_spec_int ("limit",
+ "Limit",
+ "Max number of items allowed.",
+ -1, EGG_RECENT_MODEL_MAX_ITEMS,
+ EGG_RECENT_MODEL_DEFAULT_LIMIT,
+ G_PARAM_READWRITE));
+
+ klass->changed = NULL;
+}
+
+
+
+static void
+egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id,
+ GConfEntry *entry, gpointer user_data)
+{
+ EggRecentModel *model;
+ GConfValue *value;
+
+ model = EGG_RECENT_MODEL (user_data);
+
+ g_return_if_fail (model != NULL);
+
+ if (model->priv->use_default_limit == FALSE)
+ return; /* ignore this key */
+
+ /* the key was unset, and the schema has apparently failed */
+ if (entry == NULL)
+ return;
+
+ value = gconf_entry_get_value (entry);
+
+ if (value->type != GCONF_VALUE_INT) {
+ g_warning ("Expected GConfValue of type integer, "
+ "got something else");
+ }
+
+
+ egg_recent_model_set_limit_internal (model, gconf_value_get_int (value));
+}
+
+static void
+egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id,
+ GConfEntry *entry, gpointer user_data)
+{
+
+}
+
+static void
+egg_recent_model_init (EggRecentModel * model)
+{
+ if (!gnome_vfs_init ()) {
+ g_warning ("gnome-vfs initialization failed.");
+ return;
+ }
+
+
+ model->priv = g_new0 (EggRecentModelPrivate, 1);
+
+ model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH,
+ g_get_home_dir ());
+
+ model->priv->mime_filter_values = NULL;
+ model->priv->group_filter_values = NULL;
+ model->priv->scheme_filter_values = NULL;
+
+ model->priv->client = gconf_client_get_default ();
+ gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR,
+ GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+
+ model->priv->limit_change_notify_id =
+ gconf_client_notify_add (model->priv->client,
+ EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY,
+ egg_recent_model_limit_changed,
+ model, NULL, NULL);
+
+ model->priv->expiration_change_notify_id =
+ gconf_client_notify_add (model->priv->client,
+ EGG_RECENT_MODEL_EXPIRE_KEY,
+ egg_recent_model_expiration_changed,
+ model, NULL, NULL);
+
+ model->priv->expire_days = gconf_client_get_int (
+ model->priv->client,
+ EGG_RECENT_MODEL_EXPIRE_KEY,
+ NULL);
+
+#if 0
+ /* keep this out, for now */
+ model->priv->limit = gconf_client_get_int (
+ model->priv->client,
+ EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL);
+ model->priv->use_default_limit = TRUE;
+#endif
+ model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT;
+ model->priv->use_default_limit = FALSE;
+
+ model->priv->monitors = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) gnome_vfs_monitor_cancel);
+
+ model->priv->monitor = NULL;
+ egg_recent_model_monitor (model, TRUE);
+}
+
+
+/**
+ * egg_recent_model_new:
+ * @sort: the type of sorting to use
+ * @limit: maximum number of items in the list
+ *
+ * This creates a new EggRecentModel object.
+ *
+ * Returns: a EggRecentModel object
+ */
+EggRecentModel *
+egg_recent_model_new (EggRecentModelSort sort)
+{
+ EggRecentModel *model;
+
+ model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (),
+ "sort-type", sort, NULL));
+
+ g_return_val_if_fail (model, NULL);
+
+ return model;
+}
+
+/**
+ * egg_recent_model_add_full:
+ * @model: A EggRecentModel object.
+ * @item: A EggRecentItem
+ *
+ * This function adds an item to the list of recently used URIs.
+ *
+ * Returns: gboolean
+ */
+gboolean
+egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item)
+{
+ FILE *file;
+ GList *list = NULL;
+ gboolean ret = FALSE;
+ gboolean updated = FALSE;
+ char *uri;
+ time_t t;
+
+ g_return_val_if_fail (model != NULL, FALSE);
+ g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
+
+ uri = egg_recent_item_get_uri (item);
+ if (strncmp (uri, "recent-files://", strlen ("recent-files://")) == 0) {
+ g_free (uri);
+ return FALSE;
+ } else {
+ g_free (uri);
+ }
+
+ file = egg_recent_model_open_file (model);
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ time (&t);
+ egg_recent_item_set_timestamp (item, t);
+
+ if (egg_recent_model_lock_file (file)) {
+
+ /* read existing stuff */
+ list = egg_recent_model_read (model, file);
+
+ /* if it's already there, we just update it */
+ updated = egg_recent_model_update_item (list, item);
+
+ if (!updated) {
+ list = g_list_prepend (list, item);
+
+ egg_recent_model_enforce_limit (list,
+ EGG_RECENT_MODEL_MAX_ITEMS);
+ }
+
+ /* write new stuff */
+ if (!egg_recent_model_write (model, file, list))
+ g_warning ("Write failed: %s", strerror (errno));
+
+ if (!updated)
+ list = g_list_remove (list, item);
+
+ EGG_RECENT_ITEM_LIST_UNREF (list);
+ ret = TRUE;
+ } else {
+ g_warning ("Failed to lock: %s", strerror (errno));
+ return FALSE;
+ }
+
+ if (!egg_recent_model_unlock_file (file))
+ g_warning ("Failed to unlock: %s", strerror (errno));
+
+ fclose (file);
+
+ if (model->priv->monitor == NULL) {
+ /* since monitoring isn't working, at least give a
+ * local notification
+ */
+ egg_recent_model_changed (model);
+ }
+
+ return ret;
+}
+
+/**
+ * egg_recent_model_add:
+ * @model: A EggRecentModel object.
+ * @uri: A string URI
+ *
+ * This function adds an item to the list of recently used URIs.
+ *
+ * Returns: gboolean
+ */
+gboolean
+egg_recent_model_add (EggRecentModel *model, const gchar *uri)
+{
+ EggRecentItem *item;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (model != NULL, FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ item = egg_recent_item_new_from_uri (uri);
+
+ g_return_val_if_fail (item != NULL, FALSE);
+
+ ret = egg_recent_model_add_full (model, item);
+
+ egg_recent_item_unref (item);
+
+ return ret;
+}
+
+
+
+/**
+ * egg_recent_model_delete:
+ * @model: A EggRecentModel object.
+ * @uri: The URI you want to delete.
+ *
+ * This function deletes a URI from the file of recently used URIs.
+ *
+ * Returns: gboolean
+ */
+gboolean
+egg_recent_model_delete (EggRecentModel * model, const gchar * uri)
+{
+ FILE *file;
+ GList *list;
+ unsigned int length;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (model != NULL, FALSE);
+ g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE);
+ g_return_val_if_fail (uri != NULL, FALSE);
+
+ file = egg_recent_model_open_file (model);
+ g_return_val_if_fail (file != NULL, FALSE);
+
+ if (egg_recent_model_lock_file (file)) {
+ list = egg_recent_model_read (model, file);
+
+ if (list == NULL)
+ goto out;
+
+ length = g_list_length (list);
+
+ list = egg_recent_model_delete_from_list (list, uri);
+
+ if (length == g_list_length (list)) {
+ /* nothing was deleted */
+ EGG_RECENT_ITEM_LIST_UNREF (list);
+ } else {
+ egg_recent_model_write (model, file, list);
+ EGG_RECENT_ITEM_LIST_UNREF (list);
+ ret = TRUE;
+
+ }
+ } else {
+ g_warning ("Failed to lock: %s", strerror (errno));
+ return FALSE;
+ }
+
+out:
+
+ if (!egg_recent_model_unlock_file (file))
+ g_warning ("Failed to unlock: %s", strerror (errno));
+
+ fclose (file);
+
+ g_hash_table_remove (model->priv->monitors, uri);
+
+ if (model->priv->monitor == NULL && ret) {
+ /* since monitoring isn't working, at least give a
+ * local notification
+ */
+ egg_recent_model_changed (model);
+ }
+
+ return ret;
+}
+
+
+/**
+ * egg_recent_model_get_list:
+ * @model: A EggRecentModel object.
+ *
+ * This function gets the current contents of the file
+ *
+ * Returns: a GList
+ */
+GList *
+egg_recent_model_get_list (EggRecentModel *model)
+{
+ FILE *file;
+ GList *list=NULL;
+
+ file = egg_recent_model_open_file (model);
+ g_return_val_if_fail (file != NULL, NULL);
+
+ if (egg_recent_model_lock_file (file)) {
+ list = egg_recent_model_read (model, file);
+
+ } else {
+ g_warning ("Failed to lock: %s", strerror (errno));
+ fclose (file);
+ return NULL;
+ }
+
+ if (!egg_recent_model_unlock_file (file))
+ g_warning ("Failed to unlock: %s", strerror (errno));
+
+ if (list != NULL) {
+ list = egg_recent_model_filter (model, list);
+ list = egg_recent_model_sort (model, list);
+
+ egg_recent_model_enforce_limit (list, model->priv->limit);
+ }
+
+ fclose (file);
+
+ return list;
+}
+
+
+
+/**
+ * egg_recent_model_set_limit:
+ * @model: A EggRecentModel object.
+ * @limit: The maximum length of the list
+ *
+ * This function sets the maximum length of the list. Note: This only affects
+ * the length of the list emitted in the "changed" signal, not the list stored
+ * on disk.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_set_limit (EggRecentModel *model, int limit)
+{
+ model->priv->use_default_limit = FALSE;
+
+ egg_recent_model_set_limit_internal (model, limit);
+}
+
+/**
+ * egg_recent_model_get_limit:
+ * @model: A EggRecentModel object.
+ *
+ * This function gets the maximum length of the list.
+ *
+ * Returns: int
+ */
+int
+egg_recent_model_get_limit (EggRecentModel *model)
+{
+ return model->priv->limit;
+}
+
+
+/**
+ * egg_recent_model_clear:
+ * @model: A EggRecentModel object.
+ *
+ * This function clears the contents of the file
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_clear (EggRecentModel *model)
+{
+ FILE *file;
+ int fd;
+
+ file = egg_recent_model_open_file (model);
+ g_return_if_fail (file != NULL);
+
+ fd = fileno (file);
+
+ if (egg_recent_model_lock_file (file)) {
+ ftruncate (fd, 0);
+ } else {
+ g_warning ("Failed to lock: %s", strerror (errno));
+ return;
+ }
+
+ if (!egg_recent_model_unlock_file (file))
+ g_warning ("Failed to unlock: %s", strerror (errno));
+
+ fclose (file);
+}
+
+
+/**
+ * egg_recent_model_set_filter_mime_types:
+ * @model: A EggRecentModel object.
+ *
+ * Sets which mime types are allowed in the list.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_set_filter_mime_types (EggRecentModel *model,
+ ...)
+{
+ va_list valist;
+ GSList *list = NULL;
+ gchar *str;
+
+ g_return_if_fail (model != NULL);
+
+ if (model->priv->mime_filter_values != NULL) {
+ g_slist_foreach (model->priv->mime_filter_values,
+ (GFunc) g_pattern_spec_free, NULL);
+ g_slist_free (model->priv->mime_filter_values);
+ model->priv->mime_filter_values = NULL;
+ }
+
+ va_start (valist, model);
+
+ str = va_arg (valist, gchar*);
+
+ while (str != NULL) {
+ list = g_slist_prepend (list, g_pattern_spec_new (str));
+
+ str = va_arg (valist, gchar*);
+ }
+
+ va_end (valist);
+
+ model->priv->mime_filter_values = list;
+}
+
+/**
+ * egg_recent_model_set_filter_groups:
+ * @model: A EggRecentModel object.
+ *
+ * Sets which groups are allowed in the list.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_set_filter_groups (EggRecentModel *model,
+ ...)
+{
+ va_list valist;
+ GSList *list = NULL;
+ gchar *str;
+
+ g_return_if_fail (model != NULL);
+
+ if (model->priv->group_filter_values != NULL) {
+ g_slist_foreach (model->priv->group_filter_values, (GFunc)g_free, NULL);
+ g_slist_free (model->priv->group_filter_values);
+ model->priv->group_filter_values = NULL;
+ }
+
+ va_start (valist, model);
+
+ str = va_arg (valist, gchar*);
+
+ while (str != NULL) {
+ list = g_slist_prepend (list, g_strdup (str));
+
+ str = va_arg (valist, gchar*);
+ }
+
+ va_end (valist);
+
+ model->priv->group_filter_values = list;
+}
+
+/**
+ * egg_recent_model_set_filter_uri_schemes:
+ * @model: A EggRecentModel object.
+ *
+ * Sets which URI schemes (file, http, ftp, etc) are allowed in the list.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_set_filter_uri_schemes (EggRecentModel *model, ...)
+{
+ va_list valist;
+ GSList *list = NULL;
+ gchar *str;
+
+ g_return_if_fail (model != NULL);
+
+ if (model->priv->scheme_filter_values != NULL) {
+ g_slist_foreach (model->priv->scheme_filter_values,
+ (GFunc) g_pattern_spec_free, NULL);
+ g_slist_free (model->priv->scheme_filter_values);
+ model->priv->scheme_filter_values = NULL;
+ }
+
+ va_start (valist, model);
+
+ str = va_arg (valist, gchar*);
+
+ while (str != NULL) {
+ list = g_slist_prepend (list, g_pattern_spec_new (str));
+
+ str = va_arg (valist, gchar*);
+ }
+
+ va_end (valist);
+
+ model->priv->scheme_filter_values = list;
+}
+
+/**
+ * egg_recent_model_set_sort:
+ * @model: A EggRecentModel object.
+ * @sort: A EggRecentModelSort type
+ *
+ * Sets the type of sorting to be used.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_set_sort (EggRecentModel *model,
+ EggRecentModelSort sort)
+{
+ g_return_if_fail (model != NULL);
+
+ model->priv->sort_type = sort;
+}
+
+/**
+ * egg_recent_model_changed:
+ * @model: A EggRecentModel object.
+ *
+ * This function causes a "changed" signal to be emitted.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_changed (EggRecentModel *model)
+{
+ GList *list = NULL;
+
+ if (model->priv->limit > 0) {
+ list = egg_recent_model_get_list (model);
+ /* egg_recent_model_monitor_list (model, list); */
+
+ g_signal_emit (G_OBJECT (model), model_signals[CHANGED], 0,
+ list);
+ }
+
+ if (list)
+ EGG_RECENT_ITEM_LIST_UNREF (list);
+}
+
+static void
+egg_recent_model_remove_expired_list (EggRecentModel *model, GList *list)
+{
+ time_t current_time;
+ time_t day_seconds;
+
+ time (¤t_time);
+ day_seconds = model->priv->expire_days*24*60*60;
+
+ while (list != NULL) {
+ EggRecentItem *item = list->data;
+ time_t timestamp;
+
+ timestamp = egg_recent_item_get_timestamp (item);
+
+ if ((timestamp+day_seconds) < current_time) {
+ gchar *uri = egg_recent_item_get_uri (item);
+ egg_recent_model_delete (model, uri);
+
+ g_strdup (uri);
+ }
+
+ list = list->next;
+ }
+}
+
+
+/**
+ * egg_recent_model_remove_expired:
+ * @model: A EggRecentModel object.
+ *
+ * Goes through the entire list, and removes any items that are older than
+ * the user-specified expiration period.
+ *
+ * Returns: void
+ */
+void
+egg_recent_model_remove_expired (EggRecentModel *model)
+{
+ FILE *file;
+ GList *list=NULL;
+
+ g_return_if_fail (model != NULL);
+
+ file = egg_recent_model_open_file (model);
+ g_return_if_fail (file != NULL);
+
+ if (egg_recent_model_lock_file (file)) {
+ list = egg_recent_model_read (model, file);
+
+ } else {
+ g_warning ("Failed to lock: %s", strerror (errno));
+ return;
+ }
+
+ if (!egg_recent_model_unlock_file (file))
+ g_warning ("Failed to unlock: %s", strerror (errno));
+
+ if (list != NULL) {
+ egg_recent_model_remove_expired_list (model, list);
+ EGG_RECENT_ITEM_LIST_UNREF (list);
+ }
+
+ fclose (file);
+}
+
+/**
+ * egg_recent_model_get_type:
+ *
+ * This returns a GType representing a EggRecentModel object.
+ *
+ * Returns: a GType
+ */
+GType
+egg_recent_model_get_type (void)
+{
+ static GType egg_recent_model_type = 0;
+
+ if(!egg_recent_model_type) {
+ static const GTypeInfo egg_recent_model_info = {
+ sizeof (EggRecentModelClass),
+ NULL, /* base init */
+ NULL, /* base finalize */
+ (GClassInitFunc)egg_recent_model_class_init, /* class init */
+ NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof (EggRecentModel),
+ 0,
+ (GInstanceInitFunc) egg_recent_model_init
+ };
+
+ egg_recent_model_type = g_type_register_static (G_TYPE_OBJECT,
+ "EggRecentModel",
+ &egg_recent_model_info, 0);
+ }
+
+ return egg_recent_model_type;
+}
+
--- /dev/null
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+#ifndef __EGG_RECENT_MODEL_H__
+#define __EGG_RECENT_MODEL_H__
+
+#include "egg-recent-item.h"
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_RECENT_MODEL (egg_recent_model_get_type ())
+#define EGG_RECENT_MODEL(obj) G_TYPE_CHECK_INSTANCE_CAST (obj, EGG_TYPE_RECENT_MODEL, EggRecentModel)
+#define EGG_RECENT_MODEL_CLASS(klass) G_TYPE_CHECK_CLASS_CAST (klass, EGG_TYPE_RECENT_MODEL, EggRecentModelClass)
+#define EGG_IS_RECENT_MODEL(obj) G_TYPE_CHECK_INSTANCE_TYPE (obj, egg_recent_model_get_type ())
+
+typedef struct _EggRecentModel EggRecentModel;
+typedef struct _EggRecentModelPrivate EggRecentModelPrivate;
+typedef struct _EggRecentModelClass EggRecentModelClass;
+
+struct _EggRecentModel {
+ GObject parent_instance;
+
+ EggRecentModelPrivate *priv;
+};
+
+struct _EggRecentModelClass {
+ GObjectClass parent_class;
+
+ void (*changed) (EggRecentModel *model, GList *list);
+};
+
+typedef enum {
+ EGG_RECENT_MODEL_SORT_MRU,
+ EGG_RECENT_MODEL_SORT_LRU,
+ EGG_RECENT_MODEL_SORT_NONE
+} EggRecentModelSort;
+
+
+/* Standard group names */
+#define EGG_RECENT_GROUP_LAUNCHERS "Launchers"
+
+
+GType egg_recent_model_get_type (void);
+
+/* constructors */
+EggRecentModel * egg_recent_model_new (EggRecentModelSort sort);
+
+/* public methods */
+void egg_recent_model_set_filter_mime_types (EggRecentModel *model,
+ ...);
+
+void egg_recent_model_set_filter_groups (EggRecentModel *model, ...);
+
+void egg_recent_model_set_filter_uri_schemes (EggRecentModel *model,
+ ...);
+
+void egg_recent_model_set_sort (EggRecentModel *model,
+ EggRecentModelSort sort);
+
+gboolean egg_recent_model_add_full (EggRecentModel *model,
+ EggRecentItem *item);
+
+gboolean egg_recent_model_add (EggRecentModel *model,
+ const gchar *uri);
+
+gboolean egg_recent_model_delete (EggRecentModel *model,
+ const gchar *uri);
+
+void egg_recent_model_clear (EggRecentModel *model);
+
+GList * egg_recent_model_get_list (EggRecentModel *model);
+
+void egg_recent_model_changed (EggRecentModel *model);
+
+void egg_recent_model_set_limit (EggRecentModel *model, int limit);
+int egg_recent_model_get_limit (EggRecentModel *model);
+
+void egg_recent_model_remove_expired (EggRecentModel *model);
+
+G_END_DECLS
+
+#endif /* __EGG_RECENT_MODEL_H__ */
--- /dev/null
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifndef USE_STABLE_LIBGNOMEUI
+#include <libgnomeui/gnome-icon-theme.h>
+#include <libgnomeui/gnome-icon-lookup.h>
+#endif
+#include <math.h>
+#include "egg-recent-util.h"
+
+#define EGG_RECENT_UTIL_HOSTNAME_SIZE 512
+
+/* ripped out of gedit2 */
+gchar*
+egg_recent_util_escape_underlines (const gchar* text)
+{
+ GString *str;
+ gint length;
+ const gchar *p;
+ const gchar *end;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ length = strlen (text);
+
+ str = g_string_new ("");
+
+ p = text;
+ end = text + length;
+
+ while (p != end)
+ {
+ const gchar *next;
+ next = g_utf8_next_char (p);
+
+ switch (*p)
+ {
+ case '_':
+ g_string_append (str, "__");
+ break;
+ default:
+ g_string_append_len (str, p, next - p);
+ break;
+ }
+
+ p = next;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static GdkPixbuf *
+scale_icon (GdkPixbuf *pixbuf,
+ double *scale)
+{
+ guint width, height;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ width = floor (width * *scale + 0.5);
+ height = floor (height * *scale + 0.5);
+
+ return gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+}
+
+static GdkPixbuf *
+load_icon_file (char *filename,
+ guint base_size,
+ guint nominal_size)
+{
+ GdkPixbuf *pixbuf, *scaled_pixbuf;
+ guint width, height, size;
+ double scale;
+
+ pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+ if (pixbuf == NULL) {
+ return NULL;
+ }
+
+ if (base_size == 0) {
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ size = MAX (width, height);
+ if (size > nominal_size) {
+ base_size = size;
+ } else {
+ /* Don't scale up small icons */
+ base_size = nominal_size;
+ }
+ }
+
+ if (base_size != nominal_size) {
+ scale = (double)nominal_size/base_size;
+ scaled_pixbuf = scale_icon (pixbuf, &scale);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+ }
+
+ return pixbuf;
+}
+
+#ifndef USE_STABLE_LIBGNOMEUI
+GdkPixbuf *
+egg_recent_util_get_icon (GnomeIconTheme *theme, const gchar *uri,
+ const gchar *mime_type, int size)
+{
+ gchar *icon;
+ gchar *filename;
+ const GnomeIconData *icon_data;
+ int base_size;
+ GdkPixbuf *pixbuf;
+
+ icon = gnome_icon_lookup (theme, NULL, uri, NULL, NULL,
+ mime_type, 0, NULL);
+
+
+ g_return_val_if_fail (icon != NULL, NULL);
+
+ filename = gnome_icon_theme_lookup_icon (theme, icon,
+ size,
+ &icon_data,
+ &base_size);
+ g_free (icon);
+
+ pixbuf = load_icon_file (filename, base_size, size);
+ g_free (filename);
+
+
+ return pixbuf;
+}
+#endif /* !USE_STABLE_LIBGNOMEUI */
+
+gchar *
+egg_recent_util_get_unique_id (void)
+{
+ char hostname[EGG_RECENT_UTIL_HOSTNAME_SIZE];
+ time_t the_time;
+ guint32 rand;
+ int pid;
+
+ gethostname (hostname, EGG_RECENT_UTIL_HOSTNAME_SIZE);
+
+ time (&the_time);
+ rand = g_random_int ();
+ pid = getpid ();
+
+ return g_strdup_printf ("%s-%d-%d-%d", hostname, (int)time, (int)rand, (int)pid);
+}
--- /dev/null
+
+#ifndef __EGG_RECENT_UTIL__
+#define __EGG_RECENT_UTIL__
+
+#include <gtk/gtk.h>
+#ifndef USE_STABLE_LIBGNOMEUI
+#include <libgnomeui/gnome-icon-theme.h>
+#endif
+
+G_BEGIN_DECLS
+
+gchar * egg_recent_util_escape_underlines (const gchar *uri);
+gchar * egg_recent_util_get_unique_id (void);
+#ifndef USE_STABLE_LIBGNOMEUI
+GdkPixbuf * egg_recent_util_get_icon (GnomeIconTheme *theme,
+ const gchar *uri,
+ const gchar *mime_type,
+ int size);
+#endif
+
+G_END_DECLS
+
+#endif /* __EGG_RECENT_UTIL__ */
--- /dev/null
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* gnome-vfs-utils.c - Utility gnome-vfs methods. Will use gnome-vfs
+ HEAD in time.
+
+ Copyright (C) 1999 Free Software Foundation
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <ettore@comm2000.it>
+ John Sullivan <sullivan@eazel.com>
+ Darin Adler <darin@eazel.com>
+*/
+
+#include <config.h>
+
+#include "egg-recent-vfs-utils.h"
+
+#include <libgnomevfs/gnome-vfs-utils.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef ENABLE_NLS
+#include <glib.h>
+
+#include <libintl.h>
+#define _(String) gettext(String)
+
+#ifdef gettext_noop
+#define N_(String) gettext_noop(String)
+#else
+#define N_(String) (String)
+#endif
+#else /* NLS is disabled */
+#define _(String) (String)
+#define N_(String) (String)
+#define textdomain(String) (String)
+#define gettext(String) (String)
+#define dgettext(Domain,String) (String)
+#define dcgettext(Domain,String,Type) (String)
+#define bindtextdomain(Domain,Directory) (Domain)
+#endif
+
+static char *
+make_valid_utf8 (const char *name)
+{
+ GString *string;
+ const char *remainder, *invalid;
+ int remaining_bytes, valid_bytes;
+
+ string = NULL;
+ remainder = name;
+ remaining_bytes = strlen (name);
+
+ while (remaining_bytes != 0) {
+ if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
+ break;
+ }
+ valid_bytes = invalid - remainder;
+
+ if (string == NULL) {
+ string = g_string_sized_new (remaining_bytes);
+ }
+ g_string_append_len (string, remainder, valid_bytes);
+ g_string_append_c (string, '?');
+
+ remaining_bytes -= valid_bytes + 1;
+ remainder = invalid + 1;
+ }
+
+ if (string == NULL) {
+ return g_strdup (name);
+ }
+
+ g_string_append (string, remainder);
+ g_string_append (string, _(" (invalid Unicode)"));
+ g_assert (g_utf8_validate (string->str, -1, NULL));
+
+ return g_string_free (string, FALSE);
+}
+
+static gboolean
+istr_has_prefix (const char *haystack, const char *needle)
+{
+ const char *h, *n;
+ char hc, nc;
+
+ /* Eat one character at a time. */
+ h = haystack == NULL ? "" : haystack;
+ n = needle == NULL ? "" : needle;
+ do {
+ if (*n == '\0') {
+ return TRUE;
+ }
+ if (*h == '\0') {
+ return FALSE;
+ }
+ hc = *h++;
+ nc = *n++;
+ hc = g_ascii_tolower (hc);
+ nc = g_ascii_tolower (nc);
+ } while (hc == nc);
+ return FALSE;
+}
+
+static gboolean
+str_has_prefix (const char *haystack, const char *needle)
+{
+ const char *h, *n;
+
+ /* Eat one character at a time. */
+ h = haystack == NULL ? "" : haystack;
+ n = needle == NULL ? "" : needle;
+ do {
+ if (*n == '\0') {
+ return TRUE;
+ }
+ if (*h == '\0') {
+ return FALSE;
+ }
+ } while (*h++ == *n++);
+ return FALSE;
+}
+
+static gboolean
+uri_is_local_scheme (const char *uri)
+{
+ gboolean is_local_scheme;
+ char *temp_scheme;
+ int i;
+ char *local_schemes[] = {"file:", "help:", "ghelp:", "gnome-help:",
+ "trash:", "man:", "info:",
+ "hardware:", "search:", "pipe:",
+ "gnome-trash:", NULL};
+
+ is_local_scheme = FALSE;
+ for (temp_scheme = *local_schemes, i = 0; temp_scheme != NULL; i++, temp_scheme = local_schemes[i]) {
+ is_local_scheme = istr_has_prefix (uri, temp_scheme);
+ if (is_local_scheme) {
+ break;
+ }
+ }
+
+ return is_local_scheme;
+}
+
+static char *
+handle_trailing_slashes (const char *uri)
+{
+ char *temp, *uri_copy;
+ gboolean previous_char_is_column, previous_chars_are_slashes_without_column;
+ gboolean previous_chars_are_slashes_with_column;
+ gboolean is_local_scheme;
+
+ g_assert (uri != NULL);
+
+ uri_copy = g_strdup (uri);
+ if (strlen (uri_copy) <= 2) {
+ return uri_copy;
+ }
+
+ is_local_scheme = uri_is_local_scheme (uri);
+
+ previous_char_is_column = FALSE;
+ previous_chars_are_slashes_without_column = FALSE;
+ previous_chars_are_slashes_with_column = FALSE;
+
+ /* remove multiple trailing slashes */
+ for (temp = uri_copy; *temp != '\0'; temp++) {
+ if (*temp == '/' && !previous_char_is_column) {
+ previous_chars_are_slashes_without_column = TRUE;
+ } else if (*temp == '/' && previous_char_is_column) {
+ previous_chars_are_slashes_without_column = FALSE;
+ previous_char_is_column = TRUE;
+ previous_chars_are_slashes_with_column = TRUE;
+ } else {
+ previous_chars_are_slashes_without_column = FALSE;
+ previous_char_is_column = FALSE;
+ previous_chars_are_slashes_with_column = FALSE;
+ }
+
+ if (*temp == ':') {
+ previous_char_is_column = TRUE;
+ }
+ }
+
+ if (*temp == '\0' && previous_chars_are_slashes_without_column) {
+ if (is_local_scheme) {
+ /* go back till you remove them all. */
+ for (temp--; *(temp) == '/'; temp--) {
+ *temp = '\0';
+ }
+ } else {
+ /* go back till you remove them all but one. */
+ for (temp--; *(temp - 1) == '/'; temp--) {
+ *temp = '\0';
+ }
+ }
+ }
+
+ if (*temp == '\0' && previous_chars_are_slashes_with_column) {
+ /* go back till you remove them all but three. */
+ for (temp--; *(temp - 3) != ':' && *(temp - 2) != ':' && *(temp - 1) != ':'; temp--) {
+ *temp = '\0';
+ }
+ }
+
+
+ return uri_copy;
+}
+
+static char *
+make_uri_canonical (const char *uri)
+{
+ char *canonical_uri, *old_uri, *p;
+ gboolean relative_uri;
+
+ relative_uri = FALSE;
+
+ if (uri == NULL) {
+ return NULL;
+ }
+
+ /* FIXME bugzilla.eazel.com 648:
+ * This currently ignores the issue of two uris that are not identical but point
+ * to the same data except for the specific cases of trailing '/' characters,
+ * file:/ and file:///, and "lack of file:".
+ */
+
+ canonical_uri = handle_trailing_slashes (uri);
+
+ /* Note: In some cases, a trailing slash means nothing, and can
+ * be considered equivalent to no trailing slash. But this is
+ * not true in every case; specifically not for web addresses passed
+ * to a web-browser. So we don't have the trailing-slash-equivalence
+ * logic here, but we do use that logic in EelDirectory where
+ * the rules are more strict.
+ */
+
+ /* Add file: if there is no scheme. */
+ if (strchr (canonical_uri, ':') == NULL) {
+ old_uri = canonical_uri;
+
+ if (old_uri[0] != '/') {
+ /* FIXME bugzilla.eazel.com 5069:
+ * bandaid alert. Is this really the right thing to do?
+ *
+ * We got what really is a relative path. We do a little bit of
+ * a stretch here and assume it was meant to be a cryptic absolute path,
+ * and convert it to one. Since we can't call gnome_vfs_uri_new and
+ * gnome_vfs_uri_to_string to do the right make-canonical conversion,
+ * we have to do it ourselves.
+ */
+ relative_uri = TRUE;
+ canonical_uri = gnome_vfs_make_path_name_canonical (old_uri);
+ g_free (old_uri);
+ old_uri = canonical_uri;
+ canonical_uri = g_strconcat ("file:///", old_uri, NULL);
+ } else {
+ canonical_uri = g_strconcat ("file:", old_uri, NULL);
+ }
+ g_free (old_uri);
+ }
+
+ /* Lower-case the scheme. */
+ for (p = canonical_uri; *p != ':'; p++) {
+ g_assert (*p != '\0');
+ *p = g_ascii_tolower (*p);
+ }
+
+ if (!relative_uri) {
+ old_uri = canonical_uri;
+ canonical_uri = gnome_vfs_make_uri_canonical (canonical_uri);
+ if (canonical_uri != NULL) {
+ g_free (old_uri);
+ } else {
+ canonical_uri = old_uri;
+ }
+ }
+
+ /* FIXME bugzilla.eazel.com 2802:
+ * Work around gnome-vfs's desire to convert file:foo into file://foo
+ * by converting to file:///foo here. When you remove this, check that
+ * typing "foo" into location bar does not crash and returns an error
+ * rather than displaying the contents of /
+ */
+ if (str_has_prefix (canonical_uri, "file://")
+ && !str_has_prefix (canonical_uri, "file:///")) {
+ old_uri = canonical_uri;
+ canonical_uri = g_strconcat ("file:/", old_uri + 5, NULL);
+ g_free (old_uri);
+ }
+
+ return canonical_uri;
+}
+
+static char *
+format_uri_for_display (const char *uri, gboolean filenames_are_locale_encoded)
+{
+ char *canonical_uri, *path, *utf8_path;
+
+ g_return_val_if_fail (uri != NULL, g_strdup (""));
+
+ canonical_uri = make_uri_canonical (uri);
+
+ /* If there's no fragment and it's a local path. */
+ path = gnome_vfs_get_local_path_from_uri (canonical_uri);
+
+ if (path != NULL) {
+ if (filenames_are_locale_encoded) {
+ utf8_path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
+ if (utf8_path) {
+ g_free (canonical_uri);
+ g_free (path);
+ return utf8_path;
+ }
+ } else if (g_utf8_validate (path, -1, NULL)) {
+ g_free (canonical_uri);
+ return path;
+ }
+ }
+
+ if (canonical_uri && !g_utf8_validate (canonical_uri, -1, NULL)) {
+ utf8_path = make_valid_utf8 (canonical_uri);
+ g_free (canonical_uri);
+ canonical_uri = utf8_path;
+ }
+
+ g_free (path);
+ return canonical_uri;
+}
+
+char *
+egg_recent_vfs_format_uri_for_display (const char *uri)
+{
+ static gboolean broken_filenames;
+
+ broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL;
+
+ return format_uri_for_display (uri, broken_filenames);
+}
+
+static gboolean
+is_valid_scheme_character (char c)
+{
+ return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
+}
+
+static gboolean
+has_valid_scheme (const char *uri)
+{
+ const char *p;
+
+ p = uri;
+
+ if (!is_valid_scheme_character (*p)) {
+ return FALSE;
+ }
+
+ do {
+ p++;
+ } while (is_valid_scheme_character (*p));
+
+ return *p == ':';
+}
+
+static char *
+escape_high_chars (const guchar *string)
+{
+ char *result;
+ const guchar *scanner;
+ guchar *result_scanner;
+ int escape_count;
+ static const gchar hex[16] = "0123456789ABCDEF";
+
+#define ACCEPTABLE(a) ((a)>=32 && (a)<128)
+
+ escape_count = 0;
+
+ if (string == NULL) {
+ return NULL;
+ }
+
+ for (scanner = string; *scanner != '\0'; scanner++) {
+ if (!ACCEPTABLE(*scanner)) {
+ escape_count++;
+ }
+ }
+
+ if (escape_count == 0) {
+ return g_strdup (string);
+ }
+
+ /* allocate two extra characters for every character that
+ * needs escaping and space for a trailing zero
+ */
+ result = g_malloc (scanner - string + escape_count * 2 + 1);
+ for (scanner = string, result_scanner = result; *scanner != '\0'; scanner++) {
+ if (!ACCEPTABLE(*scanner)) {
+ *result_scanner++ = '%';
+ *result_scanner++ = hex[*scanner >> 4];
+ *result_scanner++ = hex[*scanner & 15];
+
+ } else {
+ *result_scanner++ = *scanner;
+ }
+ }
+
+ *result_scanner = '\0';
+
+ return result;
+}
+
+static char *
+make_uri_from_input_internal (const char *text,
+ gboolean filenames_are_locale_encoded,
+ gboolean strip_trailing_whitespace)
+{
+ char *stripped, *path, *uri, *locale_path, *filesystem_path, *escaped;
+
+ g_return_val_if_fail (text != NULL, g_strdup (""));
+
+ /* Strip off leading whitespaces (since they can't be part of a valid
+ uri). Only strip off trailing whitespaces when requested since
+ they might be part of a valid uri.
+ */
+ if (strip_trailing_whitespace) {
+ stripped = g_strstrip (g_strdup (text));
+ } else {
+ stripped = g_strchug (g_strdup (text));
+ }
+
+ switch (stripped[0]) {
+ case '\0':
+ uri = g_strdup ("");
+ break;
+ case '/':
+ if (filenames_are_locale_encoded) {
+ GError *error = NULL;
+ locale_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, &error);
+ if (locale_path != NULL) {
+ uri = gnome_vfs_get_uri_from_local_path (locale_path);
+ g_free (locale_path);
+ } else {
+ /* We couldn't convert to the locale. */
+ /* FIXME: We should probably give a user-visible error here. */
+ uri = g_strdup("");
+ }
+ } else {
+ uri = gnome_vfs_get_uri_from_local_path (stripped);
+ }
+ break;
+ case '~':
+ if (filenames_are_locale_encoded) {
+ filesystem_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, NULL);
+ } else {
+ filesystem_path = g_strdup (stripped);
+ }
+ /* deliberately falling into default case on fail */
+ if (filesystem_path != NULL) {
+ path = gnome_vfs_expand_initial_tilde (filesystem_path);
+ g_free (filesystem_path);
+ if (*path == '/') {
+ uri = gnome_vfs_get_uri_from_local_path (path);
+ g_free (path);
+ break;
+ }
+ g_free (path);
+ }
+ /* don't insert break here, read above comment */
+ default:
+ if (has_valid_scheme (stripped)) {
+ uri = escape_high_chars (stripped);
+ } else {
+ escaped = escape_high_chars (stripped);
+ uri = g_strconcat ("http://", escaped, NULL);
+ g_free (escaped);
+ }
+ }
+
+ g_free (stripped);
+
+ return uri;
+
+}
+
+char *
+egg_recent_vfs_make_uri_from_input (const char *uri)
+{
+ static gboolean broken_filenames;
+
+ broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL;
+
+ return make_uri_from_input_internal (uri, broken_filenames, TRUE);
+}
+
+static char *
+make_uri_canonical_strip_fragment (const char *uri)
+{
+ const char *fragment;
+ char *without_fragment, *canonical;
+
+ fragment = strchr (uri, '#');
+ if (fragment == NULL) {
+ return make_uri_canonical (uri);
+ }
+
+ without_fragment = g_strndup (uri, fragment - uri);
+ canonical = make_uri_canonical (without_fragment);
+ g_free (without_fragment);
+ return canonical;
+}
+
+static gboolean
+uris_match (const char *uri_1, const char *uri_2, gboolean ignore_fragments)
+{
+ char *canonical_1, *canonical_2;
+ gboolean result;
+
+ if (ignore_fragments) {
+ canonical_1 = make_uri_canonical_strip_fragment (uri_1);
+ canonical_2 = make_uri_canonical_strip_fragment (uri_2);
+ } else {
+ canonical_1 = make_uri_canonical (uri_1);
+ canonical_2 = make_uri_canonical (uri_2);
+ }
+
+ result = strcmp (canonical_1, canonical_2) == 0;
+
+ g_free (canonical_1);
+ g_free (canonical_2);
+
+ return result;
+}
+
+gboolean
+egg_recent_vfs_uris_match (const char *uri_1, const char *uri_2)
+{
+ return uris_match (uri_1, uri_2, FALSE);
+}
+
+char *
+egg_recent_vfs_get_uri_scheme (const char *uri)
+{
+ char *colon;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ colon = strchr (uri, ':');
+
+ if (colon == NULL) {
+ return NULL;
+ }
+
+ return g_strndup (uri, colon - uri);
+}
--- /dev/null
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gnome-vfs-utils.h - Utility gnome-vfs methods. Will use gnome-vfs
+ HEAD in time.
+
+ Copyright (C) 1999 Free Software Foundation
+ Copyright (C) 2000 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ettore Perazzoli <ettore@comm2000.it>
+ John Sullivan <sullivan@eazel.com>
+*/
+
+#ifndef EGG_RECENT_VFS_UTILS_H
+#define EGG_RECENT_VFS_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+char *egg_recent_vfs_format_uri_for_display (const char *uri);
+char *egg_recent_vfs_make_uri_from_input (const char *uri);
+gboolean egg_recent_vfs_uris_match (const char *uri_1,
+ const char *uri_2);
+char *egg_recent_vfs_get_uri_scheme (const char *uri);
+
+G_END_DECLS
+
+#endif /* GNOME_VFS_UTILS_H */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * James Willcox <jwillcox@cs.indiana.edu>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <libbonoboui.h>
+#include <libgnomevfs/gnome-vfs.h>
+#ifndef USE_STABLE_LIBGNOMEUI
+#include <libgnomeui/gnome-icon-theme.h>
+#endif
+#include <gconf/gconf-client.h>
+#include "egg-recent-model.h"
+#include "egg-recent-view.h"
+#include "egg-recent-view-bonobo.h"
+#include "egg-recent-util.h"
+#include "egg-recent-item.h"
+
+struct _EggRecentViewBonobo {
+ GObject parent_instance; /* We emit signals */
+
+ BonoboUIComponent *uic;
+ gchar *path; /* The menu path where our stuff
+ * will go
+ */
+
+ gulong changed_cb_id;
+
+ gchar *uid; /* unique id used for the verb name */
+
+ gboolean show_icons;
+ gboolean show_numbers;
+#ifndef USE_STABLE_LIBGNOMEUI
+ GnomeIconTheme *theme;
+#endif
+ EggRecentViewBonoboTooltipFunc tooltip_func;
+ gpointer tooltip_func_data;
+
+ EggRecentModel *model;
+ GConfClient *client;
+ GtkIconSize icon_size;
+};
+
+
+struct _EggRecentViewBonoboMenuData {
+ EggRecentViewBonobo *view;
+ EggRecentItem *item;
+};
+
+typedef struct _EggRecentViewBonoboMenuData EggRecentViewBonoboMenuData;
+
+enum {
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+/* GObject properties */
+enum {
+ PROP_BOGUS,
+ PROP_UI_COMPONENT,
+ PROP_MENU_PATH,
+ PROP_SHOW_ICONS,
+ PROP_SHOW_NUMBERS
+};
+
+static guint egg_recent_view_bonobo_signals[LAST_SIGNAL] = { 0 };
+
+static void
+egg_recent_view_bonobo_clear (EggRecentViewBonobo *view)
+{
+ gint i=1;
+ gboolean done=FALSE;
+ EggRecentModel *model;
+
+ g_return_if_fail (view->uic);
+
+ model = egg_recent_view_get_model (EGG_RECENT_VIEW (view));
+
+ while (!done)
+ {
+ gchar *verb_name = g_strdup_printf ("%s-%d", view->uid, i);
+ gchar *item_path = g_strconcat (view->path, "/", verb_name, NULL);
+ if (bonobo_ui_component_path_exists (view->uic, item_path, NULL))
+ bonobo_ui_component_rm (view->uic, item_path, NULL);
+ else
+ done=TRUE;
+
+ g_free (item_path);
+ g_free (verb_name);
+
+ i++;
+ }
+}
+
+static void
+egg_recent_view_bonobo_menu_cb (BonoboUIComponent *uic, gpointer data, const char *cname)
+{
+ EggRecentViewBonoboMenuData *md = (EggRecentViewBonoboMenuData *) data;
+ EggRecentItem *item;
+
+ g_return_if_fail (md);
+ g_return_if_fail (md->item);
+ g_return_if_fail (md->view);
+ g_return_if_fail (EGG_IS_RECENT_VIEW_BONOBO (md->view));
+
+ item = md->item;
+ egg_recent_item_ref (item);
+
+ g_signal_emit (G_OBJECT(md->view),
+ egg_recent_view_bonobo_signals[ACTIVATE], 0,
+ item);
+
+ egg_recent_item_unref (item);
+}
+
+static void
+egg_recent_view_bonobo_menu_data_destroy_cb (gpointer data, GClosure *closure)
+{
+ EggRecentViewBonoboMenuData *md = data;
+
+ egg_recent_item_unref (md->item);
+ g_free (md);
+}
+
+
+static void
+egg_recent_view_bonobo_set_list (EggRecentViewBonobo *view, GList *list)
+{
+ BonoboUIComponent* ui_component;
+ unsigned int i;
+ gchar *label = NULL;
+ gchar *verb_name = NULL;
+ gchar *tip = NULL;
+ gchar *escaped_name = NULL;
+ gchar *item_path = NULL;
+ gchar *base_uri;
+ gchar *utf8_uri;
+ gchar *cmd;
+ gchar *xml_escaped_name;
+ EggRecentViewBonoboMenuData *md;
+ EggRecentModel *model;
+ GClosure *closure;
+
+ g_return_if_fail (view);
+
+ ui_component = view->uic;
+ g_return_if_fail (BONOBO_IS_UI_COMPONENT (ui_component));
+
+
+ model = egg_recent_view_get_model (EGG_RECENT_VIEW (view));
+
+ egg_recent_view_bonobo_clear (view);
+
+
+ bonobo_ui_component_freeze (ui_component, NULL);
+
+ for (i = 1; i <= g_list_length (list); ++i)
+ {
+ EggRecentItem *item = (EggRecentItem *)g_list_nth_data (list, i-1);
+
+ utf8_uri = egg_recent_item_get_uri_for_display (item);
+ if (utf8_uri == NULL)
+ continue;
+
+ /* this is what gets passed to our private "activate" callback */
+ md = (EggRecentViewBonoboMenuData *)g_malloc (sizeof (EggRecentViewBonoboMenuData));
+ md->view = view;
+ md->item = item;
+
+ egg_recent_item_ref (md->item);
+
+ base_uri = g_path_get_basename (utf8_uri);
+ xml_escaped_name = g_markup_escape_text (base_uri,
+ strlen (base_uri));
+
+ escaped_name = egg_recent_util_escape_underlines (xml_escaped_name);
+ g_free (xml_escaped_name);
+
+ tip = NULL;
+ if (view->tooltip_func != NULL) {
+ gchar *tmp_tip;
+ tip = view->tooltip_func (item,
+ view->tooltip_func_data);
+ tmp_tip = g_markup_escape_text (tip, strlen (tip));
+ g_free (tip);
+ tip = tmp_tip;
+ }
+
+ if (tip == NULL)
+ tip = g_strdup ("");
+
+ verb_name = g_strdup_printf ("%s-%d", view->uid, i);
+
+ if (view->show_icons) {
+ GdkPixbuf *pixbuf;
+ gchar *mime_type;
+ gchar *uri;
+
+ mime_type = egg_recent_item_get_mime_type (item);
+ uri = egg_recent_item_get_uri (item);
+#ifndef USE_STABLE_LIBGNOMEUI
+ {
+ int width, height;
+
+ gtk_icon_size_lookup_for_settings
+ (gtk_settings_get_default (),
+ view->icon_size,
+ &width, &height);
+ pixbuf = egg_recent_util_get_icon
+ (view->theme,
+ uri, mime_type,
+ height);
+ }
+#else
+ pixbuf = NULL;
+#endif
+
+
+ if (pixbuf != NULL) {
+ gchar *pixbuf_xml;
+
+ /* Riiiiight.... */
+ pixbuf_xml = bonobo_ui_util_pixbuf_to_xml (pixbuf);
+
+ cmd = g_strdup_printf ("<cmd name=\"%s\" pixtype=\"pixbuf\" pixname=\"%s\"/>", verb_name, pixbuf_xml);
+
+ g_free (pixbuf_xml);
+ g_object_unref (pixbuf);
+ } else {
+ cmd = g_strdup_printf ("<cmd name=\"%s\"/> ",
+ verb_name);
+ }
+
+ g_free (mime_type);
+ g_free (uri);
+ } else
+ cmd = g_strdup_printf ("<cmd name=\"%s\"/> ",
+ verb_name);
+ bonobo_ui_component_set_translate (ui_component, "/commands/", cmd, NULL);
+
+ closure = g_cclosure_new (G_CALLBACK (egg_recent_view_bonobo_menu_cb),
+ md, egg_recent_view_bonobo_menu_data_destroy_cb);
+
+ bonobo_ui_component_add_verb_full (ui_component, verb_name,
+ closure);
+
+ if (view->show_numbers) {
+ if (i < 10)
+ label = g_strdup_printf ("_%d. %s", i,
+ escaped_name);
+ else
+ label = g_strdup_printf ("%d. %s", i, escaped_name);
+ } else {
+ label = g_strdup (escaped_name);
+ }
+
+
+
+ item_path = g_strconcat (view->path, "/", verb_name, NULL);
+
+ if (bonobo_ui_component_path_exists (ui_component, item_path, NULL))
+ {
+ bonobo_ui_component_set_prop (ui_component, item_path,
+ "label", label, NULL);
+
+ bonobo_ui_component_set_prop (ui_component, item_path,
+ "tip", tip, NULL);
+ }
+ else
+ {
+ gchar *xml;
+
+ xml = g_strdup_printf ("<menuitem name=\"%s\" "
+ "verb=\"%s\""
+ " _label=\"%s\" _tip=\"%s\" "
+ "hidden=\"0\" />",
+ verb_name, verb_name, label,
+ tip);
+
+ bonobo_ui_component_set_translate (ui_component, view->path, xml, NULL);
+
+ g_free (xml);
+ }
+
+ g_free (label);
+ g_free (verb_name);
+ g_free (tip);
+ g_free (escaped_name);
+ g_free (item_path);
+ g_free (utf8_uri);
+ g_free (base_uri);
+ g_free (cmd);
+
+ }
+
+
+ bonobo_ui_component_thaw (ui_component, NULL);
+}
+
+static void
+model_changed_cb (EggRecentModel *model, GList *list, EggRecentViewBonobo *view)
+{
+ if (list != NULL)
+ egg_recent_view_bonobo_set_list (view, list);
+ else
+ egg_recent_view_bonobo_clear (view);
+}
+
+
+static EggRecentModel *
+egg_recent_view_bonobo_get_model (EggRecentView *view_parent)
+{
+ EggRecentViewBonobo *view;
+
+ g_return_val_if_fail (view_parent, NULL);
+ view = EGG_RECENT_VIEW_BONOBO (view_parent);
+
+ return view->model;
+}
+
+static void
+egg_recent_view_bonobo_set_model (EggRecentView *view_parent, EggRecentModel *model)
+{
+ EggRecentViewBonobo *view;
+
+ g_return_if_fail (view_parent);
+ view = EGG_RECENT_VIEW_BONOBO (view_parent);
+
+ if (view->model)
+ g_signal_handler_disconnect (G_OBJECT (view->model),
+ view->changed_cb_id);
+
+ view->model = model;
+ g_object_ref (view->model);
+ view->changed_cb_id = g_signal_connect_object (G_OBJECT (model),
+ "changed",
+ G_CALLBACK (model_changed_cb),
+ view, 0);
+
+ egg_recent_model_changed (view->model);
+}
+
+static void
+egg_recent_view_bonobo_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggRecentViewBonobo *view = EGG_RECENT_VIEW_BONOBO (object);
+
+ switch (prop_id)
+ {
+ case PROP_UI_COMPONENT:
+ egg_recent_view_bonobo_set_ui_component (EGG_RECENT_VIEW_BONOBO (view),
+ BONOBO_UI_COMPONENT (g_value_get_object (value)));
+ break;
+ case PROP_MENU_PATH:
+ view->path = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_SHOW_ICONS:
+ egg_recent_view_bonobo_show_icons (view,
+ g_value_get_boolean (value));
+ default:
+ case PROP_SHOW_NUMBERS:
+ egg_recent_view_bonobo_show_numbers (view,
+ g_value_get_boolean (value));
+ break;
+ break;
+ }
+}
+
+static void
+egg_recent_view_bonobo_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggRecentViewBonobo *view = EGG_RECENT_VIEW_BONOBO (object);
+
+ switch (prop_id)
+ {
+ case PROP_UI_COMPONENT:
+ g_value_set_pointer (value, view->uic);
+ break;
+ case PROP_MENU_PATH:
+ g_value_set_string (value, g_strdup (view->path));
+ break;
+ case PROP_SHOW_ICONS:
+ g_value_set_boolean (value, view->show_icons);
+ break;
+ case PROP_SHOW_NUMBERS:
+ g_value_set_boolean (value, view->show_numbers);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_recent_view_bonobo_finalize (GObject *object)
+{
+ EggRecentViewBonobo *view = EGG_RECENT_VIEW_BONOBO (object);
+
+ g_free (view->path);
+ g_free (view->uid);
+
+ g_object_unref (view->model);
+ g_object_unref (view->uic);
+#ifndef USE_STABLE_LIBGNOMEUI
+ g_object_unref (view->theme);
+#endif
+ g_object_unref (view->client);
+}
+
+static void
+egg_recent_view_bonobo_class_init (EggRecentViewBonoboClass * klass)
+{
+ GObjectClass *object_class;
+
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = egg_recent_view_bonobo_set_property;
+ object_class->get_property = egg_recent_view_bonobo_get_property;
+ object_class->finalize = egg_recent_view_bonobo_finalize;
+
+ egg_recent_view_bonobo_signals[ACTIVATE] = g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggRecentViewBonoboClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ EGG_TYPE_RECENT_ITEM);
+
+ g_object_class_install_property (object_class,
+ PROP_UI_COMPONENT,
+ g_param_spec_object ("ui-component",
+ "UI Component",
+ "BonoboUIComponent for menus.",
+ bonobo_ui_component_get_type(),
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_MENU_PATH,
+ g_param_spec_string ("ui-path",
+ "Path",
+ "The path to put the menu items.",
+ "/menus/File/EggRecentDocuments",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_ICONS,
+ g_param_spec_boolean ("show-icons",
+ "Show Icons",
+ "Whether or not to show icons",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_NUMBERS,
+ g_param_spec_boolean ("show-numbers",
+ "Show Numbers",
+ "Whether or not to show numbers",
+ TRUE,
+ G_PARAM_READWRITE));
+
+
+
+ klass->activate = NULL;
+}
+
+static void
+egg_recent_view_init (EggRecentViewClass *iface)
+{
+ iface->do_get_model = egg_recent_view_bonobo_get_model;
+ iface->do_set_model = egg_recent_view_bonobo_set_model;
+}
+
+static void
+show_menus_changed_cb (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ EggRecentViewBonobo *view)
+{
+ GConfValue *value;
+
+ value = gconf_entry_get_value (entry);
+
+ g_return_if_fail (value->type == GCONF_VALUE_BOOL);
+
+ egg_recent_view_bonobo_show_icons (view,
+ gconf_value_get_bool (value));
+
+}
+
+#ifndef USE_STABLE_LIBGNOMEUI
+static void
+theme_changed_cb (GnomeIconTheme *theme, EggRecentViewBonobo *view)
+{
+ if (view->model != NULL)
+ egg_recent_model_changed (view->model);
+}
+#endif
+
+static void
+egg_recent_view_bonobo_init (EggRecentViewBonobo *view)
+{
+ view->uid = egg_recent_util_get_unique_id ();
+#ifndef USE_STABLE_LIBGNOMEUI
+ view->theme = gnome_icon_theme_new ();
+ gnome_icon_theme_set_allow_svg (view->theme, TRUE);
+ g_signal_connect_object (view->theme, "changed",
+ G_CALLBACK (theme_changed_cb), view, 0);
+#endif
+
+ view->client = gconf_client_get_default ();
+ view->show_icons =
+ gconf_client_get_bool (view->client,
+ "/desktop/gnome/interface/menus_have_icons",
+ NULL);
+
+ gconf_client_add_dir (view->client, "/desktop/gnome/interface",
+ GCONF_CLIENT_PRELOAD_NONE,
+ NULL);
+ gconf_client_notify_add (view->client,
+ "/desktop/gnome/interface/menus_have_icons",
+ (GConfClientNotifyFunc)show_menus_changed_cb,
+ view, NULL, NULL);
+
+ view->tooltip_func = NULL;
+ view->tooltip_func_data = NULL;
+
+ view->icon_size = GTK_ICON_SIZE_MENU;
+}
+
+void
+egg_recent_view_bonobo_set_icon_size (EggRecentViewBonobo *view,
+ GtkIconSize icon_size)
+{
+ if (view->icon_size != icon_size) {
+ view->icon_size = icon_size;
+ egg_recent_model_changed (view->model);
+ } else {
+ view->icon_size = icon_size;
+ }
+}
+
+GtkIconSize
+egg_recent_view_bonobo_get_icon_size (EggRecentViewBonobo *view)
+{
+ return view->icon_size;
+}
+
+void
+egg_recent_view_bonobo_show_icons (EggRecentViewBonobo *view, gboolean show)
+{
+ view->show_icons = show;
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+void
+egg_recent_view_bonobo_show_numbers (EggRecentViewBonobo *view, gboolean show)
+{
+ view->show_numbers = show;
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+void
+egg_recent_view_bonobo_set_ui_component (EggRecentViewBonobo *view, BonoboUIComponent *uic)
+{
+ g_return_if_fail (view);
+ g_return_if_fail (uic);
+
+ view->uic = uic;
+
+ g_object_ref (view->uic);
+}
+
+void
+egg_recent_view_bonobo_set_ui_path (EggRecentViewBonobo *view, const gchar *path)
+{
+ g_return_if_fail (view);
+ g_return_if_fail (path);
+
+ view->path = g_strdup (path);
+}
+
+const BonoboUIComponent *
+egg_recent_view_bonobo_get_ui_component (EggRecentViewBonobo *view)
+{
+ g_return_val_if_fail (view, NULL);
+
+ return view->uic;
+}
+
+gchar *
+egg_recent_view_bonobo_get_ui_path (EggRecentViewBonobo *view)
+{
+ g_return_val_if_fail (view, NULL);
+
+ return g_strdup (view->path);
+}
+
+void
+egg_recent_view_bonobo_set_tooltip_func (EggRecentViewBonobo *view,
+ EggRecentViewBonoboTooltipFunc func,
+ gpointer user_data)
+{
+ view->tooltip_func = func;
+ view->tooltip_func_data = user_data;
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+/**
+ * egg_recent_view_bonobo_new:
+ * @appname: The name of your application.
+ * @limit: The maximum number of items allowed.
+ *
+ * This creates a new EggRecentViewBonobo object.
+ *
+ * Returns: a EggRecentViewBonobo object
+ */
+EggRecentViewBonobo *
+egg_recent_view_bonobo_new (BonoboUIComponent *uic, const gchar *path)
+{
+ EggRecentViewBonobo *view;
+
+ g_return_val_if_fail (uic, NULL);
+ g_return_val_if_fail (path, NULL);
+
+ view = EGG_RECENT_VIEW_BONOBO (g_object_new (egg_recent_view_bonobo_get_type (),
+ "ui-path", path,
+ "ui-component", uic,
+ "show-icons", FALSE,
+ "show-numbers", TRUE, NULL));
+
+ g_return_val_if_fail (view, NULL);
+
+ return view;
+}
+
+/**
+ * egg_recent_view_bonobo_get_type:
+ * @:
+ *
+ * This returns a GType representing a EggRecentViewBonobo object.
+ *
+ * Returns: a GType
+ */
+GType
+egg_recent_view_bonobo_get_type (void)
+{
+ static GType egg_recent_view_bonobo_type = 0;
+
+ if(!egg_recent_view_bonobo_type) {
+ static const GTypeInfo egg_recent_view_bonobo_info = {
+ sizeof (EggRecentViewBonoboClass),
+ NULL, /* base init */
+ NULL, /* base finalize */
+ (GClassInitFunc)egg_recent_view_bonobo_class_init, /* class init */
+ NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof (EggRecentViewBonobo),
+ 0,
+ (GInstanceInitFunc) egg_recent_view_bonobo_init
+ };
+
+ static const GInterfaceInfo view_info =
+ {
+ (GInterfaceInitFunc) egg_recent_view_init,
+ NULL,
+ NULL
+ };
+
+ egg_recent_view_bonobo_type = g_type_register_static (G_TYPE_OBJECT,
+ "EggRecentViewBonobo",
+ &egg_recent_view_bonobo_info, 0);
+ g_type_add_interface_static (egg_recent_view_bonobo_type,
+ EGG_TYPE_RECENT_VIEW,
+ &view_info);
+ }
+
+ return egg_recent_view_bonobo_type;
+}
+
--- /dev/null
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+#ifndef __EGG_RECENT_VIEW_BONOBO_H__
+#define __EGG_RECENT_VIEW_BONOBO_H__
+
+#include <libbonoboui.h>
+
+G_BEGIN_DECLS
+
+#define EGG_RECENT_VIEW_BONOBO(obj) G_TYPE_CHECK_INSTANCE_CAST (obj, egg_recent_view_bonobo_get_type (), EggRecentViewBonobo)
+#define EGG_RECENT_VIEW_BONOBO_CLASS(klass) G_TYPE_CHECK_CLASS_CAST (klass, egg_recent_view_bonobo_get_type (), EggRecentViewBonoboClass)
+#define EGG_IS_RECENT_VIEW_BONOBO(obj) G_TYPE_CHECK_INSTANCE_TYPE (obj, egg_recent_view_bonobo_get_type ())
+
+typedef char *(*EggRecentViewBonoboTooltipFunc) (EggRecentItem *item,
+ gpointer user_data);
+
+typedef struct _EggRecentViewBonobo EggRecentViewBonobo;
+
+typedef struct _EggRecentViewBonoboClass EggRecentViewBonoboClass;
+
+struct _EggRecentViewBonoboClass {
+ GObjectClass parent_class;
+
+ void (*activate) (EggRecentViewBonobo *view, EggRecentItem *item);
+};
+
+GType egg_recent_view_bonobo_get_type (void);
+
+EggRecentViewBonobo * egg_recent_view_bonobo_new (BonoboUIComponent *uic,
+ const gchar *path);
+
+
+void egg_recent_view_bonobo_set_ui_component (EggRecentViewBonobo *view,
+ BonoboUIComponent *uic);
+
+void egg_recent_view_bonobo_set_ui_path (EggRecentViewBonobo *view,
+ const gchar *path);
+
+gchar * egg_recent_view_bonobo_get_ui_path (EggRecentViewBonobo *view);
+const BonoboUIComponent *egg_recent_view_bonobo_get_ui_component (EggRecentViewBonobo *view);
+
+void egg_recent_view_bonobo_show_icons (EggRecentViewBonobo *view,
+ gboolean show);
+
+void egg_recent_view_bonobo_show_numbers (EggRecentViewBonobo *view,
+ gboolean show);
+
+void egg_recent_view_bonobo_set_tooltip_func (EggRecentViewBonobo *view,
+ EggRecentViewBonoboTooltipFunc func,
+ gpointer user_data);
+
+void egg_recent_view_bonobo_set_icon_size (EggRecentViewBonobo *view,
+ GtkIconSize icon_size);
+
+GtkIconSize egg_recent_view_bonobo_get_icon_size (EggRecentViewBonobo *view);
+
+G_END_DECLS
+
+#endif /* __EGG_RECENT_VIEW_BONOBO_H__ */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * James Willcox <jwillcox@cs.indiana.edu>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <libgnomevfs/gnome-vfs.h>
+#ifndef USE_STABLE_LIBGNOMEUI
+#include <libgnomeui/gnome-icon-theme.h>
+#endif
+#include <gconf/gconf-client.h>
+#include "egg-recent-model.h"
+#include "egg-recent-view.h"
+#include "egg-recent-view-gtk.h"
+#include "egg-recent-util.h"
+#include "egg-recent-item.h"
+
+struct _EggRecentViewGtk {
+ GObject parent_instance; /* We emit signals */
+
+ GtkWidget *menu;
+ GtkWidget *start_menu_item;
+
+ gboolean leading_sep;
+ gboolean trailing_sep;
+
+ gulong changed_cb_id;
+
+ gchar *uid;
+
+ gboolean show_icons;
+ gboolean show_numbers;
+#ifndef USE_STABLE_LIBGNOMEUI
+ GnomeIconTheme *theme;
+#endif
+
+ GtkTooltips *tooltips;
+ EggRecentViewGtkTooltipFunc tooltip_func;
+ gpointer tooltip_func_data;
+
+ EggRecentModel *model;
+ GConfClient *client;
+ GtkIconSize icon_size;
+};
+
+
+
+struct _EggRecentViewGtkMenuData {
+ EggRecentViewGtk *view;
+ EggRecentItem *item;
+};
+
+typedef struct _EggRecentViewGtkMenuData EggRecentViewGtkMenuData;
+
+enum {
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+/* GObject properties */
+enum {
+ PROP_BOGUS,
+ PROP_MENU,
+ PROP_START_MENU_ITEM,
+ PROP_SHOW_ICONS,
+ PROP_SHOW_NUMBERS
+};
+
+static guint view_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+egg_recent_view_gtk_clear (EggRecentViewGtk *view)
+{
+ GList *menu_children;
+ GList *p;
+ GObject *menu_item;
+ gint *menu_data=NULL;
+
+ g_return_if_fail (view->menu != NULL);
+
+ menu_children = gtk_container_get_children (GTK_CONTAINER (view->menu));
+
+ p = menu_children;
+ while (p != NULL) {
+ menu_item = (GObject *)p->data;
+
+ menu_data = (gint *)g_object_get_data (menu_item,
+ view->uid);
+
+ if (menu_data) {
+ gtk_container_remove (GTK_CONTAINER (view->menu),
+ GTK_WIDGET (menu_item));
+
+ }
+
+ p = p->next;
+ }
+}
+
+
+static gint
+egg_recent_view_gtk_find_menu_offset (EggRecentViewGtk *view)
+{
+ gint i;
+ GList *menu_children;
+ GList *p;
+ GtkWidget *menu_item;
+ gint menu_loc=-1;
+
+ g_return_val_if_fail (view, 0);
+
+ menu_children = GTK_MENU_SHELL (view->menu)->children;
+
+ i = 0;
+ p = menu_children;
+ while (p != NULL) {
+ menu_item = (GtkWidget *)p->data;
+
+ if (menu_item == view->start_menu_item) {
+ menu_loc = i;
+ break;
+ }
+
+ p = p->next;
+ i++;
+ }
+
+ return menu_loc;
+}
+
+static void
+egg_recent_view_gtk_menu_cb (GtkWidget *menu, gpointer data)
+{
+ EggRecentViewGtkMenuData *md = (EggRecentViewGtkMenuData *) data;
+ EggRecentItem *item;
+
+ g_return_if_fail (md);
+ g_return_if_fail (md->item);
+ g_return_if_fail (md->view);
+ g_return_if_fail (EGG_IS_RECENT_VIEW_GTK (md->view));
+
+ item = md->item;
+
+ egg_recent_item_ref (item);
+
+ g_signal_emit (G_OBJECT(md->view), view_signals[ACTIVATE], 0,
+ item);
+
+ egg_recent_item_unref (item);
+}
+
+static void
+egg_recent_view_gtk_destroy_cb (gpointer data, GClosure *closure)
+{
+ EggRecentViewGtkMenuData *md = data;
+
+ egg_recent_item_unref (md->item);
+ g_free (md);
+}
+
+static GtkWidget *
+egg_recent_view_gtk_new_menu_item (EggRecentViewGtk *view,
+ EggRecentItem *item,
+ gint index)
+{
+ GtkWidget *menu_item;
+ EggRecentViewGtkMenuData *md=(EggRecentViewGtkMenuData *)g_malloc (sizeof (EggRecentViewGtkMenuData));
+ gchar *text;
+ gchar *basename;
+ gchar *escaped;
+ gchar *uri;
+
+ g_return_val_if_fail (view, NULL);
+
+ if (item != NULL) {
+ gchar *mime_type;
+ GtkWidget *image;
+ GdkPixbuf *pixbuf;
+
+ uri = egg_recent_item_get_uri_for_display (item);
+ if (uri == NULL)
+ return NULL;
+
+ basename = g_path_get_basename (uri);
+ escaped = egg_recent_util_escape_underlines (basename);
+ g_free (basename);
+ g_free (uri);
+
+ if (view->show_numbers) {
+ /* avoid having conflicting mnemonics */
+ if (index >= 10)
+ text = g_strdup_printf ("%d. %s", index,
+ escaped);
+ else
+ text = g_strdup_printf ("_%d. %s", index,
+ escaped);
+ g_free (escaped);
+ } else {
+ text = escaped;
+ }
+
+
+ mime_type = egg_recent_item_get_mime_type (item);
+#ifndef USE_STABLE_LIBGNOMEUI
+ {
+ int width, height;
+
+ gtk_icon_size_lookup_for_settings
+ (gtk_widget_get_settings (view->menu),
+ view->icon_size,
+ &width, &height);
+
+ pixbuf = egg_recent_util_get_icon (view->theme, uri,
+ mime_type,
+ height);
+ }
+#else
+ pixbuf = NULL;
+#endif
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ if (view->show_icons)
+ gtk_widget_show (image);
+
+ menu_item = gtk_image_menu_item_new_with_mnemonic (text);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
+ image);
+
+ md->view = view;
+ md->item = item;
+
+ egg_recent_item_ref (md->item);
+
+ g_signal_connect_data (G_OBJECT (menu_item), "activate",
+ G_CALLBACK (egg_recent_view_gtk_menu_cb),
+ md,
+ (GClosureNotify)egg_recent_view_gtk_destroy_cb,
+ 0);
+
+ g_free (mime_type);
+ g_free (text);
+ }
+ else {
+ menu_item = gtk_separator_menu_item_new ();
+ }
+
+ g_return_val_if_fail (menu_item, NULL);
+
+ /**
+ * this is a tag so we can distinguish our menu items
+ * from others that may be in the menu.
+ */
+ g_object_set_data (G_OBJECT (menu_item),
+ view->uid,
+ GINT_TO_POINTER (1));
+
+
+ gtk_widget_show (menu_item);
+
+ return menu_item;
+}
+
+static void
+egg_recent_view_gtk_add_to_menu (EggRecentViewGtk *view,
+ EggRecentItem *item,
+ gint display,
+ gint index)
+{
+ GtkWidget *menu_item;
+ gint menu_offset;
+
+ g_return_if_fail (view);
+ g_return_if_fail (view->menu);
+
+ menu_offset = egg_recent_view_gtk_find_menu_offset (view);
+
+ menu_item = egg_recent_view_gtk_new_menu_item (view, item, display);
+
+ if (view->tooltip_func != NULL && menu_item != NULL) {
+ view->tooltip_func (view->tooltips, menu_item,
+ item, view->tooltip_func_data);
+ }
+
+ if (menu_item)
+ gtk_menu_shell_insert (GTK_MENU_SHELL (view->menu), menu_item,
+ menu_offset+index);
+}
+
+
+
+
+static void
+egg_recent_view_gtk_set_list (EggRecentViewGtk *view, GList *list)
+{
+ EggRecentItem *item;
+ GList *p;
+ gint display=1;
+ gint index=1;
+
+ g_return_if_fail (view);
+
+ egg_recent_view_gtk_clear (view);
+
+ if (view->leading_sep) {
+ egg_recent_view_gtk_add_to_menu (view, NULL, display, index);
+ index++;
+ }
+
+ p = list;
+ while (p != NULL) {
+ item = (EggRecentItem *)p->data;
+
+ egg_recent_view_gtk_add_to_menu (view, item, display, index);
+
+ p = p->next;
+ display++;
+ index++;
+ }
+
+ if (view->trailing_sep)
+ egg_recent_view_gtk_add_to_menu (view, NULL, display, index);
+}
+
+static void
+model_changed_cb (EggRecentModel *model, GList *list, EggRecentViewGtk *view)
+{
+ if (list != NULL)
+ egg_recent_view_gtk_set_list (view, list);
+ else
+ egg_recent_view_gtk_clear (view);
+}
+
+static EggRecentModel *
+egg_recent_view_gtk_get_model (EggRecentView *view_parent)
+{
+ EggRecentViewGtk *view;
+
+ g_return_val_if_fail (view_parent != NULL, NULL);
+ view = EGG_RECENT_VIEW_GTK (view_parent);
+ return view->model;
+}
+
+static void
+egg_recent_view_gtk_set_model (EggRecentView *view_parent,
+ EggRecentModel *model)
+{
+ EggRecentViewGtk *view;
+
+ g_return_if_fail (view_parent != NULL);
+ view = EGG_RECENT_VIEW_GTK (view_parent);
+
+ if (view->model != NULL) {
+ g_object_unref (view->model);
+ g_signal_handler_disconnect (G_OBJECT (model),
+ view->changed_cb_id);
+ }
+
+ view->model = model;
+ g_object_ref (view->model);
+
+ view->changed_cb_id = g_signal_connect_object (G_OBJECT (model),
+ "changed",
+ G_CALLBACK (model_changed_cb),
+ view, 0);
+
+ egg_recent_model_changed (view->model);
+}
+
+void
+egg_recent_view_gtk_set_leading_sep (EggRecentViewGtk *view, gboolean val)
+{
+ view->leading_sep = val;
+
+ egg_recent_view_gtk_clear (view);
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+void
+egg_recent_view_gtk_set_trailing_sep (EggRecentViewGtk *view, gboolean val)
+{
+ view->trailing_sep = val;
+
+ egg_recent_view_gtk_clear (view);
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+static void
+egg_recent_view_gtk_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggRecentViewGtk *view = EGG_RECENT_VIEW_GTK (object);
+
+ switch (prop_id)
+ {
+ case PROP_MENU:
+ egg_recent_view_gtk_set_menu (view,
+ GTK_WIDGET (g_value_get_object (value)));
+ break;
+ case PROP_START_MENU_ITEM:
+ egg_recent_view_gtk_set_start_menu_item (view,
+ g_value_get_object (value));
+ break;
+ case PROP_SHOW_ICONS:
+ egg_recent_view_gtk_show_icons (view,
+ g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_NUMBERS:
+ egg_recent_view_gtk_show_numbers (view,
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+egg_recent_view_gtk_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggRecentViewGtk *view = EGG_RECENT_VIEW_GTK (object);
+
+ switch (prop_id)
+ {
+ case PROP_MENU:
+ g_value_set_object (value, view->menu);
+ break;
+ case PROP_START_MENU_ITEM:
+ g_value_set_object (value, view->start_menu_item);
+ break;
+ case PROP_SHOW_ICONS:
+ g_value_set_boolean (value, view->show_icons);
+ break;
+ case PROP_SHOW_NUMBERS:
+ g_value_set_boolean (value, view->show_numbers);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+egg_recent_view_gtk_finalize (GObject *object)
+{
+ EggRecentViewGtk *view = EGG_RECENT_VIEW_GTK (object);
+
+ g_signal_handler_disconnect (G_OBJECT (view->model),
+ view->changed_cb_id);
+
+ g_free (view->uid);
+
+ g_object_unref (view->menu);
+ g_object_unref (view->model);
+#ifndef USE_STABLE_LIBGNOMEUI
+ g_object_unref (view->theme);
+#endif
+ g_object_unref (view->client);
+
+ g_object_unref (view->tooltips);
+}
+
+static void
+egg_recent_view_gtk_class_init (EggRecentViewGtkClass * klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = egg_recent_view_gtk_set_property;
+ object_class->get_property = egg_recent_view_gtk_get_property;
+ object_class->finalize = egg_recent_view_gtk_finalize;
+
+ view_signals[ACTIVATE] = g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggRecentViewGtkClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ EGG_TYPE_RECENT_ITEM);
+
+ g_object_class_install_property (object_class,
+ PROP_MENU,
+ g_param_spec_object ("menu",
+ "Menu",
+ "The GtkMenuShell this object will update.",
+ gtk_menu_get_type(),
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_START_MENU_ITEM,
+ g_param_spec_object ("start-menu-item",
+ "Start Menu Item",
+ "The menu item that precedes where are menu items will go",
+ gtk_menu_item_get_type (),
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_ICONS,
+ g_param_spec_boolean ("show-icons",
+ "Show Icons",
+ "Whether or not to show icons",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_NUMBERS,
+ g_param_spec_boolean ("show-numbers",
+ "Show Numbers",
+ "Whether or not to show numbers",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ klass->activate = NULL;
+}
+
+static void
+egg_recent_view_init (EggRecentViewClass *iface)
+{
+ iface->do_get_model = egg_recent_view_gtk_get_model;
+ iface->do_set_model = egg_recent_view_gtk_set_model;
+}
+
+static void
+show_menus_changed_cb (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ EggRecentViewGtk *view)
+{
+ GConfValue *value;
+
+ value = gconf_entry_get_value (entry);
+
+ g_return_if_fail (value->type == GCONF_VALUE_BOOL);
+
+ egg_recent_view_gtk_show_icons (view,
+ gconf_value_get_bool (value));
+
+}
+
+#ifndef USE_STABLE_LIBGNOMEUI
+static void
+theme_changed_cb (GnomeIconTheme *theme, EggRecentViewGtk *view)
+{
+ if (view->model != NULL)
+ egg_recent_model_changed (view->model);
+}
+#endif
+
+static void
+egg_recent_view_gtk_init (EggRecentViewGtk * view)
+{
+ view->client = gconf_client_get_default ();
+
+ view->show_icons =
+ gconf_client_get_bool (view->client,
+ "/desktop/gnome/interface/menus_have_icons",
+ NULL);
+
+ gconf_client_add_dir (view->client, "/desktop/gnome/interface",
+ GCONF_CLIENT_PRELOAD_NONE,
+ NULL);
+ gconf_client_notify_add (view->client,
+ "/desktop/gnome/interface/menus_have_icons",
+ (GConfClientNotifyFunc)show_menus_changed_cb,
+ view, NULL, NULL);
+
+
+ view->leading_sep = FALSE;
+ view->trailing_sep = FALSE;
+
+ view->uid = egg_recent_util_get_unique_id ();
+#ifndef USE_STABLE_LIBGNOMEUI
+ view->theme = gnome_icon_theme_new ();
+ gnome_icon_theme_set_allow_svg (view->theme, TRUE);
+ g_signal_connect_object (view->theme, "changed",
+ G_CALLBACK (theme_changed_cb), view, 0);
+#endif
+ view->tooltips = gtk_tooltips_new ();
+ g_object_ref (view->tooltips);
+ gtk_object_sink (GTK_OBJECT (view->tooltips));
+ view->tooltip_func = NULL;
+ view->tooltip_func_data = NULL;
+
+ view->icon_size = GTK_ICON_SIZE_MENU;
+}
+
+void
+egg_recent_view_gtk_set_icon_size (EggRecentViewGtk *view,
+ GtkIconSize icon_size)
+{
+ if (view->icon_size != icon_size) {
+ view->icon_size = icon_size;
+ egg_recent_model_changed (view->model);
+ } else {
+ view->icon_size = icon_size;
+ }
+}
+
+GtkIconSize
+egg_recent_view_gtk_get_icon_size (EggRecentViewGtk *view)
+{
+ return view->icon_size;
+}
+
+void
+egg_recent_view_gtk_show_icons (EggRecentViewGtk *view, gboolean show)
+{
+ view->show_icons = show;
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+void
+egg_recent_view_gtk_show_numbers (EggRecentViewGtk *view, gboolean show)
+{
+ view->show_numbers = show;
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+void
+egg_recent_view_gtk_set_tooltip_func (EggRecentViewGtk *view,
+ EggRecentViewGtkTooltipFunc func,
+ gpointer user_data)
+{
+ view->tooltip_func = func;
+ view->tooltip_func_data = user_data;
+
+ if (view->model)
+ egg_recent_model_changed (view->model);
+}
+
+/**
+ * egg_recent_view_gtk_set_menu:
+ * @view: A EggRecentViewGtk object.
+ * @menu: The GtkMenuShell to put the menu items in.
+ *
+ * Use this function to change the GtkMenuShell that the recent
+ * documents appear in.
+ *
+ */
+void
+egg_recent_view_gtk_set_menu (EggRecentViewGtk *view,
+ GtkWidget *menu)
+{
+ g_return_if_fail (view);
+ g_return_if_fail (EGG_IS_RECENT_VIEW_GTK (view));
+ g_return_if_fail (menu);
+
+ if (view->menu != NULL)
+ g_object_unref (view->menu);
+
+ view->menu = menu;
+ g_object_ref (view->menu);
+}
+
+/**
+ * egg_recent_view_gtk_set_start_menu_item:
+ * @view: A EggRecentViewGtk object.
+ * @start_menu_item: The menu item that appears just before where our menu
+ * items should appear
+ *
+ */
+void
+egg_recent_view_gtk_set_start_menu_item (EggRecentViewGtk *view,
+ GtkWidget *menu_item)
+{
+ g_return_if_fail (view);
+ g_return_if_fail (EGG_IS_RECENT_VIEW_GTK (view));
+
+ view->start_menu_item = menu_item;
+}
+
+/**
+ * egg_recent_view_gtk_get_menu:
+ * @view: A EggRecentViewGtk object.
+ *
+ */
+GtkWidget *
+egg_recent_view_gtk_get_menu (EggRecentViewGtk *view)
+{
+ return view->menu;
+}
+
+/**
+ * egg_recent_view_gtk_get_start_menu_item
+ * @view: A EggRecentViewGtk object.
+ *
+ */
+GtkWidget *
+egg_recent_view_gtk_get_start_menu_item (EggRecentViewGtk *view)
+{
+ return view->start_menu_item;
+}
+
+
+/**
+ * egg_recent_view_gtk_new:
+ * @appname: The name of your application.
+ * @limit: The maximum number of items allowed.
+ *
+ * This creates a new EggRecentViewGtk object.
+ *
+ * Returns: a EggRecentViewGtk object
+ */
+EggRecentViewGtk *
+egg_recent_view_gtk_new (GtkWidget *menu, GtkWidget *start_menu_item)
+{
+ EggRecentViewGtk *view;
+
+ g_return_val_if_fail (menu, NULL);
+
+ view = EGG_RECENT_VIEW_GTK (g_object_new (egg_recent_view_gtk_get_type (),
+ "start-menu-item",
+ start_menu_item,
+ "menu", menu,
+ "show-numbers", TRUE, NULL));
+
+ g_return_val_if_fail (view, NULL);
+
+ return view;
+}
+
+/**
+ * egg_recent_view_gtk_get_type:
+ * @:
+ *
+ * This returns a GType representing a EggRecentViewGtk object.
+ *
+ * Returns: a GType
+ */
+GType
+egg_recent_view_gtk_get_type (void)
+{
+ static GType egg_recent_view_gtk_type = 0;
+
+ if(!egg_recent_view_gtk_type) {
+ static const GTypeInfo egg_recent_view_gtk_info = {
+ sizeof (EggRecentViewGtkClass),
+ NULL, /* base init */
+ NULL, /* base finalize */
+ (GClassInitFunc)egg_recent_view_gtk_class_init, /* class init */
+ NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof (EggRecentViewGtk),
+ 0,
+ (GInstanceInitFunc) egg_recent_view_gtk_init
+ };
+
+ static const GInterfaceInfo view_info =
+ {
+ (GInterfaceInitFunc) egg_recent_view_init,
+ NULL,
+ NULL
+ };
+
+ egg_recent_view_gtk_type = g_type_register_static (G_TYPE_OBJECT,
+ "EggRecentViewGtk",
+ &egg_recent_view_gtk_info, 0);
+ g_type_add_interface_static (egg_recent_view_gtk_type,
+ EGG_TYPE_RECENT_VIEW,
+ &view_info);
+ }
+
+ return egg_recent_view_gtk_type;
+}
+
--- /dev/null
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+#ifndef __EGG_RECENT_VIEW_GTK_H__
+#define __EGG_RECENT_VIEW_GTK_H__
+
+G_BEGIN_DECLS
+
+#include <gtk/gtk.h>
+#include "egg-recent-item.h"
+
+#define EGG_RECENT_VIEW_GTK(obj) G_TYPE_CHECK_INSTANCE_CAST (obj, egg_recent_view_gtk_get_type (), EggRecentViewGtk)
+#define EGG_RECENT_VIEW_GTK_CLASS(klass) G_TYPE_CHECK_CLASS_CAST (klass, egg_recent_view_gtk_get_type (), EggRecentViewGtkClass)
+#define EGG_IS_RECENT_VIEW_GTK(obj) G_TYPE_CHECK_INSTANCE_TYPE (obj, egg_recent_view_gtk_get_type ())
+
+typedef void (*EggRecentViewGtkTooltipFunc) (GtkTooltips *tooltips,
+ GtkWidget *menu,
+ EggRecentItem *item,
+ gpointer user_data);
+
+typedef struct _EggRecentViewGtk EggRecentViewGtk;
+
+typedef struct _EggRecentViewGtkClass EggRecentViewGtkClass;
+
+struct _EggRecentViewGtkClass {
+ GObjectClass parent_class;
+
+ void (*activate) (EggRecentViewGtk *view, EggRecentItem *item);
+};
+
+GType egg_recent_view_gtk_get_type (void);
+
+EggRecentViewGtk * egg_recent_view_gtk_new (GtkWidget *menu,
+ GtkWidget *start_menu_item);
+
+void egg_recent_view_gtk_set_menu (EggRecentViewGtk *view,
+ GtkWidget *menu);
+GtkWidget * egg_recent_view_gtk_get_menu (EggRecentViewGtk *view);
+
+
+void egg_recent_view_gtk_set_start_menu_item (EggRecentViewGtk *view,
+ GtkWidget *menu_item);
+GtkWidget *egg_recent_view_gtk_get_start_menu_item (EggRecentViewGtk *view);
+
+void egg_recent_view_gtk_set_leading_sep (EggRecentViewGtk *view,
+ gboolean val);
+
+void egg_recent_view_gtk_set_trailing_sep (EggRecentViewGtk *view,
+ gboolean val);
+
+void egg_recent_view_gtk_show_icons (EggRecentViewGtk *view,
+ gboolean show);
+void egg_recent_view_gtk_show_numbers (EggRecentViewGtk *view,
+ gboolean show);
+
+void egg_recent_view_gtk_set_tooltip_func (EggRecentViewGtk *view,
+ EggRecentViewGtkTooltipFunc func,
+ gpointer user_data);
+
+void egg_recent_view_gtk_set_icon_size (EggRecentViewGtk *view,
+ GtkIconSize icon_size);
+GtkIconSize egg_recent_view_gtk_get_icon_size (EggRecentViewGtk *view);
+
+G_END_DECLS
+
+#endif /* __EGG_RECENT_VIEW_GTK_H__ */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ * James Willcox <jwillcox@cs.indiana.edu>
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include "egg-recent-view.h"
+
+
+GtkType
+egg_recent_view_get_type (void)
+{
+ static GtkType view_type = 0;
+
+ if (!view_type)
+ {
+ static const GTypeInfo view_info =
+ {
+ sizeof (EggRecentViewClass), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ };
+
+ view_type = g_type_register_static (G_TYPE_INTERFACE,
+ "EggRecentView",
+ &view_info, 0);
+ }
+
+ return view_type;
+}
+
+EggRecentModel *
+egg_recent_view_get_model (EggRecentView *view)
+{
+ g_return_val_if_fail (view, NULL);
+
+ return EGG_RECENT_VIEW_GET_CLASS (view)->do_get_model (view);
+}
+
+void
+egg_recent_view_set_model (EggRecentView *view, EggRecentModel *model)
+{
+ g_return_if_fail (view);
+ g_return_if_fail (model);
+
+ EGG_RECENT_VIEW_GET_CLASS (view)->do_set_model (view, model);
+}
--- /dev/null
+#ifndef __EGG_RECENT_VIEW_H__
+#define __EGG_RECENT_VIEW_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtkwidget.h>
+#include "egg-recent-model.h"
+#include "egg-recent-item.h"
+
+#define EGG_TYPE_RECENT_VIEW (egg_recent_view_get_type ())
+#define EGG_RECENT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_RECENT_VIEW, EggRecentView))
+#define EGG_RECENT_VIEW_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), EGG_TYPE_RECENT_VIEW, EggRecentViewClass))
+#define EGG_IS_RECENT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_RECENT_VIEW))
+#define EGG_IS_RECENT_VIEW_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), EGG_TYPE_RECENT_VIEW))
+#define EGG_RECENT_VIEW_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), EGG_TYPE_RECENT_VIEW, EggRecentViewClass))
+
+
+typedef struct _EggRecentView EggRecentView;
+typedef struct _EggRecentViewClass EggRecentViewClass;
+
+struct _EggRecentViewClass
+{
+ GTypeInterface base_iface;
+
+ /* vtable, not signals */
+ void (* do_set_model) (EggRecentView *view,
+ EggRecentModel *model);
+ EggRecentModel * (* do_get_model) (EggRecentView *view);
+};
+
+GtkType egg_recent_view_get_type (void) G_GNUC_CONST;
+void egg_recent_view_set_list (EggRecentView *view,
+ GSList *list);
+void egg_recent_view_clear (EggRecentView *view);
+EggRecentModel *egg_recent_view_get_model (EggRecentView *view);
+void egg_recent_view_set_model (EggRecentView *view,
+ EggRecentModel *model);
+
+
+#endif /* __EGG_RECENT_VIEW_H__ */
--- /dev/null
+#!/bin/sh
+
+function die() {
+ echo $*
+ exit 1
+}
+
+if test -z "$EGGDIR"; then
+ echo "Must set EGGDIR"
+ exit 1
+fi
+
+if test -z "$EGGFILES"; then
+ echo "Must set EGGFILES"
+ exit 1
+fi
+
+for FILE in $EGGFILES; do
+ if cmp -s $EGGDIR/$FILE $FILE; then
+ echo "File $FILE is unchanged"
+ else
+ cp $EGGDIR/$FILE $FILE || die "Could not move $EGGDIR/$FILE to $FILE"
+ echo "Updated $FILE"
+ fi
+done