diff options
Diffstat (limited to 'cut-n-paste-code/libegg')
-rw-r--r-- | cut-n-paste-code/libegg/Makefile.am | 47 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-recent-item.c | 345 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-recent-item.h | 77 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-recent-model.c | 1740 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-recent-model.h | 80 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-recent-vfs-utils.c | 570 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-recent-vfs-utils.h | 42 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-screen-exec.c | 260 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-screen-exec.h | 46 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-screen-help.c | 163 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-screen-help.h | 56 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-screen-url.c | 59 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/egg-screen-url.h | 36 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/eggtreemultidnd.c | 412 | ||||
-rw-r--r-- | cut-n-paste-code/libegg/eggtreemultidnd.h | 76 | ||||
-rwxr-xr-x | cut-n-paste-code/libegg/update-from-egg.sh | 25 |
16 files changed, 4034 insertions, 0 deletions
diff --git a/cut-n-paste-code/libegg/Makefile.am b/cut-n-paste-code/libegg/Makefile.am new file mode 100644 index 000000000..5a357c363 --- /dev/null +++ b/cut-n-paste-code/libegg/Makefile.am @@ -0,0 +1,47 @@ +NULL= + +noinst_LTLIBRARIES = libegg.la + +INCLUDES = $(LIBEGG_CFLAGS) + +EGG_SCREEN_FILES = \ + egg-screen-exec.h \ + egg-screen-exec.c \ + egg-screen-url.h \ + egg-screen-url.c \ + egg-screen-help.h \ + egg-screen-help.c \ + $(NULL) + +EGG_RECENT_FILES = \ + egg-recent-model.c \ + egg-recent-model.h \ + egg-recent-item.c \ + egg-recent-item.h \ + egg-recent-vfs-utils.c \ + egg-recent-vfs-utils.h \ + $(NULL) + +EGG_TREE_DND_FILES = \ + eggtreemultidnd.c \ + eggtreemultidnd.h \ + $(NULL) + +libegg_la_SOURCES = \ + $(EGG_SCREEN_FILES) \ + $(EGG_RECENT_FILES) \ + $(EGG_TREE_DND_FILES) \ + $(NULL) + +EXTRA_DIST = \ + update-from-egg.sh \ + $(NULL) + +EGG_SCREEN_DIR = $(srcdir)/../../../libegg/libegg/screen-exec +EGG_RECENT_DIR = $(srcdir)/../../../libegg/libegg/recent-files +EGG_TREE_DND_DIR = $(srcdir)/../../../libegg/libegg/treeviewutils + +regenerate-built-sources: + EGGFILES="$(EGG_SCREEN_FILES)" EGGDIR="$(EGG_SCREEN_DIR)" $(srcdir)/update-from-egg.sh + EGGFILES="$(EGG_RECENT_FILES)" EGGDIR="$(EGG_RECENT_DIR)" $(srcdir)/update-from-egg.sh + EGGFILES="$(EGG_TREE_DND_FILES)" EGGDIR="$(EGG_TREE_DND_DIR)" $(srcdir)/update-from-egg.sh diff --git a/cut-n-paste-code/libegg/egg-recent-item.c b/cut-n-paste-code/libegg/egg-recent-item.c new file mode 100644 index 000000000..4b42aca75 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-recent-item.c @@ -0,0 +1,345 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: + * James Willcox <jwillcox@cs.indiana.edu> + */ + + +#include <stdio.h> +#include <string.h> +#include <glib.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> +#include "egg-recent-item.h" +#include "egg-recent-vfs-utils.h" + + + +EggRecentItem * +egg_recent_item_new (void) +{ + EggRecentItem *item; + + item = g_new (EggRecentItem, 1); + + item->groups = NULL; + item->private = FALSE; + item->uri = NULL; + item->mime_type = NULL; + + item->refcount = 1; + + return item; +} + +static void +egg_recent_item_free (EggRecentItem *item) +{ + if (item->uri) + g_free (item->uri); + + if (item->mime_type) + g_free (item->mime_type); + + if (item->groups) { + g_list_foreach (item->groups, (GFunc)g_free, NULL); + g_list_free (item->groups); + item->groups = NULL; + } + + g_free (item); +} + +void +egg_recent_item_ref (EggRecentItem *item) +{ + item->refcount++; +} + +void +egg_recent_item_unref (EggRecentItem *item) +{ + item->refcount--; + + if (item->refcount == 0) { + egg_recent_item_free (item); + } +} + + +EggRecentItem * +egg_recent_item_new_from_uri (const gchar *uri) +{ + EggRecentItem *item; + + g_return_val_if_fail (uri != NULL, NULL); + + item = egg_recent_item_new (); + + if (!egg_recent_item_set_uri (item ,uri)) { + egg_recent_item_free (item); + return NULL; + } + + item->mime_type = gnome_vfs_get_mime_type (item->uri); + + if (!item->mime_type) + item->mime_type = g_strdup (GNOME_VFS_MIME_TYPE_UNKNOWN); + + return item; +} + +/* +static GList * +egg_recent_item_copy_groups (const GList *list) +{ + GList *newlist = NULL; + + while (list) { + gchar *group = (gchar *)list->data; + + newlist = g_list_prepend (newlist, g_strdup (group)); + + list = list->next; + } + + return newlist; +} + + +EggRecentItem * +egg_recent_item_copy (const EggRecentItem *item) +{ + EggRecentItem *newitem; + + newitem = egg_recent_item_new (); + newitem->uri = g_strdup (item->uri); + if (item->mime_type) + newitem->mime_type = g_strdup (item->mime_type); + newitem->timestamp = item->timestamp; + newitem->private = item->private; + newitem->groups = egg_recent_item_copy_groups (item->groups); + + return newitem; +} +*/ + +/* +EggRecentItem * +egg_recent_item_new_valist (const gchar *uri, va_list args) +{ + EggRecentItem *item; + EggRecentArg arg; + gchar *str1; + gchar *str2; + gboolean priv; + + item = egg_recent_item_new (); + + arg = va_arg (args, EggRecentArg); + + while (arg != EGG_RECENT_ARG_NONE) { + switch (arg) { + case EGG_RECENT_ARG_MIME_TYPE: + str1 = va_arg (args, gchar*); + + egg_recent_item_set_mime_type (item, str1); + break; + case EGG_RECENT_ARG_GROUP: + str1 = va_arg (args, gchar*); + + egg_recent_item_add_group (item, str1); + break; + case EGG_RECENT_ARG_PRIVATE: + priv = va_arg (args, gboolean); + + egg_recent_item_set_private (item, priv); + break; + default: + break; + } + + arg = va_arg (args, EggRecentArg); + } + + return item; +} +*/ + +gboolean +egg_recent_item_set_uri (EggRecentItem *item, const gchar *uri) +{ + gchar *utf8_uri; + + /* if G_BROKEN_FILENAMES is not set, this should succede */ + if (g_utf8_validate (uri, -1, NULL)) { + item->uri = egg_recent_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 = egg_recent_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 egg_recent_vfs_format_uri_for_display (item->uri); +} + +void +egg_recent_item_set_mime_type (EggRecentItem *item, const gchar *mime) +{ + item->mime_type = g_strdup (mime); +} + +gchar * +egg_recent_item_get_mime_type (const EggRecentItem *item) +{ + return g_strdup (item->mime_type); +} + +void +egg_recent_item_set_timestamp (EggRecentItem *item, time_t timestamp) +{ + if (timestamp == (time_t) -1) + time (×tamp); + + item->timestamp = timestamp; +} + +time_t +egg_recent_item_get_timestamp (const EggRecentItem *item) +{ + return item->timestamp; +} + +G_CONST_RETURN GList * +egg_recent_item_get_groups (const EggRecentItem *item) +{ + return item->groups; +} + +gboolean +egg_recent_item_in_group (const EggRecentItem *item, const gchar *group_name) +{ + GList *tmp; + + tmp = item->groups; + while (tmp != NULL) { + gchar *val = (gchar *)tmp->data; + + if (strcmp (group_name, val) == 0) + return TRUE; + + tmp = tmp->next; + } + + return FALSE; +} + +void +egg_recent_item_add_group (EggRecentItem *item, const gchar *group_name) +{ + g_return_if_fail (group_name != NULL); + + if (!egg_recent_item_in_group (item, group_name)) + item->groups = g_list_append (item->groups, g_strdup (group_name)); +} + +void +egg_recent_item_remove_group (EggRecentItem *item, const gchar *group_name) +{ + GList *tmp; + + g_return_if_fail (group_name != NULL); + + tmp = item->groups; + while (tmp != NULL) { + gchar *val = (gchar *)tmp->data; + + if (strcmp (group_name, val) == 0) { + item->groups = g_list_remove (item->groups, + val); + g_free (val); + break; + } + + tmp = tmp->next; + } +} + +void +egg_recent_item_set_private (EggRecentItem *item, gboolean priv) +{ + item->private = priv; +} + +gboolean +egg_recent_item_get_private (const EggRecentItem *item) +{ + return item->private; +} + +GType +egg_recent_item_get_type (void) +{ + static GType boxed_type = 0; + + if (!boxed_type) { + boxed_type = g_boxed_type_register_static ("EggRecentItem", + (GBoxedCopyFunc)egg_recent_item_ref, + (GBoxedFreeFunc)egg_recent_item_unref); + } + + return boxed_type; +} diff --git a/cut-n-paste-code/libegg/egg-recent-item.h b/cut-n-paste-code/libegg/egg-recent-item.h new file mode 100644 index 000000000..74cb097a3 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-recent-item.h @@ -0,0 +1,77 @@ + +#ifndef __EGG_RECENT_ITEM_H__ +#define __EGG_RECENT_ITEM_H__ + +#include <time.h> +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_RECENT_ITEM (egg_recent_item_get_type ()) + +#define EGG_RECENT_ITEM_LIST_UNREF(list) \ + g_list_foreach (list, (GFunc)egg_recent_item_unref, NULL); \ + g_list_free (list); + +typedef struct _EggRecentItem EggRecentItem; + +struct _EggRecentItem { + /* do not access any of these directly */ + gchar *uri; + gchar *mime_type; + time_t timestamp; + + gboolean private; + + GList *groups; + + int refcount; +}; + +GType egg_recent_item_get_type (void) G_GNUC_CONST; + +/* constructors */ +EggRecentItem * egg_recent_item_new (void); + +void egg_recent_item_ref (EggRecentItem *item); +void egg_recent_item_unref (EggRecentItem *item); + +/* automatically fetches the mime type, etc */ +EggRecentItem * egg_recent_item_new_from_uri (const gchar *uri); + +gboolean egg_recent_item_set_uri (EggRecentItem *item, const gchar *uri); +gchar * egg_recent_item_get_uri (const EggRecentItem *item); +gchar * egg_recent_item_get_uri_utf8 (const EggRecentItem *item); +gchar * egg_recent_item_get_uri_for_display (const EggRecentItem *item); + +void egg_recent_item_set_mime_type (EggRecentItem *item, const gchar *mime); +gchar * egg_recent_item_get_mime_type (const EggRecentItem *item); + +void egg_recent_item_set_timestamp (EggRecentItem *item, time_t timestamp); +time_t egg_recent_item_get_timestamp (const EggRecentItem *item); + +G_CONST_RETURN gchar *egg_recent_item_peek_uri (const EggRecentItem *item); + + +/* groups */ +G_CONST_RETURN GList * egg_recent_item_get_groups (const EggRecentItem *item); + +gboolean egg_recent_item_in_group (const EggRecentItem *item, + const gchar *group_name); + +void egg_recent_item_add_group (EggRecentItem *item, + const gchar *group_name); + +void egg_recent_item_remove_group (EggRecentItem *item, + const gchar *group_name); + +void egg_recent_item_set_private (EggRecentItem *item, + gboolean priv); + +gboolean egg_recent_item_get_private (const EggRecentItem *item); + + +G_END_DECLS + +#endif /* __EGG_RECENT_ITEM_H__ */ diff --git a/cut-n-paste-code/libegg/egg-recent-model.c b/cut-n-paste-code/libegg/egg-recent-model.c new file mode 100644 index 000000000..2f6ee5498 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-recent-model.c @@ -0,0 +1,1740 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: + * James Willcox <jwillcox@cs.indiana.edu> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/time.h> +#include <time.h> +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> +#include <gconf/gconf-client.h> +#include "egg-recent-model.h" +#include "egg-recent-item.h" +#include "egg-recent-vfs-utils.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 (egg_recent_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 (""); + 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 = egg_recent_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; +} + + + +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); + } +} + + + +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) + return NULL; + + parse_info_init (&info); + + ctx = g_markup_parse_context_new (&parser, 0, &info, NULL); + + error = NULL; + if (!g_markup_parse_context_parse (ctx, content, strlen (content), + &error)) { + g_warning (error->message); + g_error_free (error); + error = NULL; + goto out; + } + + error = NULL; + if (!g_markup_parse_context_end_parse (ctx, &error)) + goto out; + + g_markup_parse_context_free (ctx); +out: + list = info.items; + + parse_info_free (&info); + + g_free (content); + + /* + g_print ("Total items: %d\n", g_list_length (list)); + */ + + return list; +} + + +static gboolean +egg_recent_model_write (EggRecentModel *model, FILE *file, GList *list) +{ + GString *string; + gchar *data; + EggRecentItem *item; + const GList *groups; + int i; + int ret; + + string = g_string_new ("<?xml version=\"1.0\"?>\n"); + string = g_string_append (string, "<" TAG_RECENT_FILES ">\n"); + + i=0; + while (list) { + gchar *uri; + gchar *mime_type; + gchar *escaped_uri; + time_t timestamp; + item = (EggRecentItem *)list->data; + + + uri = egg_recent_item_get_uri_utf8 (item); + escaped_uri = g_markup_escape_text (uri, + strlen (uri)); + g_free (uri); + + mime_type = egg_recent_item_get_mime_type (item); + timestamp = egg_recent_item_get_timestamp (item); + + string = g_string_append (string, " <" TAG_RECENT_ITEM ">\n"); + + g_string_append_printf (string, + " <" TAG_URI ">%s</" TAG_URI ">\n", escaped_uri); + + if (mime_type) + g_string_append_printf (string, + " <" TAG_MIME_TYPE ">%s</" TAG_MIME_TYPE ">\n", mime_type); + else + g_string_append_printf (string, + " <" TAG_MIME_TYPE "></" TAG_MIME_TYPE ">\n"); + + + g_string_append_printf (string, + " <" TAG_TIMESTAMP ">%d</" TAG_TIMESTAMP ">\n", (int)timestamp); + + if (egg_recent_item_get_private (item)) + string = g_string_append (string, + " <" TAG_PRIVATE "/>\n"); + + /* write the groups */ + string = g_string_append (string, + " <" TAG_GROUPS ">\n"); + groups = egg_recent_item_get_groups (item); + + if (groups == NULL && egg_recent_item_get_private (item)) + g_warning ("Item with URI \"%s\" marked as private, but" + " does not belong to any groups.\n", uri); + + while (groups) { + const gchar *group = (const gchar *)groups->data; + gchar *escaped_group; + + escaped_group = g_markup_escape_text (group, strlen(group)); + + g_string_append_printf (string, + " <" TAG_GROUP ">%s</" TAG_GROUP ">\n", + escaped_group); + + g_free (escaped_group); + + groups = groups->next; + } + + string = g_string_append (string, " </" TAG_GROUPS ">\n"); + + string = g_string_append (string, + " </" TAG_RECENT_ITEM ">\n"); + + g_free (mime_type); + g_free (escaped_uri); + + list = list->next; + i++; + } + + string = g_string_append (string, "</" TAG_RECENT_FILES ">"); + + data = g_string_free (string, FALSE); + + ret = egg_recent_model_write_raw (model, file, data); + + g_free (data); + + return ret; +} + +static FILE * +egg_recent_model_open_file (EggRecentModel *model) +{ + FILE *file; + + file = fopen (model->priv->path, "r+"); + if (file == NULL) { + /* be paranoid */ + umask (077); + + file = fopen (model->priv->path, "w+"); + + g_return_val_if_fail (file != NULL, NULL); + } + + return file; +} + +static gboolean +egg_recent_model_lock_file (FILE *file) +{ + int fd; + + rewind (file); + fd = fileno (file); + + return lockf (fd, F_LOCK, 0) == 0 ? TRUE : FALSE; +} + +static gboolean +egg_recent_model_unlock_file (FILE *file) +{ + int fd; + + rewind (file); + fd = fileno (file); + + return lockf (fd, F_ULOCK, 0) < 0 ? FALSE : TRUE; +} + +static void +egg_recent_model_finalize (GObject *object) +{ + EggRecentModel *model = EGG_RECENT_MODEL (object); + + egg_recent_model_monitor (model, FALSE); + + + g_slist_foreach (model->priv->mime_filter_values, + (GFunc) g_pattern_spec_free, NULL); + g_slist_free (model->priv->mime_filter_values); + model->priv->mime_filter_values = NULL; + + g_slist_foreach (model->priv->scheme_filter_values, + (GFunc) g_pattern_spec_free, NULL); + g_slist_free (model->priv->scheme_filter_values); + model->priv->scheme_filter_values = NULL; + + g_slist_foreach (model->priv->group_filter_values, + (GFunc) g_free, NULL); + g_slist_free (model->priv->group_filter_values); + model->priv->group_filter_values = NULL; + + + if (model->priv->limit_change_notify_id) + gconf_client_notify_remove (model->priv->client, + model->priv->limit_change_notify_id); + model->priv->expiration_change_notify_id = 0; + + if (model->priv->expiration_change_notify_id) + gconf_client_notify_remove (model->priv->client, + model->priv->expiration_change_notify_id); + model->priv->expiration_change_notify_id = 0; + + g_object_unref (model->priv->client); + model->priv->client = NULL; + + + g_free (model->priv->path); + model->priv->path = NULL; + + g_hash_table_destroy (model->priv->monitors); + model->priv->monitors = NULL; + + + g_free (model->priv); +} + +static void +egg_recent_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggRecentModel *model = EGG_RECENT_MODEL (object); + + switch (prop_id) + { + case PROP_MIME_FILTERS: + model->priv->mime_filter_values = + (GSList *)g_value_get_pointer (value); + break; + + case PROP_GROUP_FILTERS: + model->priv->group_filter_values = + (GSList *)g_value_get_pointer (value); + break; + + case PROP_SCHEME_FILTERS: + model->priv->scheme_filter_values = + (GSList *)g_value_get_pointer (value); + break; + + case PROP_SORT_TYPE: + model->priv->sort_type = g_value_get_int (value); + break; + + case PROP_LIMIT: + egg_recent_model_set_limit (model, + g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_recent_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggRecentModel *model = EGG_RECENT_MODEL (object); + + switch (prop_id) + { + case PROP_MIME_FILTERS: + g_value_set_pointer (value, model->priv->mime_filter_values); + break; + + case PROP_GROUP_FILTERS: + g_value_set_pointer (value, model->priv->group_filter_values); + break; + + case PROP_SCHEME_FILTERS: + g_value_set_pointer (value, model->priv->scheme_filter_values); + break; + + case PROP_SORT_TYPE: + g_value_set_int (value, model->priv->sort_type); + break; + + case PROP_LIMIT: + g_value_set_int (value, model->priv->limit); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_recent_model_class_init (EggRecentModelClass * klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->set_property = egg_recent_model_set_property; + object_class->get_property = egg_recent_model_get_property; + object_class->finalize = egg_recent_model_finalize; + + model_signals[CHANGED] = g_signal_new ("changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggRecentModelClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + + g_object_class_install_property (object_class, + PROP_MIME_FILTERS, + g_param_spec_pointer ("mime-filters", + "Mime Filters", + "List of mime types to be allowed.", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_GROUP_FILTERS, + g_param_spec_pointer ("group-filters", + "Group Filters", + "List of groups to be allowed.", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SCHEME_FILTERS, + g_param_spec_pointer ("scheme-filters", + "Scheme Filters", + "List of URI schemes to be allowed.", + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SORT_TYPE, + g_param_spec_int ("sort-type", + "Sort Type", + "Type of sorting to be done.", + 0, EGG_RECENT_MODEL_SORT_NONE, + EGG_RECENT_MODEL_SORT_MRU, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_LIMIT, + g_param_spec_int ("limit", + "Limit", + "Max number of items allowed.", + -1, EGG_RECENT_MODEL_MAX_ITEMS, + EGG_RECENT_MODEL_DEFAULT_LIMIT, + G_PARAM_READWRITE)); + + klass->changed = NULL; +} + + + +static void +egg_recent_model_limit_changed (GConfClient *client, guint cnxn_id, + GConfEntry *entry, gpointer user_data) +{ + EggRecentModel *model; + GConfValue *value; + + model = EGG_RECENT_MODEL (user_data); + + g_return_if_fail (model != NULL); + + if (model->priv->use_default_limit == FALSE) + return; /* ignore this key */ + + /* the key was unset, and the schema has apparently failed */ + if (entry == NULL) + return; + + value = gconf_entry_get_value (entry); + + if (value->type != GCONF_VALUE_INT) { + g_warning ("Expected GConfValue of type integer, " + "got something else"); + } + + + egg_recent_model_set_limit_internal (model, gconf_value_get_int (value)); +} + +static void +egg_recent_model_expiration_changed (GConfClient *client, guint cnxn_id, + GConfEntry *entry, gpointer user_data) +{ + +} + +static void +egg_recent_model_init (EggRecentModel * model) +{ + if (!gnome_vfs_init ()) { + g_warning ("gnome-vfs initialization failed."); + return; + } + + + model->priv = g_new0 (EggRecentModelPrivate, 1); + + model->priv->path = g_strdup_printf ("%s" EGG_RECENT_MODEL_FILE_PATH, + g_get_home_dir ()); + + model->priv->mime_filter_values = NULL; + model->priv->group_filter_values = NULL; + model->priv->scheme_filter_values = NULL; + + model->priv->client = gconf_client_get_default (); + gconf_client_add_dir (model->priv->client, EGG_RECENT_MODEL_KEY_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + + model->priv->limit_change_notify_id = + gconf_client_notify_add (model->priv->client, + EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, + egg_recent_model_limit_changed, + model, NULL, NULL); + + model->priv->expiration_change_notify_id = + gconf_client_notify_add (model->priv->client, + EGG_RECENT_MODEL_EXPIRE_KEY, + egg_recent_model_expiration_changed, + model, NULL, NULL); + + model->priv->expire_days = gconf_client_get_int ( + model->priv->client, + EGG_RECENT_MODEL_EXPIRE_KEY, + NULL); + +#if 0 + /* keep this out, for now */ + model->priv->limit = gconf_client_get_int ( + model->priv->client, + EGG_RECENT_MODEL_DEFAULT_LIMIT_KEY, NULL); + model->priv->use_default_limit = TRUE; +#endif + model->priv->limit = EGG_RECENT_MODEL_DEFAULT_LIMIT; + model->priv->use_default_limit = FALSE; + + model->priv->monitors = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) gnome_vfs_monitor_cancel); + + model->priv->monitor = NULL; + egg_recent_model_monitor (model, TRUE); +} + + +/** + * egg_recent_model_new: + * @sort: the type of sorting to use + * @limit: maximum number of items in the list + * + * This creates a new EggRecentModel object. + * + * Returns: a EggRecentModel object + */ +EggRecentModel * +egg_recent_model_new (EggRecentModelSort sort) +{ + EggRecentModel *model; + + model = EGG_RECENT_MODEL (g_object_new (egg_recent_model_get_type (), + "sort-type", sort, NULL)); + + g_return_val_if_fail (model, NULL); + + return model; +} + +/** + * egg_recent_model_add_full: + * @model: A EggRecentModel object. + * @item: A EggRecentItem + * + * This function adds an item to the list of recently used URIs. + * + * Returns: gboolean + */ +gboolean +egg_recent_model_add_full (EggRecentModel * model, EggRecentItem *item) +{ + FILE *file; + GList *list = NULL; + gboolean ret = FALSE; + gboolean updated = FALSE; + time_t t; + gchar *uri; + + g_return_val_if_fail (model != NULL, FALSE); + g_return_val_if_fail (EGG_IS_RECENT_MODEL (model), FALSE); + + file = egg_recent_model_open_file (model); + g_return_val_if_fail (file != NULL, FALSE); + + time (&t); + egg_recent_item_set_timestamp (item, t); + + uri = egg_recent_item_get_uri (item); + + 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, FALSE); + + if (egg_recent_model_lock_file (file)) { + list = egg_recent_model_read (model, file); + + } 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)); + + 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-code/libegg/egg-recent-model.h b/cut-n-paste-code/libegg/egg-recent-model.h new file mode 100644 index 000000000..8c2eb8da1 --- /dev/null +++ b/cut-n-paste-code/libegg/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-code/libegg/egg-recent-vfs-utils.c b/cut-n-paste-code/libegg/egg-recent-vfs-utils.c new file mode 100644 index 000000000..51083dd96 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-recent-vfs-utils.c @@ -0,0 +1,570 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* gnome-vfs-utils.c - Utility gnome-vfs methods. Will use gnome-vfs + HEAD in time. + + Copyright (C) 1999 Free Software Foundation + Copyright (C) 2000, 2001 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Ettore Perazzoli <ettore@comm2000.it> + John Sullivan <sullivan@eazel.com> + Darin Adler <darin@eazel.com> +*/ + +#include <config.h> + +#include "egg-recent-vfs-utils.h" + +#include <libgnomevfs/gnome-vfs-utils.h> +#include <string.h> +#include <stdlib.h> + +#ifdef ENABLE_NLS +#include <glib.h> + +#include <libintl.h> +#define _(String) gettext(String) + +#ifdef gettext_noop +#define N_(String) gettext_noop(String) +#else +#define N_(String) (String) +#endif +#else /* NLS is disabled */ +#define _(String) (String) +#define N_(String) (String) +#define textdomain(String) (String) +#define gettext(String) (String) +#define dgettext(Domain,String) (String) +#define dcgettext(Domain,String,Type) (String) +#define bindtextdomain(Domain,Directory) (Domain) +#endif + +static char * +make_valid_utf8 (const char *name) +{ + GString *string; + const char *remainder, *invalid; + int remaining_bytes, valid_bytes; + + string = NULL; + remainder = name; + remaining_bytes = strlen (name); + + while (remaining_bytes != 0) { + if (g_utf8_validate (remainder, remaining_bytes, &invalid)) { + break; + } + valid_bytes = invalid - remainder; + + if (string == NULL) { + string = g_string_sized_new (remaining_bytes); + } + g_string_append_len (string, remainder, valid_bytes); + g_string_append_c (string, '?'); + + remaining_bytes -= valid_bytes + 1; + remainder = invalid + 1; + } + + if (string == NULL) { + return g_strdup (name); + } + + g_string_append (string, remainder); + g_string_append (string, _(" (invalid Unicode)")); + g_assert (g_utf8_validate (string->str, -1, NULL)); + + return g_string_free (string, FALSE); +} + +static gboolean +istr_has_prefix (const char *haystack, const char *needle) +{ + const char *h, *n; + char hc, nc; + + /* Eat one character at a time. */ + h = haystack == NULL ? "" : haystack; + n = needle == NULL ? "" : needle; + do { + if (*n == '\0') { + return TRUE; + } + if (*h == '\0') { + return FALSE; + } + hc = *h++; + nc = *n++; + hc = g_ascii_tolower (hc); + nc = g_ascii_tolower (nc); + } while (hc == nc); + return FALSE; +} + +static gboolean +str_has_prefix (const char *haystack, const char *needle) +{ + const char *h, *n; + + /* Eat one character at a time. */ + h = haystack == NULL ? "" : haystack; + n = needle == NULL ? "" : needle; + do { + if (*n == '\0') { + return TRUE; + } + if (*h == '\0') { + return FALSE; + } + } while (*h++ == *n++); + return FALSE; +} + +static gboolean +uri_is_local_scheme (const char *uri) +{ + gboolean is_local_scheme; + char *temp_scheme; + int i; + char *local_schemes[] = {"file:", "help:", "ghelp:", "gnome-help:", + "trash:", "man:", "info:", + "hardware:", "search:", "pipe:", + "gnome-trash:", NULL}; + + is_local_scheme = FALSE; + for (temp_scheme = *local_schemes, i = 0; temp_scheme != NULL; i++, temp_scheme = local_schemes[i]) { + is_local_scheme = istr_has_prefix (uri, temp_scheme); + if (is_local_scheme) { + break; + } + } + + return is_local_scheme; +} + +static char * +handle_trailing_slashes (const char *uri) +{ + char *temp, *uri_copy; + gboolean previous_char_is_column, previous_chars_are_slashes_without_column; + gboolean previous_chars_are_slashes_with_column; + gboolean is_local_scheme; + + g_assert (uri != NULL); + + uri_copy = g_strdup (uri); + if (strlen (uri_copy) <= 2) { + return uri_copy; + } + + is_local_scheme = uri_is_local_scheme (uri); + + previous_char_is_column = FALSE; + previous_chars_are_slashes_without_column = FALSE; + previous_chars_are_slashes_with_column = FALSE; + + /* remove multiple trailing slashes */ + for (temp = uri_copy; *temp != '\0'; temp++) { + if (*temp == '/' && !previous_char_is_column) { + previous_chars_are_slashes_without_column = TRUE; + } else if (*temp == '/' && previous_char_is_column) { + previous_chars_are_slashes_without_column = FALSE; + previous_char_is_column = TRUE; + previous_chars_are_slashes_with_column = TRUE; + } else { + previous_chars_are_slashes_without_column = FALSE; + previous_char_is_column = FALSE; + previous_chars_are_slashes_with_column = FALSE; + } + + if (*temp == ':') { + previous_char_is_column = TRUE; + } + } + + if (*temp == '\0' && previous_chars_are_slashes_without_column) { + if (is_local_scheme) { + /* go back till you remove them all. */ + for (temp--; *(temp) == '/'; temp--) { + *temp = '\0'; + } + } else { + /* go back till you remove them all but one. */ + for (temp--; *(temp - 1) == '/'; temp--) { + *temp = '\0'; + } + } + } + + if (*temp == '\0' && previous_chars_are_slashes_with_column) { + /* go back till you remove them all but three. */ + for (temp--; *(temp - 3) != ':' && *(temp - 2) != ':' && *(temp - 1) != ':'; temp--) { + *temp = '\0'; + } + } + + + return uri_copy; +} + +static char * +make_uri_canonical (const char *uri) +{ + char *canonical_uri, *old_uri, *p; + gboolean relative_uri; + + relative_uri = FALSE; + + if (uri == NULL) { + return NULL; + } + + /* FIXME bugzilla.eazel.com 648: + * This currently ignores the issue of two uris that are not identical but point + * to the same data except for the specific cases of trailing '/' characters, + * file:/ and file:///, and "lack of file:". + */ + + canonical_uri = handle_trailing_slashes (uri); + + /* Note: In some cases, a trailing slash means nothing, and can + * be considered equivalent to no trailing slash. But this is + * not true in every case; specifically not for web addresses passed + * to a web-browser. So we don't have the trailing-slash-equivalence + * logic here, but we do use that logic in EelDirectory where + * the rules are more strict. + */ + + /* Add file: if there is no scheme. */ + if (strchr (canonical_uri, ':') == NULL) { + old_uri = canonical_uri; + + if (old_uri[0] != '/') { + /* FIXME bugzilla.eazel.com 5069: + * bandaid alert. Is this really the right thing to do? + * + * We got what really is a relative path. We do a little bit of + * a stretch here and assume it was meant to be a cryptic absolute path, + * and convert it to one. Since we can't call gnome_vfs_uri_new and + * gnome_vfs_uri_to_string to do the right make-canonical conversion, + * we have to do it ourselves. + */ + relative_uri = TRUE; + canonical_uri = gnome_vfs_make_path_name_canonical (old_uri); + g_free (old_uri); + old_uri = canonical_uri; + canonical_uri = g_strconcat ("file:///", old_uri, NULL); + } else { + canonical_uri = g_strconcat ("file:", old_uri, NULL); + } + g_free (old_uri); + } + + /* Lower-case the scheme. */ + for (p = canonical_uri; *p != ':'; p++) { + g_assert (*p != '\0'); + *p = g_ascii_tolower (*p); + } + + if (!relative_uri) { + old_uri = canonical_uri; + canonical_uri = gnome_vfs_make_uri_canonical (canonical_uri); + if (canonical_uri != NULL) { + g_free (old_uri); + } else { + canonical_uri = old_uri; + } + } + + /* FIXME bugzilla.eazel.com 2802: + * Work around gnome-vfs's desire to convert file:foo into file://foo + * by converting to file:///foo here. When you remove this, check that + * typing "foo" into location bar does not crash and returns an error + * rather than displaying the contents of / + */ + if (str_has_prefix (canonical_uri, "file://") + && !str_has_prefix (canonical_uri, "file:///")) { + old_uri = canonical_uri; + canonical_uri = g_strconcat ("file:/", old_uri + 5, NULL); + g_free (old_uri); + } + + return canonical_uri; +} + +static char * +format_uri_for_display (const char *uri, gboolean filenames_are_locale_encoded) +{ + char *canonical_uri, *path, *utf8_path; + + g_return_val_if_fail (uri != NULL, g_strdup ("")); + + canonical_uri = make_uri_canonical (uri); + + /* If there's no fragment and it's a local path. */ + path = gnome_vfs_get_local_path_from_uri (canonical_uri); + + if (path != NULL) { + if (filenames_are_locale_encoded) { + utf8_path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL); + if (utf8_path) { + g_free (canonical_uri); + g_free (path); + return utf8_path; + } + } else if (g_utf8_validate (path, -1, NULL)) { + g_free (canonical_uri); + return path; + } + } + + if (canonical_uri && !g_utf8_validate (canonical_uri, -1, NULL)) { + utf8_path = make_valid_utf8 (canonical_uri); + g_free (canonical_uri); + canonical_uri = utf8_path; + } + + g_free (path); + return canonical_uri; +} + +char * +egg_recent_vfs_format_uri_for_display (const char *uri) +{ + static gboolean broken_filenames; + + broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL; + + return format_uri_for_display (uri, broken_filenames); +} + +static gboolean +is_valid_scheme_character (char c) +{ + return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.'; +} + +static gboolean +has_valid_scheme (const char *uri) +{ + const char *p; + + p = uri; + + if (!is_valid_scheme_character (*p)) { + return FALSE; + } + + do { + p++; + } while (is_valid_scheme_character (*p)); + + return *p == ':'; +} + +static char * +escape_high_chars (const guchar *string) +{ + char *result; + const guchar *scanner; + guchar *result_scanner; + int escape_count; + static const gchar hex[16] = "0123456789ABCDEF"; + +#define ACCEPTABLE(a) ((a)>=32 && (a)<128) + + escape_count = 0; + + if (string == NULL) { + return NULL; + } + + for (scanner = string; *scanner != '\0'; scanner++) { + if (!ACCEPTABLE(*scanner)) { + escape_count++; + } + } + + if (escape_count == 0) { + return g_strdup (string); + } + + /* allocate two extra characters for every character that + * needs escaping and space for a trailing zero + */ + result = g_malloc (scanner - string + escape_count * 2 + 1); + for (scanner = string, result_scanner = result; *scanner != '\0'; scanner++) { + if (!ACCEPTABLE(*scanner)) { + *result_scanner++ = '%'; + *result_scanner++ = hex[*scanner >> 4]; + *result_scanner++ = hex[*scanner & 15]; + + } else { + *result_scanner++ = *scanner; + } + } + + *result_scanner = '\0'; + + return result; +} + +static char * +make_uri_from_input_internal (const char *text, + gboolean filenames_are_locale_encoded, + gboolean strip_trailing_whitespace) +{ + char *stripped, *path, *uri, *locale_path, *filesystem_path, *escaped; + + g_return_val_if_fail (text != NULL, g_strdup ("")); + + /* Strip off leading whitespaces (since they can't be part of a valid + uri). Only strip off trailing whitespaces when requested since + they might be part of a valid uri. + */ + if (strip_trailing_whitespace) { + stripped = g_strstrip (g_strdup (text)); + } else { + stripped = g_strchug (g_strdup (text)); + } + + switch (stripped[0]) { + case '\0': + uri = g_strdup (""); + break; + case '/': + if (filenames_are_locale_encoded) { + GError *error = NULL; + locale_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, &error); + if (locale_path != NULL) { + uri = gnome_vfs_get_uri_from_local_path (locale_path); + g_free (locale_path); + } else { + /* We couldn't convert to the locale. */ + /* FIXME: We should probably give a user-visible error here. */ + uri = g_strdup(""); + } + } else { + uri = gnome_vfs_get_uri_from_local_path (stripped); + } + break; + case '~': + if (filenames_are_locale_encoded) { + filesystem_path = g_locale_from_utf8 (stripped, -1, NULL, NULL, NULL); + } else { + filesystem_path = g_strdup (stripped); + } + /* deliberately falling into default case on fail */ + if (filesystem_path != NULL) { + path = gnome_vfs_expand_initial_tilde (filesystem_path); + g_free (filesystem_path); + if (*path == '/') { + uri = gnome_vfs_get_uri_from_local_path (path); + g_free (path); + break; + } + g_free (path); + } + /* don't insert break here, read above comment */ + default: + if (has_valid_scheme (stripped)) { + uri = escape_high_chars (stripped); + } else { + escaped = escape_high_chars (stripped); + uri = g_strconcat ("http://", escaped, NULL); + g_free (escaped); + } + } + + g_free (stripped); + + return uri; + +} + +char * +egg_recent_vfs_make_uri_from_input (const char *uri) +{ + static gboolean broken_filenames; + + broken_filenames = g_getenv ("G_BROKEN_FILENAMES") != NULL; + + return make_uri_from_input_internal (uri, broken_filenames, TRUE); +} + +static char * +make_uri_canonical_strip_fragment (const char *uri) +{ + const char *fragment; + char *without_fragment, *canonical; + + fragment = strchr (uri, '#'); + if (fragment == NULL) { + return make_uri_canonical (uri); + } + + without_fragment = g_strndup (uri, fragment - uri); + canonical = make_uri_canonical (without_fragment); + g_free (without_fragment); + return canonical; +} + +static gboolean +uris_match (const char *uri_1, const char *uri_2, gboolean ignore_fragments) +{ + char *canonical_1, *canonical_2; + gboolean result; + + if (ignore_fragments) { + canonical_1 = make_uri_canonical_strip_fragment (uri_1); + canonical_2 = make_uri_canonical_strip_fragment (uri_2); + } else { + canonical_1 = make_uri_canonical (uri_1); + canonical_2 = make_uri_canonical (uri_2); + } + + result = strcmp (canonical_1, canonical_2) == 0; + + g_free (canonical_1); + g_free (canonical_2); + + return result; +} + +gboolean +egg_recent_vfs_uris_match (const char *uri_1, const char *uri_2) +{ + return uris_match (uri_1, uri_2, FALSE); +} + +char * +egg_recent_vfs_get_uri_scheme (const char *uri) +{ + char *colon; + + g_return_val_if_fail (uri != NULL, NULL); + + colon = strchr (uri, ':'); + + if (colon == NULL) { + return NULL; + } + + return g_strndup (uri, colon - uri); +} diff --git a/cut-n-paste-code/libegg/egg-recent-vfs-utils.h b/cut-n-paste-code/libegg/egg-recent-vfs-utils.h new file mode 100644 index 000000000..619b25b49 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-recent-vfs-utils.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* gnome-vfs-utils.h - Utility gnome-vfs methods. Will use gnome-vfs + HEAD in time. + + Copyright (C) 1999 Free Software Foundation + Copyright (C) 2000 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Ettore Perazzoli <ettore@comm2000.it> + John Sullivan <sullivan@eazel.com> +*/ + +#ifndef EGG_RECENT_VFS_UTILS_H +#define EGG_RECENT_VFS_UTILS_H + +#include <glib.h> + +G_BEGIN_DECLS + +char *egg_recent_vfs_format_uri_for_display (const char *uri); +char *egg_recent_vfs_make_uri_from_input (const char *uri); +gboolean egg_recent_vfs_uris_match (const char *uri_1, + const char *uri_2); +char *egg_recent_vfs_get_uri_scheme (const char *uri); + +G_END_DECLS + +#endif /* GNOME_VFS_UTILS_H */ diff --git a/cut-n-paste-code/libegg/egg-screen-exec.c b/cut-n-paste-code/libegg/egg-screen-exec.c new file mode 100644 index 000000000..94a068e36 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-screen-exec.c @@ -0,0 +1,260 @@ +/* egg-screen-exec.c + * + * Copyright (C) 2002 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mark McLoughlin <mark@skynet.ie> + */ + +#include <config.h> + +#include "egg-screen-exec.h" + +#include <string.h> +#include <libgnome/gnome-exec.h> + +#ifndef HAVE_GTK_MULTIHEAD +#include <gdk/gdkx.h> +#endif + +extern char **environ; + +/** + * egg_screen_exec_display_string: + * @screen: A #GdkScreen + * + * Description: Returns a string that when passed to XOpenDisplay() + * would cause @screen to be the default screen on the newly opened + * X display. This string is suitable for setting $DISPLAY when + * launching an application which should appear on @screen. + * + * Returns: a newly allocated string or %NULL on error. + **/ +char * +egg_screen_exec_display_string (GdkScreen *screen) +{ +#ifdef HAVE_GTK_MULTIHEAD + GString *str; + const char *old_display; + char *retval; + char *p; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + if (gdk_screen_get_default () == screen) + return g_strdup_printf ("DISPLAY=%s", + gdk_display_get_name ( + gdk_screen_get_display (screen))); + + old_display = gdk_display_get_name (gdk_screen_get_display (screen)); + + str = g_string_new ("DISPLAY="); + g_string_append (str, old_display); + + p = strrchr (str->str, '.'); + if (p && p > strchr (str->str, ':')) + g_string_truncate (str, p - str->str); + + g_string_append_printf (str, ".%d", gdk_screen_get_number (screen)); + + retval = str->str; + + g_string_free (str, FALSE); + + return retval; +#else + return g_strdup (DisplayString (GDK_DISPLAY ())); +#endif +} + +/** + * egg_screen_exec_environment: + * @screen: A #GdkScreen + * + * Description: Modifies the current program environment to + * ensure that $DISPLAY is set such that a launched application + * inheriting this environment would appear on @screen. + * + * Returns: a newly-allocated %NULL-terminated array of strings or + * %NULL on error. Use g_strfreev() to free it. + **/ +char ** +egg_screen_exec_environment (GdkScreen *screen) +{ + char **retval = NULL; + int i; +#ifdef HAVE_GTK_MULTIHEAD + int display_index = -1; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL); + + for (i = 0; environ [i]; i++) + if (!strncmp (environ [i], "DISPLAY", 7)) + display_index = i; + + if (display_index == -1) + display_index = i++; +#else + for (i = 0; environ [i]; i++); +#endif + + retval = g_new (char *, i + 1); + + for (i = 0; environ [i]; i++) +#ifdef HAVE_GTK_MULTIHEAD + if (i == display_index) + retval [i] = egg_screen_exec_display_string (screen); + else +#endif + retval [i] = g_strdup (environ [i]); + + retval [i] = NULL; + + return retval; +} + +/** + * egg_screen_execute_async: + * @screen: A #GdkScreen + * @dir: Directory in which child should be executed, or %NULL for current + * directory + * @argc: Number of arguments + * @argv: Argument vector to exec child + * + * Description: Like gnome_execute_async(), but ensures that the child + * is launched in an environment such that if it calls XOpenDisplay() + * the resulting display would have @screen as the default screen. + * + * Returns: process id of child, or %-1 on error. + **/ +int +egg_screen_execute_async (GdkScreen *screen, + const char *dir, + int argc, + char * const argv []) +{ +#ifdef HAVE_GTK_MULTIHEAD + char **envp = NULL; + int envc = 0; + int retval; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), -1); + + if (gdk_screen_get_default () != screen) { + envc = 1; + envp = g_new0 (char *, 2); + envp [0] = egg_screen_exec_display_string (screen); + } + + retval = gnome_execute_async_with_env (dir, argc, argv, envc, envp); + + g_strfreev (envp); + + return retval; +#else + return gnome_execute_async (dir, argc, argv); +#endif +} + +/** + * egg_screen_execute_shell: + * @screen: A #GdkScreen. + * @dir: Directory in which child should be executed, or %NULL for current + * directory + * @commandline: Shell command to execute + * + * Description: Like gnome_execute_shell(), but ensures that the child + * is launched in an environment such that if it calls XOpenDisplay() + * the resulting display would have @screen as the default screen. + * + * Returns: process id of shell, or %-1 on error. + **/ +int +egg_screen_execute_shell (GdkScreen *screen, + const char *dir, + const char *command) +{ +#ifdef HAVE_GTK_MULTIHEAD + int retval = -1; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), -1); + + if (gdk_screen_get_default () == screen) + retval = gnome_execute_shell (dir, command); + + else { + char *exec; + char *display; + + display = egg_screen_exec_display_string (screen); + exec = g_strconcat (display, " ", command, NULL); + + retval = gnome_execute_shell (dir, exec); + + g_free (display); + g_free (exec); + } + + return retval; +#else + return gnome_execute_shell (dir, command); +#endif +} + +/** + * egg_screen_execute_command_line_async: + * @screen: A #GdkScreen. + * @command_line: a command line + * @error: return location for errors + * + * Description: Like g_spawn_command_line_async(), but ensures that + * the child is launched in an environment such that if it calls + * XOpenDisplay() the resulting display would have @screen as the + * default screen. + * + * Returns: %TRUE on success, %FALSE if error is set. + **/ +gboolean +egg_screen_execute_command_line_async (GdkScreen *screen, + const char *command, + GError **error) +{ +#ifdef HAVE_GTK_MULTIHEAD + gboolean retval; + char **argv = NULL; + char **envp = NULL; + + g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); + g_return_val_if_fail (command != NULL, FALSE); + + if (!g_shell_parse_argv (command, NULL, &argv, error)) + return FALSE; + + if (gdk_screen_get_default () != screen) + envp = egg_screen_exec_environment (screen); + + retval = g_spawn_async (g_get_home_dir (), + argv, envp, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, error); + g_strfreev (argv); + g_strfreev (envp); + + return retval; +#else + return g_spawn_command_line_async (command, error); +#endif +} diff --git a/cut-n-paste-code/libegg/egg-screen-exec.h b/cut-n-paste-code/libegg/egg-screen-exec.h new file mode 100644 index 000000000..65a6df021 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-screen-exec.h @@ -0,0 +1,46 @@ +/* egg-screen-exec.h + * + * Copyright (C) 2002 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mark McLoughlin <mark@skynet.ie> + */ + +#ifndef __EGG_SCREEN_EXEC_H__ +#define __EGG_SCREEN_EXEC_H__ + +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +char *egg_screen_exec_display_string (GdkScreen *screen); +char **egg_screen_exec_environment (GdkScreen *screen); + +int egg_screen_execute_async (GdkScreen *screen, + const char *dir, + int argc, + char * const argv []); +int egg_screen_execute_shell (GdkScreen *screen, + const char *dir, + const char *command); +gboolean egg_screen_execute_command_line_async (GdkScreen *screen, + const char *command, + GError **error); + +G_END_DECLS + +#endif /* __EGG_SCREEN_EXEC_H__ */ diff --git a/cut-n-paste-code/libegg/egg-screen-help.c b/cut-n-paste-code/libegg/egg-screen-help.c new file mode 100644 index 000000000..42e717f5e --- /dev/null +++ b/cut-n-paste-code/libegg/egg-screen-help.c @@ -0,0 +1,163 @@ +/* egg-screen-help.c + * Copyright (C) 2001 Sid Vicious + * Copyright (C) 2001 Jonathan Blandford <jrb@alum.mit.edu> + * Copyright (C) 2002 Sun Microsystems Inc. + * All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Cambridge, MA 02139, USA. + * + * Authors: Mark McLoughlin <mark@skynet.ie> + */ + +#include <config.h> + +#include "egg-screen-help.h" + +#include <libgnome/gnome-help.h> + +#include "egg-screen-exec.h" + +/** + * egg_help_display_on_screen: + * @file_name: The name of the help document to display. + * @link_id: Can be %NULL. If set, refers to an anchor or section id within the + * requested document. + * @screen: a #GdkScreen. + * @error: A #GError instance that will hold the specifics of any error which + * occurs during processing, or %NULL + * + * Description: Like gnome_help_display(), but ensures that the help viewer + * application appears on @screen. + * + * Returns: %TRUE on success, %FALSE otherwise (in which case @error will + * contain the actual error). + **/ +gboolean +egg_help_display_on_screen (const char *file_name, + const char *link_id, + GdkScreen *screen, + GError **error) +{ + return egg_help_display_with_doc_id_on_screen ( + NULL, NULL, file_name, link_id, screen, error); +} + +/** + * egg_help_display_with_doc_id_on_screen: + * @program: The current application object, or %NULL for the default one. + * @doc_id: The document identifier, or %NULL to default to the application ID + * (app_id) of the specified @program. + * @file_name: The name of the help document to display. + * @link_id: Can be %NULL. If set, refers to an anchor or section id within the + * requested document. + * @screen: a #GdkScreen. + * @error: A #GError instance that will hold the specifics of any error which + * occurs during processing, or %NULL + * + * Description: Like gnome_help_display_with_doc_id(), but ensures that the help + * viewer application appears on @screen. + * + * Returns: %TRUE on success, %FALSE otherwise (in which case @error will + * contain the actual error). + **/ +gboolean +egg_help_display_with_doc_id_on_screen (GnomeProgram *program, + const char *doc_id, + const char *file_name, + const char *link_id, + GdkScreen *screen, + GError **error) +{ + gboolean retval; + char **env; + + env = egg_screen_exec_environment (screen); + + retval = gnome_help_display_with_doc_id_and_env ( + program, doc_id, file_name, link_id, env, error); + + g_strfreev (env); + + return retval; +} + +/** + * egg_help_display_desktop_on_screen: + * @program: The current application object, or %NULL for the default one. + * @doc_id: The name of the help file relative to the system's help domain + * (#GNOME_FILE_DOMAIN_HELP). + * @file_name: The name of the help document to display. + * @link_id: Can be %NULL. If set, refers to an anchor or section id within the + * requested document. + * @screen: a #GdkScreen. + * @error: A #GError instance that will hold the specifics of any error which + * occurs during processing, or %NULL + * + * Description: Like gnome_help_display_desktop(), but ensures that the help + * viewer application appears on @screen. + * + * Returns: %TRUE on success, %FALSE otherwise (in which case @error will + * contain the actual error). + **/ +gboolean +egg_help_display_desktop_on_screen (GnomeProgram *program, + const char *doc_id, + const char *file_name, + const char *link_id, + GdkScreen *screen, + GError **error) +{ + gboolean retval; + char **env; + + env = egg_screen_exec_environment (screen); + + retval = gnome_help_display_desktop_with_env ( + program, doc_id, file_name, link_id, env, error); + + g_strfreev (env); + + return retval; +} + +/** + * egg_help_display_uri_on_screen: + * @help_uri: The URI to display. + * @screen: a #GdkScreen. + * @error: A #GError instance that will hold the specifics of any error which + * occurs during processing, or %NULL + * + * Description: Like gnome_help_display_uri(), but ensures that the help viewer + * application appears on @screen. + * + * Returns: %TRUE on success, %FALSE otherwise (in which case @error will + * contain the actual error). + **/ +gboolean +egg_help_display_uri_on_screen (const char *help_uri, + GdkScreen *screen, + GError **error) +{ + gboolean retval; + char **env; + + env = egg_screen_exec_environment (screen); + + retval = gnome_help_display_uri_with_env (help_uri, env, error); + + g_strfreev (env); + + return retval; +} diff --git a/cut-n-paste-code/libegg/egg-screen-help.h b/cut-n-paste-code/libegg/egg-screen-help.h new file mode 100644 index 000000000..5c6770d43 --- /dev/null +++ b/cut-n-paste-code/libegg/egg-screen-help.h @@ -0,0 +1,56 @@ +/* egg-screen-help.h + * + * Copyright (C) 2002 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mark McLoughlin <mark@skynet.ie> + */ + +#ifndef __EGG_SCREEN_HELP_H__ +#define __EGG_SCREEN_HELP_H__ + +#include <glib.h> +#include <gdk/gdk.h> +#include <libgnome/gnome-program.h> + +G_BEGIN_DECLS + +/* Destined for libgnomeui. + */ +gboolean egg_help_display_on_screen (const char *file_name, + const char *link_id, + GdkScreen *screen, + GError **error); +gboolean egg_help_display_with_doc_id_on_screen (GnomeProgram *program, + const char *doc_id, + const char *file_name, + const char *link_id, + GdkScreen *screen, + GError **error); +gboolean egg_help_display_desktop_on_screen (GnomeProgram *program, + const char *doc_id, + const char *file_name, + const char *link_id, + GdkScreen *screen, + GError **error); +gboolean egg_help_display_uri_on_screen (const char *help_uri, + GdkScreen *screen, + GError **error); + +G_END_DECLS + +#endif /* __EGG_SCREEN_HELP_H__ */ diff --git a/cut-n-paste-code/libegg/egg-screen-url.c b/cut-n-paste-code/libegg/egg-screen-url.c new file mode 100644 index 000000000..1fd0c5daa --- /dev/null +++ b/cut-n-paste-code/libegg/egg-screen-url.c @@ -0,0 +1,59 @@ +/* egg-screen-url.c + * Copyright (C) 1998, James Henstridge <james@daa.com.au> + * Copyright (C) 1999, 2000 Red Hat, Inc. + * Copyright (C) 2002, Sun Microsystems Inc. + * All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Cambridge, MA 02139, USA. + * + * Authors: Mark McLoughlin <mark@skynet.ie> + */ + +#include <config.h> + +#include <libgnome/gnome-url.h> + +#include "egg-screen-url.h" +#include "egg-screen-exec.h" + +/** + * egg_url_show_on_screen: + * @url: The url to display. Should begin with the protocol to use (e.g. + * "http:", "ghelp:", etc) + * @screen: a #GdkScreen. + * @error: Used to store any errors that result from trying to display the @url. + * + * Description: Like gnome_url_show(), but ensures that the viewer application + * appears on @screen. + * + * Returns: %TRUE if everything went fine, %FALSE otherwise (in which case + * @error will contain the actual error). + **/ +gboolean +egg_url_show_on_screen (const char *url, + GdkScreen *screen, + GError **error) +{ + char **env; + gboolean retval; + + env = egg_screen_exec_environment (screen); + + retval = gnome_url_show_with_env (url, env, error); + + g_strfreev (env); + + return retval; +} diff --git a/cut-n-paste-code/libegg/egg-screen-url.h b/cut-n-paste-code/libegg/egg-screen-url.h new file mode 100644 index 000000000..d4aa0144c --- /dev/null +++ b/cut-n-paste-code/libegg/egg-screen-url.h @@ -0,0 +1,36 @@ +/* egg-screen-url.h + * + * Copyright (C) 2002 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mark McLoughlin <mark@skynet.ie> + */ + +#ifndef __EGG_SCREEN_URL_H__ +#define __EGG_SCREEN_URL_H__ + +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +gboolean egg_url_show_on_screen (const char *url, + GdkScreen *screen, + GError **error); + +G_END_DECLS + +#endif /* __EGG_SCREEN_URL_H__ */ diff --git a/cut-n-paste-code/libegg/eggtreemultidnd.c b/cut-n-paste-code/libegg/eggtreemultidnd.c new file mode 100644 index 000000000..ae7a4d5aa --- /dev/null +++ b/cut-n-paste-code/libegg/eggtreemultidnd.c @@ -0,0 +1,412 @@ +/* eggtreemultidnd.c + * Copyright (C) 2001 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <string.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtksignal.h> +#include <gtk/gtkwidget.h> +#include <gtk/gtkmain.h> +#include "eggtreemultidnd.h" + +#define EGG_TREE_MULTI_DND_STRING "EggTreeMultiDndString" + +typedef struct +{ + guint pressed_button; + gint x; + gint y; + guint motion_notify_handler; + guint button_release_handler; + guint drag_data_get_handler; + GSList *event_list; +} EggTreeMultiDndData; + +/* CUT-N-PASTE from gtktreeview.c */ +typedef struct _TreeViewDragInfo TreeViewDragInfo; +struct _TreeViewDragInfo +{ + GdkModifierType start_button_mask; + GtkTargetList *source_target_list; + GdkDragAction source_actions; + + GtkTargetList *dest_target_list; + + guint source_set : 1; + guint dest_set : 1; +}; + + +GType +egg_tree_multi_drag_source_get_type (void) +{ + static GType our_type = 0; + + if (!our_type) + { + static const GTypeInfo our_info = + { + sizeof (EggTreeMultiDragSourceIface), /* class_size */ + NULL, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, /* n_preallocs */ + NULL + }; + + our_type = g_type_register_static (G_TYPE_INTERFACE, "EggTreeMultiDragSource", &our_info, 0); + } + + return our_type; +} + + +/** + * egg_tree_multi_drag_source_row_draggable: + * @drag_source: a #EggTreeMultiDragSource + * @path: row on which user is initiating a drag + * + * Asks the #EggTreeMultiDragSource whether a particular row can be used as + * the source of a DND operation. If the source doesn't implement + * this interface, the row is assumed draggable. + * + * Return value: %TRUE if the row can be dragged + **/ +gboolean +egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source, + GList *path_list) +{ + EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source); + + g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE); + g_return_val_if_fail (iface->row_draggable != NULL, FALSE); + g_return_val_if_fail (path_list != NULL, FALSE); + + if (iface->row_draggable) + return (* iface->row_draggable) (drag_source, path_list); + else + return TRUE; +} + + +/** + * egg_tree_multi_drag_source_drag_data_delete: + * @drag_source: a #EggTreeMultiDragSource + * @path: row that was being dragged + * + * Asks the #EggTreeMultiDragSource to delete the row at @path, because + * it was moved somewhere else via drag-and-drop. Returns %FALSE + * if the deletion fails because @path no longer exists, or for + * some model-specific reason. Should robustly handle a @path no + * longer found in the model! + * + * Return value: %TRUE if the row was successfully deleted + **/ +gboolean +egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source, + GList *path_list) +{ + EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source); + + g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE); + g_return_val_if_fail (iface->drag_data_delete != NULL, FALSE); + g_return_val_if_fail (path_list != NULL, FALSE); + + return (* iface->drag_data_delete) (drag_source, path_list); +} + +/** + * egg_tree_multi_drag_source_drag_data_get: + * @drag_source: a #EggTreeMultiDragSource + * @path: row that was dragged + * @selection_data: a #EggSelectionData to fill with data from the dragged row + * + * Asks the #EggTreeMultiDragSource to fill in @selection_data with a + * representation of the row at @path. @selection_data->target gives + * the required type of the data. Should robustly handle a @path no + * longer found in the model! + * + * Return value: %TRUE if data of the required type was provided + **/ +gboolean +egg_tree_multi_drag_source_drag_data_get (EggTreeMultiDragSource *drag_source, + GList *path_list, + GtkSelectionData *selection_data) +{ + EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source); + + g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE); + g_return_val_if_fail (iface->drag_data_get != NULL, FALSE); + g_return_val_if_fail (path_list != NULL, FALSE); + g_return_val_if_fail (selection_data != NULL, FALSE); + + return (* iface->drag_data_get) (drag_source, path_list, selection_data); +} + +static void +stop_drag_check (GtkWidget *widget) +{ + EggTreeMultiDndData *priv_data; + GSList *l; + + priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING); + + for (l = priv_data->event_list; l != NULL; l = l->next) + gdk_event_free (l->data); + + g_slist_free (priv_data->event_list); + priv_data->event_list = NULL; + g_signal_handler_disconnect (widget, priv_data->motion_notify_handler); + g_signal_handler_disconnect (widget, priv_data->button_release_handler); +} + +static gboolean +egg_tree_multi_drag_button_release_event (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + EggTreeMultiDndData *priv_data; + GSList *l; + + priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING); + + for (l = priv_data->event_list; l != NULL; l = l->next) + gtk_propagate_event (widget, l->data); + + stop_drag_check (widget); + + return FALSE; +} + +static void +selection_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GList **list_ptr; + + list_ptr = (GList **) data; + + *list_ptr = g_list_prepend (*list_ptr, gtk_tree_row_reference_new (model, path)); +} + +static void +path_list_free (GList *path_list) +{ + g_list_foreach (path_list, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free (path_list); +} + +static void +set_context_data (GdkDragContext *context, + GList *path_list) +{ + g_object_set_data_full (G_OBJECT (context), + "egg-tree-view-multi-source-row", + path_list, + (GDestroyNotify) path_list_free); +} + +static GList * +get_context_data (GdkDragContext *context) +{ + return g_object_get_data (G_OBJECT (context), + "egg-tree-view-multi-source-row"); +} + +/* CUT-N-PASTE from gtktreeview.c */ +static TreeViewDragInfo* +get_info (GtkTreeView *tree_view) +{ + return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info"); +} + + +static void +egg_tree_multi_drag_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GtkTreeView *tree_view; + GtkTreeModel *model; + TreeViewDragInfo *di; + GList *path_list; + + tree_view = GTK_TREE_VIEW (widget); + + model = gtk_tree_view_get_model (tree_view); + + if (model == NULL) + return; + + di = get_info (GTK_TREE_VIEW (widget)); + + if (di == NULL) + return; + + path_list = get_context_data (context); + + if (path_list == NULL) + return; + + /* We can implement the GTK_TREE_MODEL_ROW target generically for + * any model; for DragSource models there are some other targets + * we also support. + */ + + if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model)) + { + egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model), + path_list, + selection_data); + } +} + +static gboolean +egg_tree_multi_drag_motion_event (GtkWidget *widget, + GdkEventMotion *event, + gpointer data) +{ + EggTreeMultiDndData *priv_data; + + priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING); + + if (gtk_drag_check_threshold (widget, + priv_data->x, + priv_data->y, + event->x, + event->y)) + { + GList *path_list = NULL; + GtkTreeSelection *selection; + GtkTreeModel *model; + GdkDragContext *context; + TreeViewDragInfo *di; + + di = get_info (GTK_TREE_VIEW (widget)); + + if (di == NULL) + return FALSE; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + stop_drag_check (widget); + gtk_tree_selection_selected_foreach (selection, selection_foreach, &path_list); + path_list = g_list_reverse (path_list); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); + if (egg_tree_multi_drag_source_row_draggable (EGG_TREE_MULTI_DRAG_SOURCE (model), path_list)) + { + + context = gtk_drag_begin (widget, + di->source_target_list, + di->source_actions, + priv_data->pressed_button, + (GdkEvent*)event); + set_context_data (context, path_list); + gtk_drag_set_icon_default (context); + + } + else + { + path_list_free (path_list); + } + } + + return TRUE; +} + +static gboolean +egg_tree_multi_drag_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + GtkTreeView *tree_view; + GtkTreePath *path = NULL; + GtkTreeViewColumn *column = NULL; + gint cell_x, cell_y; + GtkTreeSelection *selection; + EggTreeMultiDndData *priv_data; + + tree_view = GTK_TREE_VIEW (widget); + priv_data = g_object_get_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING); + if (priv_data == NULL) + { + priv_data = g_new0 (EggTreeMultiDndData, 1); + g_object_set_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING, priv_data); + } + + if (g_slist_find (priv_data->event_list, event)) + return FALSE; + + if (priv_data->event_list) + { + /* save the event to be propagated in order */ + priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event)); + return TRUE; + } + + if (event->type == GDK_2BUTTON_PRESS) + return FALSE; + + gtk_tree_view_get_path_at_pos (tree_view, + event->x, event->y, + &path, &column, + &cell_x, &cell_y); + + selection = gtk_tree_view_get_selection (tree_view); + + if (path && gtk_tree_selection_path_is_selected (selection, path)) + { + priv_data->pressed_button = event->button; + priv_data->x = event->x; + priv_data->y = event->y; + priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event)); + priv_data->motion_notify_handler = + g_signal_connect (G_OBJECT (tree_view), "motion_notify_event", G_CALLBACK (egg_tree_multi_drag_motion_event), NULL); + priv_data->button_release_handler = + g_signal_connect (G_OBJECT (tree_view), "button_release_event", G_CALLBACK (egg_tree_multi_drag_button_release_event), NULL); + + if (priv_data->drag_data_get_handler == 0) + { + priv_data->drag_data_get_handler = + g_signal_connect (G_OBJECT (tree_view), "drag_data_get", G_CALLBACK (egg_tree_multi_drag_drag_data_get), NULL); + } + + return TRUE; + } + + if (path) + { + gtk_tree_path_free (path); + } + + return FALSE; +} + +void +egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view) +{ + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); + g_signal_connect (G_OBJECT (tree_view), "button_press_event", G_CALLBACK (egg_tree_multi_drag_button_press_event), NULL); +} + diff --git a/cut-n-paste-code/libegg/eggtreemultidnd.h b/cut-n-paste-code/libegg/eggtreemultidnd.h new file mode 100644 index 000000000..810d48ea7 --- /dev/null +++ b/cut-n-paste-code/libegg/eggtreemultidnd.h @@ -0,0 +1,76 @@ +/* eggtreednd.h + * Copyright (C) 2001 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_TREE_MULTI_DND_H__ +#define __EGG_TREE_MULTI_DND_H__ + +#include <gtk/gtktreemodel.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkdnd.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_TREE_MULTI_DRAG_SOURCE (egg_tree_multi_drag_source_get_type ()) +#define EGG_TREE_MULTI_DRAG_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TREE_MULTI_DRAG_SOURCE, EggTreeMultiDragSource)) +#define EGG_IS_TREE_MULTI_DRAG_SOURCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TREE_MULTI_DRAG_SOURCE)) +#define EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), EGG_TYPE_TREE_MULTI_DRAG_SOURCE, EggTreeMultiDragSourceIface)) + +typedef struct _EggTreeMultiDragSource EggTreeMultiDragSource; /* Dummy typedef */ +typedef struct _EggTreeMultiDragSourceIface EggTreeMultiDragSourceIface; + +struct _EggTreeMultiDragSourceIface +{ + GTypeInterface g_iface; + + /* VTable - not signals */ + gboolean (* row_draggable) (EggTreeMultiDragSource *drag_source, + GList *path_list); + + gboolean (* drag_data_get) (EggTreeMultiDragSource *drag_source, + GList *path_list, + GtkSelectionData *selection_data); + + gboolean (* drag_data_delete) (EggTreeMultiDragSource *drag_source, + GList *path_list); +}; + +GType egg_tree_multi_drag_source_get_type (void) G_GNUC_CONST; + +/* Returns whether the given row can be dragged */ +gboolean egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source, + GList *path_list); + +/* Deletes the given row, or returns FALSE if it can't */ +gboolean egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source, + GList *path_list); + + +/* Fills in selection_data with type selection_data->target based on the row + * denoted by path, returns TRUE if it does anything + */ +gboolean egg_tree_multi_drag_source_drag_data_get (EggTreeMultiDragSource *drag_source, + GList *path_list, + GtkSelectionData *selection_data); +void egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view); + + + +G_END_DECLS + +#endif /* __EGG_TREE_MULTI_DND_H__ */ diff --git a/cut-n-paste-code/libegg/update-from-egg.sh b/cut-n-paste-code/libegg/update-from-egg.sh new file mode 100755 index 000000000..9be68a9b4 --- /dev/null +++ b/cut-n-paste-code/libegg/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 |