mc/gnome/gicon.c

539 lines
12 KiB
C

/* Icon loading support for the Midnight Commander
*
* Copyright (C) 1998-2000 The Free Software Foundation
*
* Authors: Miguel de Icaza <miguel@nuclecu.unam.mx>
* Federico Mena <federico@nuclecu.unam.mx>
*/
#include <config.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "util.h"
#include "dialog.h"
#include <gnome.h>
#include "gicon.h"
#include "gmain.h"
/* What kinds of images can an icon set contain */
typedef enum {
ICON_TYPE_PLAIN,
ICON_TYPE_SYMLINK,
ICON_TYPE_STALLED
} IconType;
/* Information for an icon set (plain icon plus overlayed versions) */
typedef struct {
GdkImlibImage *plain; /* Plain version */
GdkImlibImage *symlink; /* Symlink version */
GdkImlibImage *stalled; /* Stalled symlink version */
char *filename; /* Filename for the plain image */
} IconSet;
static int gicon_inited; /* Has the icon system been inited? */
static GHashTable *name_hash; /* Hash from filename -> IconSet */
static GHashTable *image_hash; /* Hash from imlib image -> IconSet */
static GdkImlibImage *symlink_overlay; /* The little symlink overlay */
static GdkImlibImage *stalled_overlay; /* The little stalled symlink overlay */
/* Default icons, guaranteed to exist */
static IconSet *iset_directory;
static IconSet *iset_dirclosed;
static IconSet *iset_executable;
static IconSet *iset_regular;
static IconSet *iset_core;
static IconSet *iset_sock;
static IconSet *iset_fifo;
static IconSet *iset_chardev;
static IconSet *iset_blockdev;
/* Our UID and GID, needed to see if the user can access some files */
static uid_t our_uid;
static gid_t our_gid;
/* Whether we should always use (expensive) metadata lookups for file panels or not */
int we_can_afford_the_speed = 0;
/* Builds a composite of the plain image and the litle symlink icon */
static GdkImlibImage *
build_overlay (GdkImlibImage *plain, GdkImlibImage *overlay)
{
int rowstride;
int overlay_rowstride;
guchar *src, *dest;
int y;
GdkImlibImage *im;
im = gdk_imlib_clone_image (plain);
g_return_val_if_fail(plain, NULL);
g_return_val_if_fail(overlay, NULL);
rowstride = plain->rgb_width * 3;
overlay_rowstride = overlay->rgb_width * 3;
dest = im->rgb_data + ((plain->rgb_height - overlay->rgb_height) * rowstride
+ (plain->rgb_width - overlay->rgb_width) * 3);
src = overlay->rgb_data;
for (y = 0; y < overlay->rgb_height; y++) {
memcpy (dest, src, overlay_rowstride);
dest += rowstride;
src += overlay_rowstride;
}
gdk_imlib_changed_image (im);
return im;
}
/* Ensures that the icon set has the requested image */
static void
ensure_icon_image (IconSet *iset, IconType type)
{
g_assert (iset->plain != NULL);
switch (type) {
case ICON_TYPE_PLAIN:
/* The plain type always exists, so do nothing */
break;
case ICON_TYPE_SYMLINK:
iset->symlink = build_overlay (iset->plain, symlink_overlay);
g_hash_table_insert (image_hash, iset->symlink, iset);
break;
case ICON_TYPE_STALLED:
iset->stalled = build_overlay (iset->plain, stalled_overlay);
g_hash_table_insert (image_hash, iset->stalled, iset);
break;
default:
g_assert_not_reached ();
}
}
static void
compute_scaled_size (int width, int height, int *nwidth, int *nheight)
{
g_return_if_fail (nwidth != NULL);
g_return_if_fail (nheight != NULL);
if (width <= ICON_IMAGE_WIDTH && height <= ICON_IMAGE_HEIGHT) {
*nheight = height;
*nwidth = width;
} else if (width < height) {
*nheight = ICON_IMAGE_HEIGHT;
*nwidth = *nheight * width / height;
} else {
*nwidth = ICON_IMAGE_WIDTH;
*nheight = *nwidth * height / width;
}
}
/* Returns a newly allocated, correctly scaled image */
static GdkImlibImage *
get_scaled_image (GdkImlibImage *orig)
{
GdkImlibImage *im;
int width, height;
g_return_val_if_fail (orig != NULL, NULL);
compute_scaled_size (orig->rgb_width, orig->rgb_height,
&width, &height);
im = gdk_imlib_clone_scaled_image (orig, width, height);
return im;
}
/* Returns the icon set corresponding to the specified image.
* If we create a new IconSet, iset->plain is set to a new scaled
* image, so _WE FREE THE IM PARAMETER_. */
static IconSet *
get_icon_set_from_image (GdkImlibImage *im)
{
IconSet *iset;
g_return_val_if_fail (im != NULL, NULL);
iset = g_hash_table_lookup (image_hash, im);
if (iset)
return iset;
iset = g_new (IconSet, 1);
iset->plain = get_scaled_image (im);
iset->symlink = NULL;
iset->stalled = NULL;
iset->filename = NULL;
/* Insert the icon information into the hash tables */
g_hash_table_remove (image_hash, im);
g_hash_table_insert (image_hash, iset->plain, iset);
gdk_imlib_destroy_image (im);
return iset;
}
/* Returns the icon set corresponding to the specified icon filename, or NULL if
* the file could not be loaded.
*/
static IconSet *
get_icon_set (const char *filename)
{
GdkImlibImage *im;
IconSet *iset;
iset = g_hash_table_lookup (name_hash, filename);
if (iset)
return iset;
im = gdk_imlib_load_image ((char *) filename);
if (!im)
return NULL;
iset = get_icon_set_from_image (im);
im = NULL;
iset->filename = g_strdup (filename);
/* Insert the icon information into the hash tables */
g_hash_table_insert (name_hash, iset->filename, iset);
return iset;
}
/* Convenience function to load one of the default icons and die if this fails */
static IconSet *
get_stock_icon (char *name)
{
char *filename;
IconSet *iset;
filename = g_concat_dir_and_file (ICONDIR, name);
iset = get_icon_set (filename);
g_free (filename);
return iset;
}
/* Convenience function to load one of the default overlays and die if this fails */
static GdkImlibImage *
get_stock_overlay (char *name)
{
char *filename;
GdkImlibImage *im;
filename = g_concat_dir_and_file (ICONDIR, name);
im = gdk_imlib_load_image (filename);
g_free (filename);
return im;
}
/**
* gicon_init:
* @void:
*
* Initializes the icon module.
**/
void
gicon_init (void)
{
if (gicon_inited)
return;
gicon_inited = TRUE;
name_hash = g_hash_table_new (g_str_hash, g_str_equal);
image_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
/* Load the default icons */
iset_directory = get_stock_icon ("i-directory.png");
iset_dirclosed = get_stock_icon ("i-dirclosed.png");
iset_executable = get_stock_icon ("i-executable.png");
iset_regular = get_stock_icon ("i-regular.png");
iset_core = get_stock_icon ("i-core.png");
iset_sock = get_stock_icon ("i-sock.png");
iset_fifo = get_stock_icon ("i-fifo.png");
iset_chardev = get_stock_icon ("i-chardev.png");
iset_blockdev = get_stock_icon ("i-blockdev.png");
/* Load the overlay icons */
symlink_overlay = get_stock_overlay ("i-symlink.png");
stalled_overlay = get_stock_overlay ("i-stalled.png");
if (!iset_directory || !iset_dirclosed || !iset_executable ||
!iset_regular || !iset_core || !iset_fifo || !iset_chardev ||
!iset_blockdev || !symlink_overlay || !stalled_overlay) {
message (1, _("Error"), _("Default set of icons not found, check your installation"));
exit(1);
}
our_uid = getuid ();
our_gid = getgid ();
}
/* Tries to get an icon from the file's metadata information */
static GdkImlibImage *
get_icon_from_metadata (char *filename)
{
int size;
char *buf;
IconSet *iset = NULL;
/* Try the inlined icon */
if (gnome_metadata_get (filename, "icon-inline-png", &size, &buf) == 0) {
GdkImlibImage *im;
im = gdk_imlib_inlined_png_to_image (buf, size);
g_free (buf);
if (im) {
iset = get_icon_set_from_image (im);
im = NULL;
}
}
/* Try the non-inlined icon */
if (!iset && gnome_metadata_get (filename, "icon-filename", &size, &buf) == 0) {
iset = get_icon_set (buf);
g_free (buf);
}
if (iset) {
ensure_icon_image (iset, ICON_TYPE_PLAIN);
return iset->plain;
}
return NULL; /* nothing is available */
}
/* Returns whether we are in the specified group or not */
static int
we_are_in_group (gid_t gid)
{
gid_t *groups;
int ngroups, i;
int retval;
if (our_gid == gid)
return TRUE;
ngroups = getgroups (0, NULL);
if (ngroups == -1 || ngroups == 0)
return FALSE;
groups = g_new (gid_t, ngroups);
ngroups = getgroups (ngroups, groups);
if (ngroups == -1) {
g_free (groups);
return FALSE;
}
retval = FALSE;
for (i = 0; i < ngroups; i++)
if (groups[i] == gid)
retval = TRUE;
g_free (groups);
return retval;
}
/* Returns whether we can access the contents of directory specified by the file entry */
static int
can_access_directory (file_entry *fe)
{
mode_t test_mode;
if (fe->buf.st_uid == our_uid)
test_mode = S_IRUSR | S_IXUSR;
else if (we_are_in_group (fe->buf.st_gid))
test_mode = S_IRGRP | S_IXGRP;
else
test_mode = S_IROTH | S_IXOTH;
return (fe->buf.st_mode & test_mode) == test_mode;
}
/* This is the last resort for getting a file's icon. It uses the file mode
* bits or a hardcoded name.
*/
static IconSet *
get_default_icon (file_entry *fe)
{
mode_t mode = fe->buf.st_mode;
if (S_ISSOCK (mode))
return iset_sock;
if (S_ISCHR (mode))
return iset_chardev;
if (S_ISBLK (mode))
return iset_blockdev;
if (S_ISFIFO (mode))
return iset_fifo;
if (!S_ISLNK (mode) && is_exe (mode))
return iset_executable;
if (!strcmp (fe->fname, "core") || !strcmp (extension (fe->fname), "core"))
return iset_core;
return iset_regular; /* boooo */
}
/**
* gicon_get_icon_for_file:
* @directory: The directory on which the file resides.
* @fe: The file entry that represents the file.
* @do_quick: Whether the function should try to use (expensive) metadata information.
*
* Returns the appropriate icon for the specified file.
*
* Return value: The icon for the specified file.
**/
GdkImlibImage *
gicon_get_icon_for_file (char *directory, file_entry *fe, gboolean do_quick)
{
IconSet *iset;
mode_t mode;
const char *mime_type;
gboolean is_user_set = FALSE;
g_return_val_if_fail (directory != NULL, NULL);
g_return_val_if_fail (fe != NULL, NULL);
gicon_init ();
mode = fe->buf.st_mode;
/* 1. First try the user's settings */
if (!do_quick || we_can_afford_the_speed) {
char *full_name;
GdkImlibImage *im;
full_name = g_concat_dir_and_file (directory, fe->fname);
im = get_icon_from_metadata (full_name);
g_free (full_name);
if (im) {
iset = get_icon_set_from_image (im);
im = NULL;
is_user_set = TRUE;
goto add_link;
}
}
/* 2. Before we do anything else, make sure the
* pointed-to file exists if a link */
if (S_ISLNK (mode) && fe->f.stalled_link) {
const char *icon_name;
icon_name = gnome_unconditional_pixmap_file ("gnome-warning.png");
if (icon_name) {
iset = get_icon_set (icon_name);
if (iset)
goto add_link;
}
}
/* 3. See if it is a directory */
if (S_ISDIR (mode)) {
if (can_access_directory (fe))
iset = iset_directory;
else
iset = iset_dirclosed;
goto add_link;
}
/* 4. Try MIME-types */
if (use_magic) {
char *full_name;
full_name = g_concat_dir_and_file (directory, fe->fname);
mime_type = gnome_mime_type_or_default_of_file (full_name, NULL);
g_free (full_name);
}
else
mime_type = gnome_mime_type_or_default (fe->fname, NULL);
if (mime_type) {
const char *icon_name;
icon_name = gnome_mime_get_value (mime_type, "icon-filename");
if (icon_name) {
iset = get_icon_set (icon_name);
if (iset)
goto add_link;
}
}
/* 5. Get an icon from the file mode */
iset = get_default_icon (fe);
add_link:
g_return_val_if_fail (iset, NULL);
if (S_ISLNK (mode)) {
if (fe->f.link_to_dir && !is_user_set)
iset = iset_directory;
if (fe->f.stalled_link) {
ensure_icon_image (iset, ICON_TYPE_STALLED);
return iset->stalled;
} else {
ensure_icon_image (iset, ICON_TYPE_SYMLINK);
return iset->symlink;
}
} else {
ensure_icon_image (iset, ICON_TYPE_PLAIN);
return iset->plain;
}
}
/**
* gicon_get_filename_for_icon:
* @image: An icon image loaded by the icon module.
*
* Queries the icon database for the icon filename that corresponds to the
* specified image.
*
* Return value: The filename that contains the icon for the specified image.
**/
const char *
gicon_get_filename_for_icon (GdkImlibImage *image)
{
IconSet *iset;
g_return_val_if_fail (image != NULL, NULL);
gicon_init ();
iset = g_hash_table_lookup (image_hash, image);
g_assert (iset != NULL);
return iset->filename;
}