From 69d92c409d333e5dd9cc82537c0834b201487c60 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Tue, 21 Dec 2004 17:41:03 +0000 Subject: Initial revision --- (limited to 'cut-n-paste/recent-files') diff --git a/cut-n-paste/recent-files/Makefile.am b/cut-n-paste/recent-files/Makefile.am new file mode 100644 index 0000000..c3b3dbd --- /dev/null +++ b/cut-n-paste/recent-files/Makefile.am @@ -0,0 +1,32 @@ +NULL = + +INCLUDES = \ + $(RECENT_FILES_CFLAGS) \ + $(NULL) + +# Uses the deprecated GnomeIconTheme +# $(EVINCE_DISABLE_DEPRECATED) + +EGG_FILES = \ + egg-recent-model.c \ + egg-recent-model.h \ + egg-recent-item.c \ + egg-recent-item.h \ + egg-recent-view.c \ + egg-recent-view.h \ + egg-recent-view-bonobo.c \ + egg-recent-view-bonobo.h \ + egg-recent-view-gtk.c \ + egg-recent-view-gtk.h \ + egg-recent-util.c \ + egg-recent-util.h + +noinst_LIBRARIES = librecent.a + +librecent_a_SOURCES = $(EGG_FILES) + +EXTRA_DIST = update-from-egg.sh + +EGGDIR=$(srcdir)/../../../libegg/libegg/recent-files +regenerate-built-sources: + EGGFILES="$(EGG_FILES)" EGGDIR="$(EGGDIR)" $(srcdir)/update-from-egg.sh diff --git a/cut-n-paste/recent-files/egg-recent-item.c b/cut-n-paste/recent-files/egg-recent-item.c new file mode 100644 index 0000000..9be7970 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-item.c @@ -0,0 +1,440 @@ +/* -*- 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 + */ + + +#include +#include +#include +#include +#include +#include "egg-recent-item.h" + + + +EggRecentItem * +egg_recent_item_new (void) +{ + EggRecentItem *item; + + item = g_new (EggRecentItem, 1); + + item->groups = NULL; + item->private_data = 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_data = item->private_data; + 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); +} + +/* Stolen from gnome_vfs_make_valid_utf8() */ +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 file name)")); */ + g_assert (g_utf8_validate (string->str, -1, NULL)); + + return g_string_free (string, FALSE); +} + +/** + * egg_recent_item_get_short_name: + * @item: an #EggRecentItem + * + * Computes a valid UTF-8 string that can be used as the name of the item in a + * menu or list. For example, calling this function on an item that refers to + * "file:///foo/bar.txt" will yield "bar.txt". + * + * Return value: A newly-allocated string in UTF-8 encoding; free it with + * g_free(). + **/ +gchar * +egg_recent_item_get_short_name (const EggRecentItem *item) +{ + GnomeVFSURI *uri; + char *short_name; + gboolean valid; + + g_return_val_if_fail (item != NULL, NULL); + + if (item->uri == NULL) + return NULL; + + uri = gnome_vfs_uri_new (item->uri); + if (uri == NULL) + return NULL; + + short_name = gnome_vfs_uri_extract_short_name (uri); + valid = FALSE; + + if (strcmp (gnome_vfs_uri_get_scheme (uri), "file") == 0) { + char *tmp; + + tmp = g_filename_to_utf8 (short_name, -1, NULL, NULL, NULL); + if (tmp) { + g_free (short_name); + short_name = tmp; + valid = TRUE; + } + } + + if (!valid) { + char *tmp; + + tmp = make_valid_utf8 (short_name); + g_assert (tmp != NULL); + g_free (short_name); + short_name = tmp; + } + + gnome_vfs_uri_unref (uri); + + return short_name; +} + +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_data = priv; +} + +gboolean +egg_recent_item_get_private (const EggRecentItem *item) +{ + return item->private_data; +} + +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; +} diff --git a/cut-n-paste/recent-files/egg-recent-item.h b/cut-n-paste/recent-files/egg-recent-item.h new file mode 100644 index 0000000..5b3b405 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-item.h @@ -0,0 +1,78 @@ + +#ifndef __EGG_RECENT_ITEM_H__ +#define __EGG_RECENT_ITEM_H__ + +#include +#include +#include + +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_data; + + 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); +gchar * egg_recent_item_get_short_name (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__ */ diff --git a/cut-n-paste/recent-files/egg-recent-model.c b/cut-n-paste/recent-files/egg-recent-model.c new file mode 100644 index 0000000..f36053b --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-model.c @@ -0,0 +1,1775 @@ +/* -*- 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 ("\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\n", escaped_uri); + + if (mime_type) + g_string_append_printf (string, + " <" TAG_MIME_TYPE ">%s\n", mime_type); + else + g_string_append_printf (string, + " <" TAG_MIME_TYPE ">\n"); + + + g_string_append_printf (string, + " <" TAG_TIMESTAMP ">%d\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\n", + escaped_group); + + g_free (escaped_group); + + groups = groups->next; + } + + string = g_string_append (string, " \n"); + + string = g_string_append (string, + " \n"); + + g_free (mime_type); + g_free (escaped_uri); + + list = list->next; + i++; + } + + string = g_string_append (string, ""); + + 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; + mode_t prev_umask; + + file = fopen (model->priv->path, "r+"); + if (file == NULL) { + /* be paranoid */ + prev_umask = umask (077); + + file = fopen (model->priv->path, "w+"); + + umask (prev_umask); + + g_return_val_if_fail (file != NULL, NULL); + } + + return file; +} + +static gboolean +egg_recent_model_lock_file (FILE *file) +{ + int fd; + gint try = 5; + + rewind (file); + fd = fileno (file); + + /* Attempt to lock the file 5 times, + * waiting a random interval (< 1 second) + * in between attempts. + * We should really be doing asynchronous + * locking, but requires substantially larger + * changes. + */ + + while (try > 0) + { + int rand_interval; + + if (lockf (fd, F_TLOCK, 0) == 0) + return TRUE; + + rand_interval = 1 + (int) (10.0 * rand()/(RAND_MAX + 1.0)); + + g_usleep (100000 * rand_interval); + + --try; + } + + return FALSE; +} + +static gboolean +egg_recent_model_unlock_file (FILE *file) +{ + int fd; + + rewind (file); + fd = fileno (file); + + return (lockf (fd, F_ULOCK, 0) == 0) ? TRUE : FALSE; +} + +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; +} + diff --git a/cut-n-paste/recent-files/egg-recent-model.h b/cut-n-paste/recent-files/egg-recent-model.h new file mode 100644 index 0000000..8c2eb8d --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-model.h @@ -0,0 +1,80 @@ +/* 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__ */ diff --git a/cut-n-paste/recent-files/egg-recent-util.c b/cut-n-paste/recent-files/egg-recent-util.c new file mode 100644 index 0000000..cb30e05 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-util.c @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include +#include +#include +#ifndef USE_STABLE_LIBGNOMEUI +#include +#include +#endif +#include +#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); +} + +#ifndef USE_STABLE_LIBGNOMEUI +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; +} + +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); + + if (filename == NULL) { + return NULL; + } + + 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); +} diff --git a/cut-n-paste/recent-files/egg-recent-util.h b/cut-n-paste/recent-files/egg-recent-util.h new file mode 100644 index 0000000..ae8a641 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-util.h @@ -0,0 +1,23 @@ + +#ifndef __EGG_RECENT_UTIL__ +#define __EGG_RECENT_UTIL__ + +#include +#ifndef USE_STABLE_LIBGNOMEUI +#include +#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__ */ diff --git a/cut-n-paste/recent-files/egg-recent-view-bonobo.c b/cut-n-paste/recent-files/egg-recent-view-bonobo.c new file mode 100644 index 0000000..e728e70 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-view-bonobo.c @@ -0,0 +1,714 @@ +/* -*- 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#ifndef USE_STABLE_LIBGNOMEUI +#include +#endif +#include +#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 ("", verb_name, pixbuf_xml); + + g_free (pixbuf_xml); + g_object_unref (pixbuf); + } else { + cmd = g_strdup_printf (" ", + verb_name); + } + + g_free (mime_type); + g_free (uri); + } else + cmd = g_strdup_printf (" ", + 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 ("", + 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; +} + diff --git a/cut-n-paste/recent-files/egg-recent-view-bonobo.h b/cut-n-paste/recent-files/egg-recent-view-bonobo.h new file mode 100644 index 0000000..0b283ef --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-view-bonobo.h @@ -0,0 +1,58 @@ +/* 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 + +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__ */ diff --git a/cut-n-paste/recent-files/egg-recent-view-gtk.c b/cut-n-paste/recent-files/egg-recent-view-gtk.c new file mode 100644 index 0000000..34805b2 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-view-gtk.c @@ -0,0 +1,817 @@ +/* -*- 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#ifndef USE_STABLE_LIBGNOMEUI +#include +#endif +#include +#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_separator (EggRecentViewGtk *view) +{ + GtkWidget *retval; + + g_return_val_if_fail (view, NULL); + + retval = gtk_separator_menu_item_new (); + + /** + * 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 (retval), + view->uid, + GINT_TO_POINTER (1)); + + + gtk_widget_show (retval); + + return retval; +} + +static GtkWidget * +egg_recent_view_gtk_new_menu_item (EggRecentViewGtk *view, + EggRecentItem *item, + gint index) +{ + GtkWidget *menu_item; + EggRecentViewGtkMenuData *md; + gchar *mime_type; + GtkWidget *image; + GdkPixbuf *pixbuf; + gchar *text; + gchar *short_name; + gchar *escaped; + + g_return_val_if_fail (view, NULL); + g_return_val_if_fail (item, NULL); + + short_name = egg_recent_item_get_short_name (item); + if (!short_name) + return NULL; + + escaped = egg_recent_util_escape_underlines (short_name); + g_free (short_name); + + 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; + gchar *uri; + + gtk_icon_size_lookup_for_settings + (gtk_widget_get_settings (view->menu), + view->icon_size, + &width, &height); + + uri = egg_recent_item_get_uri (item); + pixbuf = egg_recent_util_get_icon (view->theme, uri, + mime_type, + height); + g_free (uri); + } +#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 = g_new0 (EggRecentViewGtkMenuData, 1); + md->view = view; + md->item = egg_recent_item_ref (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); + + /** + * 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); + + if (item != NULL) + menu_item = egg_recent_view_gtk_new_menu_item (view, item, display); + else + menu_item = egg_recent_view_gtk_new_separator (view); + + 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; +} + diff --git a/cut-n-paste/recent-files/egg-recent-view-gtk.h b/cut-n-paste/recent-files/egg-recent-view-gtk.h new file mode 100644 index 0000000..7e56baf --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-view-gtk.h @@ -0,0 +1,64 @@ +/* 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 +#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__ */ diff --git a/cut-n-paste/recent-files/egg-recent-view.c b/cut-n-paste/recent-files/egg-recent-view.c new file mode 100644 index 0000000..0aa38b9 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-view.c @@ -0,0 +1,68 @@ +/* -*- 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 + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#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); +} diff --git a/cut-n-paste/recent-files/egg-recent-view.h b/cut-n-paste/recent-files/egg-recent-view.h new file mode 100644 index 0000000..79f0346 --- /dev/null +++ b/cut-n-paste/recent-files/egg-recent-view.h @@ -0,0 +1,43 @@ +#ifndef __EGG_RECENT_VIEW_H__ +#define __EGG_RECENT_VIEW_H__ + + +#include +#include +#include "egg-recent-model.h" +#include "egg-recent-item.h" + +G_BEGIN_DECLS + +#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); + +G_END_DECLS + +#endif /* __EGG_RECENT_VIEW_H__ */ diff --git a/cut-n-paste/recent-files/update-from-egg.sh b/cut-n-paste/recent-files/update-from-egg.sh new file mode 100755 index 0000000..9be68a9 --- /dev/null +++ b/cut-n-paste/recent-files/update-from-egg.sh @@ -0,0 +1,25 @@ +#!/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 -- cgit v0.9.1