medit/moo/mooedit/moofile.c
2005-08-11 23:05:09 +00:00

1224 lines
36 KiB
C

/*
* mooedit/moofile.c
*
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* 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.
*
* See COPYING file that comes with this distribution.
*/
/*
* Icon handling code was copied (and modified) from gtk/gtkfilesystemunix.c,
* Copyright (C) 2003, Red Hat, Inc.
*
* Don't ask me why and how rest of code works/done this way, it's all
* experimental.
*/
#define MOO_FILE_SYSTEM_COMPILATION
#include "mooedit/moofilesystem.h"
#include "mooedit/xdgmime/xdgmime.h"
#include "mooutils/moomarshals.h"
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#define NORMAL_PRIORITY G_PRIORITY_DEFAULT_IDLE
#define NORMAL_TIMEOUT 0.1
#define BACKGROUND_PRIORITY G_PRIORITY_LOW
#define BACKGROUND_TIMEOUT 0.001
#define TIMER_CLEAR(timer) \
G_STMT_START { \
g_timer_start (timer); \
g_timer_stop (timer); \
} G_STMT_END
#if 0
#define PRINT_TIMES g_print
#else
static void PRINT_TIMES (G_GNUC_UNUSED const char *format, ...)
{
}
#endif
typedef enum {
STAGE_NAMES = 1,
STAGE_STAT = 2,
STAGE_MIME_TYPE = 3
} Stage;
typedef struct {
double names_timer;
double stat_timer;
guint stat_counter;
double icons_timer;
guint icons_counter;
} Debug;
struct _MooFolderPrivate {
guint deleted : 1;
Stage done;
Stage wanted;
Stage wanted_bg;
MooFileSystem *fs;
GDir *dir;
GSList *files;
GSList *files_copy;
char *path;
GSourceFunc populate_func;
int populate_priority;
guint populate_idle_id;
double populate_timeout;
Debug debug;
GTimer *timer;
};
static void moo_folder_finalize (GObject *object);
static void moo_folder_deleted (MooFolder *folder);
static void folder_emit_deleted (MooFolder *folder);
static void folder_emit_files (MooFolder *folder,
guint signal,
GSList *files);
static void stop_populate (MooFolder *folder);
static GSList *files_list_copy (GSList *list);
static void files_list_free (GSList **list);
static gboolean get_icons_a_bit (MooFolder *folder);
static gboolean get_stat_a_bit (MooFolder *folder);
static double get_names (MooFolder *folder);
static const char *get_default_file_icon (void);
static const char *get_nonexistent_icon (void);
static const char *get_blank_icon (void);
static const char *get_icon (MooFolder *folder,
MooFile *file);
static const char *get_folder_icon (const char *path);
static GdkPixbuf *get_named_icon (const char *name,
GtkWidget *widget,
GtkIconSize size);
static GdkPixbuf *create_named_icon (GtkIconTheme *icon_theme,
const char *name,
GtkWidget *widget,
GtkIconSize size);
static GdkPixbuf *create_fallback_icon (GtkIconTheme *icon_theme,
const char *name,
GtkWidget *widget,
GtkIconSize size);
static GdkPixbuf *create_icon_for_mime_type (GtkIconTheme *icon_theme,
const char *mime_type,
int pixel_size);
#define FILE_PATH(folder,file) g_build_filename (folder->priv->path, file->basename, NULL)
/* MOO_TYPE_FOLDER */
G_DEFINE_TYPE (MooFolder, moo_folder, G_TYPE_OBJECT)
enum {
DELETED,
FILES_ADDED,
FILES_REMOVED,
FILES_CHANGED,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS];
static void moo_folder_class_init (MooFolderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = moo_folder_finalize;
klass->deleted = moo_folder_deleted;
signals[DELETED] =
g_signal_new ("deleted",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (MooFolderClass, deleted),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[FILES_ADDED] =
g_signal_new ("files-added",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (MooFolderClass, files_added),
NULL, NULL,
_moo_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
signals[FILES_REMOVED] =
g_signal_new ("files-removed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (MooFolderClass, files_removed),
NULL, NULL,
_moo_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
signals[FILES_CHANGED] =
g_signal_new ("files-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (MooFolderClass, files_changed),
NULL, NULL,
_moo_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
}
static void moo_folder_init (MooFolder *folder)
{
folder->priv = g_new0 (MooFolderPrivate, 1);
folder->priv->deleted = FALSE;
folder->priv->done = 0;
folder->priv->wanted = 0;
folder->priv->fs = NULL;
folder->priv->dir = NULL;
folder->priv->files = NULL;
folder->priv->files_copy = NULL;
folder->priv->path = NULL;
folder->priv->populate_func = NULL;
folder->priv->populate_idle_id = 0;
folder->priv->populate_timeout = BACKGROUND_TIMEOUT;
folder->priv->timer = g_timer_new ();
g_timer_stop (folder->priv->timer);
}
static void moo_folder_finalize (GObject *object)
{
MooFolder *folder = MOO_FOLDER (object);
files_list_free (&folder->priv->files);
files_list_free (&folder->priv->files_copy);
if (folder->priv->dir)
g_dir_close (folder->priv->dir);
g_free (folder->priv->path);
if (folder->priv->populate_idle_id)
g_source_remove (folder->priv->populate_idle_id);
g_timer_destroy (folder->priv->timer);
g_free (folder->priv);
folder->priv = NULL;
G_OBJECT_CLASS (moo_folder_parent_class)->finalize (object);
}
MooFolder *moo_folder_new (MooFileSystem *fs,
const char *path,
MooFileFlags wanted,
GError **error)
{
char *norm_path;
GDir *dir;
MooFolder *folder;
GError *file_error = NULL;
g_return_val_if_fail (MOO_IS_FILE_SYSTEM (fs), NULL);
g_return_val_if_fail (path != NULL, NULL);
norm_path = moo_normalize_path (path);
g_return_val_if_fail (norm_path != NULL, NULL);
dir = g_dir_open (norm_path, 0, &file_error);
if (!dir)
{
if (file_error->domain != G_FILE_ERROR)
{
g_set_error (error, MOO_FILE_ERROR,
MOO_FILE_ERROR_FAILED,
"%s", file_error->message);
}
else switch (file_error->code)
{
case G_FILE_ERROR_NOENT:
g_set_error (error, MOO_FILE_ERROR,
MOO_FILE_ERROR_NONEXISTENT,
"%s", file_error->message);
break;
case G_FILE_ERROR_NOTDIR:
g_set_error (error, MOO_FILE_ERROR,
MOO_FILE_ERROR_NOT_FOLDER,
"%s", file_error->message);
break;
case G_FILE_ERROR_NAMETOOLONG:
case G_FILE_ERROR_LOOP:
g_set_error (error, MOO_FILE_ERROR,
MOO_FILE_ERROR_BAD_FILENAME,
"%s", file_error->message);
break;
default:
g_set_error (error, MOO_FILE_ERROR,
MOO_FILE_ERROR_FAILED,
"%s", file_error->message);
break;
}
g_error_free (file_error);
g_free (norm_path);
return NULL;
}
folder = g_object_new (MOO_TYPE_FOLDER, NULL);
folder->priv->fs = fs;
folder->priv->path = norm_path;
folder->priv->dir = dir;
moo_folder_set_wanted (folder, wanted, TRUE);
return folder;
}
static void moo_folder_deleted (MooFolder *folder)
{
stop_populate (folder);
folder->priv->deleted = TRUE;
files_list_free (&folder->priv->files);
}
void moo_folder_set_wanted (MooFolder *folder,
MooFileFlags wanted,
gboolean bit_now)
{
Stage wanted_stage = STAGE_NAMES;
double elapsed = 0.0;
g_return_if_fail (MOO_IS_FOLDER (folder));
g_return_if_fail (!folder->priv->deleted);
if (wanted & MOO_FILE_HAS_ICON)
wanted_stage = STAGE_MIME_TYPE;
else if (wanted & MOO_FILE_HAS_MIME_TYPE)
wanted_stage = STAGE_MIME_TYPE;
else if (wanted & MOO_FILE_HAS_STAT)
wanted_stage = STAGE_STAT;
if (wanted_stage <= folder->priv->done)
return;
if (folder->priv->wanted > folder->priv->done)
{
g_assert (folder->priv->populate_idle_id != 0);
folder->priv->wanted = MAX (folder->priv->wanted, wanted_stage);
return;
}
folder->priv->wanted = wanted_stage;
if (!folder->priv->done)
{
g_assert (folder->priv->dir != NULL);
elapsed = get_names (folder);
}
if (folder->priv->wanted_bg != 0)
{
g_assert (folder->priv->populate_idle_id != 0);
g_assert (folder->priv->populate_func != NULL);
g_assert (folder->priv->populate_priority != 0);
g_source_remove (folder->priv->populate_idle_id);
}
else
{
g_assert (folder->priv->populate_idle_id == 0);
switch (folder->priv->done)
{
case STAGE_NAMES:
g_assert (folder->priv->dir == NULL);
folder->priv->populate_func = (GSourceFunc) get_stat_a_bit;
break;
case STAGE_STAT:
g_assert (folder->priv->dir == NULL);
folder->priv->populate_func = (GSourceFunc) get_icons_a_bit;
break;
default:
g_assert_not_reached ();
}
}
folder->priv->populate_timeout = NORMAL_TIMEOUT;
folder->priv->populate_priority = NORMAL_PRIORITY;
TIMER_CLEAR (folder->priv->timer);
if (!bit_now ||
elapsed > folder->priv->populate_timeout ||
folder->priv->populate_func (folder))
{
folder->priv->populate_idle_id =
g_idle_add_full (folder->priv->populate_priority,
folder->priv->populate_func,
folder, NULL);
}
}
static void stop_populate (MooFolder *folder)
{
if (folder->priv->populate_idle_id)
g_source_remove (folder->priv->populate_idle_id);
folder->priv->populate_idle_id = 0;
folder->priv->populate_func = 0;
if (folder->priv->dir)
g_dir_close (folder->priv->dir);
folder->priv->dir = NULL;
files_list_free (&folder->priv->files_copy);
}
static void folder_emit_deleted (MooFolder *folder)
{
g_signal_emit (folder, signals[DELETED], 0);
}
static void folder_emit_files (MooFolder *folder,
guint sig,
GSList *files)
{
if (files)
g_signal_emit (folder, signals[sig], 0, files);
}
GSList *moo_folder_list_files (MooFolder *folder)
{
g_return_val_if_fail (MOO_IS_FOLDER (folder), NULL);
return files_list_copy (folder->priv->files);
}
static double get_names (MooFolder *folder)
{
GTimer *timer;
GSList *added = NULL;
const char *name;
MooFile *file;
double elapsed;
g_assert (folder->priv->path != NULL);
g_assert (folder->priv->dir != NULL);
timer = g_timer_new ();
file = moo_file_new (folder->priv->path, "..");
file->icon = get_folder_icon (NULL);
file->flags = MOO_FILE_HAS_MIME_TYPE | MOO_FILE_HAS_ICON;
file->info = MOO_FILE_EXISTS | MOO_FILE_IS_FOLDER;
folder->priv->files = g_slist_prepend (folder->priv->files, file);
added = g_slist_prepend (added, file);
for (name = g_dir_read_name (folder->priv->dir);
name != NULL;
name = g_dir_read_name (folder->priv->dir))
{
file = moo_file_new (folder->priv->path, name);
file->icon = get_blank_icon ();
folder->priv->files = g_slist_prepend (folder->priv->files, file);
added = g_slist_prepend (added, file);
}
elapsed = folder->priv->debug.names_timer =
g_timer_elapsed (timer, NULL);
g_timer_destroy (timer);
folder_emit_files (folder, FILES_ADDED, added);
g_slist_free (added);
g_dir_close (folder->priv->dir);
folder->priv->dir = NULL;
folder->priv->done = STAGE_NAMES;
PRINT_TIMES ("names folder %s: %f sec\n",
folder->priv->path,
folder->priv->debug.names_timer);
return elapsed;
}
static gboolean get_stat_a_bit (MooFolder *folder)
{
gboolean done = FALSE;
GSList *changed = NULL;
double elapsed;
g_assert (folder->priv->dir == NULL);
g_assert (folder->priv->done == STAGE_NAMES);
g_assert (folder->priv->path != NULL);
elapsed = g_timer_elapsed (folder->priv->timer, NULL);
g_timer_continue (folder->priv->timer);
if (!folder->priv->files_copy)
folder->priv->files_copy =
files_list_copy (folder->priv->files);
if (!folder->priv->files_copy)
done = TRUE;
while (!done)
{
MooFile *file = folder->priv->files_copy->data;
folder->priv->files_copy =
g_slist_delete_link (folder->priv->files_copy,
folder->priv->files_copy);
if (!(file->flags & MOO_FILE_HAS_STAT))
{
changed = g_slist_prepend (changed, file);
moo_file_stat (file, folder->priv->path);
}
else
{
moo_file_unref (file);
}
if (!folder->priv->files_copy)
done = TRUE;
if (g_timer_elapsed (folder->priv->timer, NULL) > folder->priv->populate_timeout)
break;
}
elapsed = g_timer_elapsed (folder->priv->timer, NULL) - elapsed;
folder->priv->debug.stat_timer += elapsed;
folder->priv->debug.stat_counter += 1;
g_timer_stop (folder->priv->timer);
folder_emit_files (folder, FILES_CHANGED, changed);
files_list_free (&changed);
if (!done)
{
TIMER_CLEAR (folder->priv->timer);
return TRUE;
}
else
{
g_assert (folder->priv->files_copy == NULL);
folder->priv->populate_idle_id = 0;
PRINT_TIMES ("stat folder %s: %d iterations, %f sec\n",
folder->priv->path,
folder->priv->debug.stat_counter,
folder->priv->debug.stat_timer);
folder->priv->done = STAGE_STAT;
if (folder->priv->wanted >= STAGE_MIME_TYPE || folder->priv->wanted_bg >= STAGE_MIME_TYPE)
{
if (folder->priv->wanted >= STAGE_MIME_TYPE)
{
folder->priv->populate_priority = NORMAL_PRIORITY;
folder->priv->populate_timeout = NORMAL_TIMEOUT;
}
else if (folder->priv->wanted_bg >= STAGE_MIME_TYPE)
{
folder->priv->populate_priority = BACKGROUND_PRIORITY;
folder->priv->populate_timeout = BACKGROUND_TIMEOUT;
}
if (folder->priv->populate_idle_id)
g_source_remove (folder->priv->populate_idle_id);
folder->priv->populate_idle_id = 0;
folder->priv->populate_func = (GSourceFunc) get_icons_a_bit;
if (g_timer_elapsed (folder->priv->timer, NULL) < folder->priv->populate_timeout)
{
/* in this case we may block for as much as twice TIMEOUT, but usually
it allows stat and loading icons in one iteration */
TIMER_CLEAR (folder->priv->timer);
if (folder->priv->populate_func (folder))
folder->priv->populate_idle_id =
g_idle_add_full (folder->priv->populate_priority,
folder->priv->populate_func,
folder, NULL);
}
else
{
TIMER_CLEAR (folder->priv->timer);
folder->priv->populate_idle_id =
g_idle_add_full (folder->priv->populate_priority,
folder->priv->populate_func,
folder, NULL);
}
}
else
{
folder->priv->populate_func = NULL;
folder->priv->populate_priority = 0;
folder->priv->populate_timeout = 0;
}
return FALSE;
}
}
static gboolean get_icons_a_bit (MooFolder *folder)
{
gboolean done = FALSE;
double elapsed;
GSList *changed = NULL;
g_assert (folder->priv->dir == NULL);
g_assert (folder->priv->done == STAGE_STAT);
g_assert (folder->priv->path != NULL);
elapsed = g_timer_elapsed (folder->priv->timer, NULL);
g_timer_continue (folder->priv->timer);
if (!folder->priv->files_copy)
folder->priv->files_copy =
files_list_copy (folder->priv->files);
if (!folder->priv->files_copy)
done = TRUE;
while (!done)
{
MooFile *file = folder->priv->files_copy->data;
char *path;
folder->priv->files_copy =
g_slist_delete_link (folder->priv->files_copy,
folder->priv->files_copy);
if (file->info & MOO_FILE_EXISTS &&
!(file->flags & MOO_FILE_HAS_MIME_TYPE))
{
changed = g_slist_prepend (changed, file);
path = FILE_PATH (folder, file);
file->mime_type = xdg_mime_get_mime_type_for_file (path);
file->flags |= MOO_FILE_HAS_MIME_TYPE;
file->flags |= MOO_FILE_HAS_ICON;
file->icon = get_icon (folder, file);
g_free (path);
}
else
{
moo_file_unref (file);
}
if (!folder->priv->files_copy)
done = TRUE;
if (g_timer_elapsed (folder->priv->timer, NULL) > folder->priv->populate_timeout)
break;
}
elapsed = g_timer_elapsed (folder->priv->timer, NULL) - elapsed;
folder->priv->debug.icons_timer += elapsed;
folder->priv->debug.icons_counter += 1;
TIMER_CLEAR (folder->priv->timer);
folder_emit_files (folder, FILES_CHANGED, changed);
files_list_free (&changed);
if (done)
{
PRINT_TIMES ("icons folder %s: %d iterations, %f sec\n",
folder->priv->path,
folder->priv->debug.icons_counter,
folder->priv->debug.icons_timer);
g_assert (folder->priv->files_copy == NULL);
folder->priv->populate_idle_id = 0;
folder->priv->done = STAGE_MIME_TYPE;
folder->priv->populate_func = NULL;
folder->priv->populate_priority = 0;
folder->priv->populate_timeout = 0;
}
return !done;
}
/* TODO TODO */
MooFile *moo_folder_get_file (MooFolder *folder,
const char *basename)
{
GSList *l;
g_return_val_if_fail (MOO_IS_FOLDER (folder), NULL);
g_return_val_if_fail (basename != NULL, NULL);
for (l = folder->priv->files; l != NULL; l = l->next)
{
MooFile *file = l->data;
g_assert (file != NULL && file->basename != NULL);
if (!strcmp (basename, file->basename))
return file;
}
return NULL;
}
/********************************************************************/
/* MooFile
*/
MooFile *moo_file_new (const char *dirname,
const char *basename)
{
MooFile *file;
char *path;
g_return_val_if_fail (dirname != NULL && basename != NULL, NULL);
file = g_new0 (MooFile, 1);
file->basename = g_strdup (basename);
path = g_build_filename (dirname, basename, NULL);
file->display_basename = g_filename_display_basename (path);
g_free (path);
file->ref_count = 1;
if (basename[0] == '.')
file->info = MOO_FILE_IS_HIDDEN;
return file;
}
MooFile *moo_file_ref (MooFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
file->ref_count++;
return file;
}
void moo_file_unref (MooFile *file)
{
if (file && !--file->ref_count)
{
g_free (file->basename);
g_free (file->display_basename);
g_free (file);
}
}
void moo_file_stat (MooFile *file,
const char *dirname)
{
char *fullname;
g_return_if_fail (file != NULL);
fullname = g_build_filename (dirname, file->basename, NULL);
file->info = MOO_FILE_EXISTS;
file->flags = MOO_FILE_HAS_STAT;
if (stat (fullname, &file->statbuf) != 0)
{
if (errno == ENOENT && !lstat (fullname, &file->statbuf))
{
gchar *display_name = g_filename_display_name (fullname);
g_warning ("%s: file '%s' is a broken link",
G_STRLOC, display_name);
g_free (display_name);
file->info = MOO_FILE_IS_LINK;
}
else
{
int save_errno = errno;
gchar *display_name = g_filename_display_name (fullname);
g_warning ("%s: error getting information for '%s': %s",
G_STRLOC, display_name,
g_strerror (save_errno));
g_free (display_name);
file->info = 0;
file->flags = 0;
}
}
if (file->info & MOO_FILE_EXISTS)
{
if (S_ISDIR (file->statbuf.st_mode))
file->info |= MOO_FILE_IS_FOLDER;
if (S_ISLNK (file->statbuf.st_mode))
file->info |= MOO_FILE_IS_LINK;
}
if (file->info & MOO_FILE_EXISTS)
{
if (file->info & MOO_FILE_IS_FOLDER)
{
file->flags |= MOO_FILE_HAS_MIME_TYPE;
file->flags |= MOO_FILE_HAS_ICON;
file->icon = get_folder_icon (fullname);
}
else
{
file->icon = get_default_file_icon ();
}
}
else
{
file->icon = get_nonexistent_icon ();
}
if (file->basename[0] == '.')
file->info |= MOO_FILE_IS_HIDDEN;
g_free (fullname);
}
gboolean moo_file_test (const MooFile *file,
MooFileInfo test)
{
g_return_val_if_fail (file != NULL, FALSE);
return file->info & test;
}
char *moo_normalize_path (const char *path)
{
return g_strdup (path);
}
const char *moo_file_get_display_basename (const MooFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
return file->display_basename;
}
const char *moo_file_get_basename (const MooFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
return file->basename;
}
const char *moo_file_get_mime_type (const MooFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
return file->mime_type;
}
gconstpointer moo_file_get_stat (const MooFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
if (file->flags & MOO_FILE_HAS_STAT && file->info & MOO_FILE_EXISTS)
return &file->statbuf;
else
return NULL;
}
GdkPixbuf *moo_file_get_icon (const MooFile *file,
GtkWidget *widget,
GtkIconSize size)
{
g_return_val_if_fail (file != NULL, NULL);
return get_named_icon (file->icon, widget, size);
}
GType moo_file_get_type (void)
{
static GType type = 0;
if (!type)
type = g_boxed_type_register_static ("MooFile",
(GBoxedCopyFunc) moo_file_ref,
(GBoxedFreeFunc) moo_file_unref);
return type;
}
GType moo_file_flags_get_type (void)
{
static GType type = 0;
static const GFlagsValue values[] = {
{ MOO_FILE_HAS_MIME_TYPE, (char*)"MOO_FILE_HAS_MIME_TYPE", (char*)"has-mime-type" },
{ MOO_FILE_HAS_ICON, (char*)"MOO_FILE_HAS_ICON", (char*)"has-icon" },
{ MOO_FILE_HAS_STAT, (char*)"MOO_FILE_HAS_STAT", (char*)"has-stat" },
{ MOO_FILE_ALL_FLAGS, (char*)"MOO_FILE_ALL_FLAGS", (char*)"all-flags" },
{ 0, NULL, NULL }
};
if (!type)
type = g_flags_register_static ("MooFileFlags", values);
return type;
}
GType moo_file_info_get_type (void)
{
static GType type = 0;
static const GFlagsValue values[] = {
{ MOO_FILE_EXISTS, (char*)"MOO_FILE_EXISTS", (char*)"exists" },
{ MOO_FILE_IS_FOLDER, (char*)"MOO_FILE_IS_FOLDER", (char*)"is-folder" },
{ MOO_FILE_IS_HIDDEN, (char*)"MOO_FILE_IS_HIDDEN", (char*)"is-hidden" },
{ MOO_FILE_IS_LINK, (char*)"MOO_FILE_IS_LINK", (char*)"is-link" },
{ 0, NULL, NULL }
};
if (!type)
type = g_flags_register_static ("MooFileInfo", values);
return type;
}
const char *moo_folder_get_path (MooFolder *folder)
{
g_return_val_if_fail (MOO_IS_FOLDER (folder), NULL);
return folder->priv->path;
}
static GSList *files_list_copy (GSList *list)
{
GSList *copy, *l;
for (copy = NULL, l = list; l != NULL; l = l->next)
copy = g_slist_prepend (copy, moo_file_ref (l->data));
return g_slist_reverse (copy);
}
static void files_list_free (GSList **list)
{
g_slist_foreach (*list, (GFunc) moo_file_unref, NULL);
g_slist_free (*list);
*list = NULL;
}
/***************************************************************************/
#define GNOME_HOME_ICON_NAME "gnome-fs-home"
#define GNOME_DESKTOP_ICON_NAME "gnome-fs-desktop"
#define GNOME_TRASH_ICON_NAME "gnome-fs-trash-full"
#define GNOME_DIRECTORY_ICON_NAME "gnome-fs-directory"
#define FILE_ICON_NAME "gnome-fs-regular"
#define BLOCK_DEVICE_ICON_NAME "gnome-fs-blockdev"
#define EXECUTABLE_ICON_NAME "gnome-fs-executable"
#define SYMBOLIC_LINK_ICON_NAME "gnome-fs-symlink"
#define CHARACTER_DEVICE_ICON_NAME "gnome-fs-chardev"
#define FIFO_ICON_NAME "gnome-fs-fifo"
#define SOCKET_ICON_NAME "gnome-fs-socket"
#define NONEXISTENT_ICON_NAME "BROKEN"
#define BROKEN_LINK_ICON_NAME "BROKEN_LINK"
#define BLANK_ICON_NAME ""
static const char *get_default_file_icon (void)
{
return FILE_ICON_NAME;
}
static const char *get_blank_icon (void)
{
return BLANK_ICON_NAME;
}
static const char *get_nonexistent_icon (void)
{
return NONEXISTENT_ICON_NAME;
}
static GdkPixbuf *create_icon_for_mime_type (GtkIconTheme *icon_theme,
const char *mime_type,
int pixel_size)
{
const char *separator;
GString *icon_name;
GdkPixbuf *pixbuf;
separator = strchr (mime_type, '/');
if (!separator)
return NULL;
icon_name = g_string_new ("gnome-mime-");
g_string_append_len (icon_name, mime_type, separator - mime_type);
g_string_append_c (icon_name, '-');
g_string_append (icon_name, separator + 1);
pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str,
pixel_size, 0, NULL);
g_string_free (icon_name, TRUE);
if (pixbuf)
return pixbuf;
icon_name = g_string_new ("gnome-mime-");
g_string_append_len (icon_name, mime_type, separator - mime_type);
pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str,
pixel_size, 0, NULL);
g_string_free (icon_name, TRUE);
return pixbuf;
}
static GdkPixbuf *get_named_icon (const char *name,
GtkWidget *widget,
GtkIconSize size)
{
GtkIconTheme *icon_theme;
GHashTable *all_sizes_cache, *cache;
GdkPixbuf *pixbuf;
if (!name)
return NULL;
icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
all_sizes_cache = g_object_get_data (G_OBJECT (icon_theme), "moo-file-icon-cache");
if (!all_sizes_cache)
{
all_sizes_cache =
g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL,
(GDestroyNotify) g_hash_table_destroy);
g_object_set_data_full (G_OBJECT (icon_theme),
"moo-file-icon-cache",
all_sizes_cache,
(GDestroyNotify) g_hash_table_destroy);
}
cache = g_hash_table_lookup (all_sizes_cache, GINT_TO_POINTER (size));
if (!cache)
{
cache = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_object_unref);
g_hash_table_insert (all_sizes_cache, GINT_TO_POINTER (size), cache);
}
pixbuf = g_hash_table_lookup (cache, name);
if (pixbuf)
return pixbuf;
pixbuf = create_named_icon (icon_theme, name, widget, size);
g_assert (pixbuf != NULL);
g_hash_table_insert (cache, g_strdup (name), pixbuf);
return pixbuf;
}
static GdkPixbuf *create_named_icon (GtkIconTheme *icon_theme,
const char *name,
GtkWidget *widget,
GtkIconSize size)
{
int width, height;
GdkPixbuf *pixbuf;
g_return_val_if_fail (name != NULL, NULL);
if (!strcmp (name, BLANK_ICON_NAME))
{
pixbuf = gtk_widget_render_icon (widget, GTK_STOCK_FILE, size, "");
if (!pixbuf)
g_warning ("%s: could not create a stock icon for %s",
G_STRLOC, GTK_STOCK_FILE);
return pixbuf;
}
if (!strcmp (name, NONEXISTENT_ICON_NAME) ||
!strcmp (name, BROKEN_LINK_ICON_NAME))
{
pixbuf = gtk_widget_render_icon (widget, GTK_STOCK_MISSING_IMAGE, size, "");
if (!pixbuf)
g_warning ("%s: could not create a stock icon for %s",
G_STRLOC, GTK_STOCK_MISSING_IMAGE);
return pixbuf;
}
if (!gtk_icon_size_lookup (size, &width, &height))
if (!gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height))
width = height = 16;
if (!strncmp (name, "MIME-", 5))
{
const char *mime_type = name + 5;
pixbuf = create_icon_for_mime_type (icon_theme, mime_type, width);
if (pixbuf)
return pixbuf;
else
return get_named_icon (FILE_ICON_NAME, widget, size);
}
pixbuf = gtk_icon_theme_load_icon (icon_theme, name, width, 0, NULL);
if (!pixbuf)
pixbuf = create_fallback_icon (icon_theme, name, widget, size);
return pixbuf;
}
static const char *get_folder_icon (const char *path)
{
static const char *home_path = NULL;
static char *desktop_path = NULL;
static char *trash_path = NULL;
if (!path)
return GNOME_DIRECTORY_ICON_NAME;
if (!home_path)
home_path = g_get_home_dir ();
if (!home_path)
return GNOME_DIRECTORY_ICON_NAME;
if (!desktop_path)
desktop_path = g_build_filename (home_path, "Desktop", NULL);
if (!trash_path)
trash_path = g_build_filename (desktop_path, "Trash", NULL);
if (strcmp (home_path, path) == 0)
return GNOME_HOME_ICON_NAME;
else if (strcmp (desktop_path, path) == 0)
return GNOME_DESKTOP_ICON_NAME;
else if (strcmp (trash_path, path) == 0)
return GNOME_TRASH_ICON_NAME;
else
return GNOME_DIRECTORY_ICON_NAME;
}
static GdkPixbuf *create_fallback_icon (GtkIconTheme *icon_theme,
const char *name,
GtkWidget *widget,
GtkIconSize size)
{
const char *stock_name;
GdkPixbuf *pixbuf;
if (!strcmp (name, BLOCK_DEVICE_ICON_NAME))
stock_name = GTK_STOCK_HARDDISK;
else if (!strcmp (name, EXECUTABLE_ICON_NAME))
stock_name = GTK_STOCK_EXECUTE;
else
{
int width, height;
if (!gtk_icon_size_lookup (size, &width, &height))
if (!gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height))
width = height = 16;
pixbuf = gtk_icon_theme_load_icon (icon_theme, FILE_ICON_NAME,
width, 0, NULL);
if (pixbuf)
return pixbuf;
else
stock_name = GTK_STOCK_FILE;
}
pixbuf = gtk_widget_render_icon (widget, stock_name, size, NULL);
if (!pixbuf)
g_warning ("%s: could not create a stock icon for %s",
G_STRLOC, stock_name);
return pixbuf;
}
static const char *get_icon (MooFolder *folder,
MooFile *file)
{
static GHashTable *mime_type_names = NULL;
if (!(file->info & MOO_FILE_EXISTS))
return NONEXISTENT_ICON_NAME;
if (file->info & MOO_FILE_IS_FOLDER)
{
char *path = FILE_PATH (folder, file);
const char *name = get_folder_icon (path);
g_free (path);
return name;
}
if (file->flags & MOO_FILE_HAS_STAT)
{
struct stat *statp = &file->statbuf;
if (S_ISBLK (statp->st_mode))
return BLOCK_DEVICE_ICON_NAME;
else if (S_ISLNK (statp->st_mode))
return BROKEN_LINK_ICON_NAME;
else if (S_ISCHR (statp->st_mode))
return CHARACTER_DEVICE_ICON_NAME;
#ifdef S_ISFIFO
else if (S_ISFIFO (statp->st_mode))
return FIFO_ICON_NAME;
#endif
#ifdef S_ISSOCK
else if (S_ISSOCK (statp->st_mode))
return SOCKET_ICON_NAME;
#endif
}
if (file->flags & MOO_FILE_HAS_MIME_TYPE && file->mime_type)
{
char *name;
if (!mime_type_names)
mime_type_names =
g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
name = g_hash_table_lookup (mime_type_names, file->mime_type);
if (!name)
{
name = g_strdup_printf ("MIME-%s", file->mime_type);
g_hash_table_insert (mime_type_names,
g_strdup (file->mime_type),
name);
}
return name;
}
return FILE_ICON_NAME;
}