thunar-usagebars/thunar/thunar-icon-renderer.c
4aiman e969f94310 thunar 4.20.2 upstream
Tree of tag thunar-4.20.2 (commit 11a519f) from https://gitlab.xfce.org/xfce/thunar.
Upstream history is not included here to keep the repository small.
2026-11-03 22:42:48 +03:00

755 lines
26 KiB
C

/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
*
* 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
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "thunar/thunar-clipboard-manager.h"
#include "thunar/thunar-gdk-extensions.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-icon-factory.h"
#include "thunar/thunar-icon-renderer.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-util.h"
enum
{
PROP_0,
PROP_DROP_FILE,
PROP_FILE,
PROP_EMBLEMS,
PROP_FOLLOW_STATE,
PROP_SIZE,
PROP_HIGHLIGHT_COLOR,
PROP_ROUNDED_CORNERS,
PROP_HIGHLIGHTING_ENABLED,
PROP_IMAGE_PREVIEW_ENABLED,
PROP_USE_SYMBOLIC_ICONS,
};
static void
thunar_icon_renderer_finalize (GObject *object);
static void
thunar_icon_renderer_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void
thunar_icon_renderer_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void
thunar_icon_renderer_get_preferred_width (GtkCellRenderer *renderer,
GtkWidget *widget,
gint *minimum,
gint *natural);
static void
thunar_icon_renderer_get_preferred_height (GtkCellRenderer *renderer,
GtkWidget *widget,
gint *minimum,
gint *natural);
static void
thunar_icon_renderer_render (GtkCellRenderer *renderer,
cairo_t *cr,
GtkWidget *widget,
const GdkRectangle *background_area,
const GdkRectangle *cell_area,
GtkCellRendererState flags);
G_DEFINE_TYPE (ThunarIconRenderer, thunar_icon_renderer, GTK_TYPE_CELL_RENDERER)
static void
thunar_icon_renderer_class_init (ThunarIconRendererClass *klass)
{
GtkCellRendererClass *gtkcell_renderer_class;
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = thunar_icon_renderer_finalize;
gobject_class->get_property = thunar_icon_renderer_get_property;
gobject_class->set_property = thunar_icon_renderer_set_property;
gtkcell_renderer_class = GTK_CELL_RENDERER_CLASS (klass);
gtkcell_renderer_class->get_preferred_width = thunar_icon_renderer_get_preferred_width;
gtkcell_renderer_class->get_preferred_height = thunar_icon_renderer_get_preferred_height;
gtkcell_renderer_class->render = thunar_icon_renderer_render;
/**
* ThunarIconRenderer:drop-file:
*
* The file which should be rendered in the drop
* accept state.
**/
g_object_class_install_property (gobject_class,
PROP_DROP_FILE,
g_param_spec_object ("drop-file",
"drop-file",
"drop-file",
THUNAR_TYPE_FILE,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:file:
*
* The file whose icon to render.
**/
g_object_class_install_property (gobject_class,
PROP_FILE,
g_param_spec_object ("file", "file", "file",
THUNAR_TYPE_FILE,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:emblems:
*
* Specifies whether to render emblems in addition to the file icons.
**/
g_object_class_install_property (gobject_class,
PROP_EMBLEMS,
g_param_spec_boolean ("emblems",
"emblems",
"emblems",
TRUE,
G_PARAM_CONSTRUCT | EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:follow-state:
*
* Specifies whether the icon renderer should render icons
* based on the selection state of the items. This is necessary
* for #ExoIconView, which doesn't draw any item state indicators
* itself.
**/
g_object_class_install_property (gobject_class,
PROP_FOLLOW_STATE,
g_param_spec_boolean ("follow-state",
"follow-state",
"follow-state",
FALSE,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:size:
*
* The size at which icons should be rendered by this
* #ThunarIconRenderer instance.
**/
g_object_class_install_property (gobject_class,
PROP_SIZE,
g_param_spec_enum ("size", "size", "size",
THUNAR_TYPE_ICON_SIZE,
THUNAR_ICON_SIZE_32,
G_PARAM_CONSTRUCT | EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:highlight-color:
*
* The color with which the cell should be highlighted.
**/
g_object_class_install_property (gobject_class,
PROP_HIGHLIGHT_COLOR,
g_param_spec_string ("highlight-color", "highlight-color", "highlight-color",
NULL,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:rounded-corners:
*
* Determines if the cell should be clipped to rounded-corners.
* Useful when highlighting is enabled & a highlight color is set.
**/
g_object_class_install_property (gobject_class,
PROP_ROUNDED_CORNERS,
g_param_spec_boolean ("rounded-corners", "rounded-corners", "rounded-corners",
FALSE,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:highlighting-enabled:
*
* Determines if the cell background should be drawn with highlight color.
**/
g_object_class_install_property (gobject_class,
PROP_HIGHLIGHTING_ENABLED,
g_param_spec_boolean ("highlighting-enabled", "highlighting-enabled", "highlighting-enabled",
FALSE,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:image-preview-enabled:
*
* Determines if as well xxl-thumbnails should be requested
**/
g_object_class_install_property (gobject_class,
PROP_IMAGE_PREVIEW_ENABLED,
g_param_spec_boolean ("image-preview-enabled", "image-preview-enabled", "image-preview-enabled",
FALSE,
EXO_PARAM_READWRITE));
/**
* ThunarIconRenderer:use_symbolic_icons:
*
* Whether to use symbolic icons.
**/
g_object_class_install_property (gobject_class,
PROP_USE_SYMBOLIC_ICONS,
g_param_spec_boolean ("use-symbolic-icons",
"use-symbolic-icons",
"use-symbolic-icons",
FALSE,
EXO_PARAM_READWRITE));
}
static void
thunar_icon_renderer_init (ThunarIconRenderer *icon_renderer)
{
/* use 1px padding */
gtk_cell_renderer_set_padding (GTK_CELL_RENDERER (icon_renderer), 1, 1);
}
static void
thunar_icon_renderer_finalize (GObject *object)
{
ThunarIconRenderer *icon_renderer = THUNAR_ICON_RENDERER (object);
/* free the icon data */
if (G_UNLIKELY (icon_renderer->drop_file != NULL))
g_object_unref (G_OBJECT (icon_renderer->drop_file));
if (G_LIKELY (icon_renderer->file != NULL))
g_object_unref (G_OBJECT (icon_renderer->file));
g_free (icon_renderer->highlight_color);
(*G_OBJECT_CLASS (thunar_icon_renderer_parent_class)->finalize) (object);
}
static void
thunar_icon_renderer_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ThunarIconRenderer *icon_renderer = THUNAR_ICON_RENDERER (object);
switch (prop_id)
{
case PROP_DROP_FILE:
g_value_set_object (value, icon_renderer->drop_file);
break;
case PROP_FILE:
g_value_set_object (value, icon_renderer->file);
break;
case PROP_EMBLEMS:
g_value_set_boolean (value, icon_renderer->emblems);
break;
case PROP_FOLLOW_STATE:
g_value_set_boolean (value, icon_renderer->follow_state);
break;
case PROP_SIZE:
g_value_set_enum (value, icon_renderer->size);
break;
case PROP_HIGHLIGHT_COLOR:
g_value_set_string (value, icon_renderer->highlight_color);
break;
case PROP_ROUNDED_CORNERS:
g_value_set_boolean (value, icon_renderer->rounded_corners);
break;
case PROP_HIGHLIGHTING_ENABLED:
g_value_set_boolean (value, icon_renderer->highlighting_enabled);
break;
case PROP_IMAGE_PREVIEW_ENABLED:
g_value_set_boolean (value, icon_renderer->highlighting_enabled);
break;
case PROP_USE_SYMBOLIC_ICONS:
g_value_set_boolean (value, icon_renderer->use_symbolic_icons);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
thunar_icon_renderer_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ThunarIconRenderer *icon_renderer = THUNAR_ICON_RENDERER (object);
switch (prop_id)
{
case PROP_DROP_FILE:
if (G_LIKELY (icon_renderer->drop_file != NULL))
g_object_unref (G_OBJECT (icon_renderer->drop_file));
icon_renderer->drop_file = (gpointer) g_value_dup_object (value);
break;
case PROP_FILE:
if (G_LIKELY (icon_renderer->file != NULL))
g_object_unref (G_OBJECT (icon_renderer->file));
icon_renderer->file = (gpointer) g_value_dup_object (value);
break;
case PROP_EMBLEMS:
icon_renderer->emblems = g_value_get_boolean (value);
break;
case PROP_FOLLOW_STATE:
icon_renderer->follow_state = g_value_get_boolean (value);
break;
case PROP_SIZE:
icon_renderer->size = g_value_get_enum (value);
break;
case PROP_HIGHLIGHT_COLOR:
g_free (icon_renderer->highlight_color);
icon_renderer->highlight_color = g_value_dup_string (value);
break;
case PROP_ROUNDED_CORNERS:
icon_renderer->rounded_corners = g_value_get_boolean (value);
break;
case PROP_HIGHLIGHTING_ENABLED:
icon_renderer->highlighting_enabled = g_value_get_boolean (value);
break;
case PROP_IMAGE_PREVIEW_ENABLED:
icon_renderer->image_preview_enabled = g_value_get_boolean (value);
break;
case PROP_USE_SYMBOLIC_ICONS:
icon_renderer->use_symbolic_icons = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
thunar_icon_renderer_get_preferred_width (GtkCellRenderer *renderer,
GtkWidget *widget,
gint *minimum,
gint *natural)
{
ThunarIconRenderer *icon_renderer = THUNAR_ICON_RENDERER (renderer);
int xpad;
gtk_cell_renderer_get_padding (renderer, &xpad, NULL);
if (G_LIKELY (minimum))
*minimum = (gint) xpad * 2 + icon_renderer->size;
if (G_LIKELY (natural))
*natural = (gint) xpad * 2 + icon_renderer->size;
}
static void
thunar_icon_renderer_get_preferred_height (GtkCellRenderer *renderer,
GtkWidget *widget,
gint *minimum,
gint *natural)
{
ThunarIconRenderer *icon_renderer = THUNAR_ICON_RENDERER (renderer);
int ypad;
gtk_cell_renderer_get_padding (renderer, NULL, &ypad);
if (G_LIKELY (minimum))
*minimum = (gint) ypad * 2 + icon_renderer->size;
if (G_LIKELY (natural))
*natural = (gint) ypad * 2 + icon_renderer->size;
}
static void
thunar_icon_renderer_color_insensitive (cairo_t *cr,
GtkWidget *widget)
{
cairo_pattern_t *source;
GdkRGBA *color;
GtkStyleContext *context = gtk_widget_get_style_context (widget);
cairo_save (cr);
source = cairo_pattern_reference (cairo_get_source (cr));
gtk_style_context_get (context, GTK_STATE_FLAG_INSENSITIVE, GTK_STYLE_PROPERTY_COLOR, &color, NULL);
gdk_cairo_set_source_rgba (cr, color);
gdk_rgba_free (color);
cairo_set_operator (cr, CAIRO_OPERATOR_MULTIPLY);
cairo_mask (cr, source);
cairo_pattern_destroy (source);
cairo_restore (cr);
}
static void
thunar_icon_renderer_color_selected (cairo_t *cr,
GtkWidget *widget)
{
cairo_pattern_t *source;
GtkStateFlags state;
GdkRGBA *color;
GtkStyleContext *context = gtk_widget_get_style_context (widget);
cairo_save (cr);
source = cairo_pattern_reference (cairo_get_source (cr));
state = gtk_widget_has_focus (widget) ? GTK_STATE_FLAG_SELECTED : GTK_STATE_FLAG_ACTIVE;
gtk_style_context_get (context, state, GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &color, NULL);
gdk_cairo_set_source_rgba (cr, color);
gdk_rgba_free (color);
cairo_set_operator (cr, CAIRO_OPERATOR_MULTIPLY);
cairo_mask (cr, source);
cairo_pattern_destroy (source);
cairo_restore (cr);
}
static void
thunar_icon_renderer_color_lighten (cairo_t *cr,
GtkWidget *widget)
{
cairo_pattern_t *source;
cairo_save (cr);
source = cairo_pattern_reference (cairo_get_source (cr));
cairo_set_source_rgb (cr, .15, .15, .15);
cairo_set_operator (cr, CAIRO_OPERATOR_COLOR_DODGE);
cairo_mask (cr, source);
cairo_pattern_destroy (source);
cairo_restore (cr);
}
static void
thunar_icon_renderer_render (GtkCellRenderer *renderer,
cairo_t *cr,
GtkWidget *widget,
const GdkRectangle *background_area,
const GdkRectangle *cell_area,
GtkCellRendererState flags)
{
ThunarClipboardManager *clipboard;
ThunarFileIconState icon_state;
ThunarIconRenderer *icon_renderer = THUNAR_ICON_RENDERER (renderer);
ThunarIconFactory *icon_factory;
GtkIconTheme *icon_theme;
GtkStyleContext *context = NULL;
GdkRectangle emblem_area;
GdkRectangle icon_area;
GdkRectangle clip_area;
GdkPixbuf *emblem;
GdkPixbuf *icon;
GdkPixbuf *temp;
GList *emblems;
GList *lp;
gint scale_factor;
gint position;
gdouble alpha;
gint emblem_size;
gboolean color_selected;
gboolean color_lighten;
gboolean is_expanded;
if (G_UNLIKELY (icon_renderer->file == NULL))
return;
if (G_UNLIKELY (!gdk_cairo_get_clip_rectangle (cr, &clip_area)))
return;
g_object_get (renderer, "is-expanded", &is_expanded, NULL);
if (THUNAR_ICON_RENDERER (renderer)->highlighting_enabled)
thunar_util_clip_view_background (renderer, cr, background_area, widget, flags);
/* determine the icon state */
icon_state = (icon_renderer->drop_file != icon_renderer->file)
? is_expanded
? THUNAR_FILE_ICON_STATE_OPEN
: THUNAR_FILE_ICON_STATE_DEFAULT
: THUNAR_FILE_ICON_STATE_DROP;
/* load the main icon */
icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme);
scale_factor = gtk_widget_get_scale_factor (widget);
/* load thumbnail for supported file type */
if (thunar_icon_factory_get_show_thumbnail (icon_factory, icon_renderer->file))
thunar_file_request_thumbnail (icon_renderer->file, thunar_icon_size_to_thumbnail_size (icon_renderer->size * scale_factor));
/* load additional thumbnail for image preview */
if (icon_renderer->image_preview_enabled)
thunar_file_request_thumbnail (icon_renderer->file, THUNAR_THUMBNAIL_SIZE_XX_LARGE);
/* get style context for symbolic icon */
if (icon_renderer->use_symbolic_icons)
context = gtk_widget_get_style_context (widget);
icon = thunar_icon_factory_load_file_icon (icon_factory, icon_renderer->file, icon_state,
icon_renderer->size, scale_factor,
icon_renderer->use_symbolic_icons, context);
if (G_UNLIKELY (icon == NULL))
{
g_object_unref (G_OBJECT (icon_factory));
return;
}
/* pre-light the item if we're dragging about it */
if (G_UNLIKELY (icon_state == THUNAR_FILE_ICON_STATE_DROP))
flags |= GTK_CELL_RENDERER_PRELIT;
/* determine the real icon size */
icon_area.width = gdk_pixbuf_get_width (icon) / scale_factor;
icon_area.height = gdk_pixbuf_get_height (icon) / scale_factor;
/* scale down the icon on-demand */
if (G_UNLIKELY (icon_area.width > cell_area->width || icon_area.height > cell_area->height))
{
/* scale down to fit */
temp = exo_gdk_pixbuf_scale_down (icon, TRUE, MAX (1, cell_area->width * scale_factor), MAX (1, cell_area->height * scale_factor));
g_object_unref (G_OBJECT (icon));
icon = temp;
/* determine the icon dimensions again */
icon_area.width = gdk_pixbuf_get_width (icon) / scale_factor;
icon_area.height = gdk_pixbuf_get_height (icon) / scale_factor;
}
icon_area.x = cell_area->x + (cell_area->width - icon_area.width) / 2;
icon_area.y = cell_area->y + (cell_area->height - icon_area.height) / 2;
/* bools for cairo transformations */
color_selected = (flags & GTK_CELL_RENDERER_SELECTED) != 0 && icon_renderer->follow_state;
color_lighten = (flags & GTK_CELL_RENDERER_PRELIT) != 0 && icon_renderer->follow_state;
/* check whether the icon is affected by the expose event */
if (gdk_rectangle_intersect (&clip_area, &icon_area, NULL))
{
/* use a translucent icon to represent cutted and hidden files to the user */
clipboard = thunar_clipboard_manager_get_for_display (gtk_widget_get_display (widget));
if (thunar_clipboard_manager_has_cutted_file (clipboard, icon_renderer->file) && thunar_file_is_writable (icon_renderer->file))
{
/* 50% translucent for cutted files */
alpha = 0.50;
}
else if (thunar_file_is_hidden (icon_renderer->file))
{
/* 75% translucent for hidden files */
alpha = 0.75;
}
else
{
alpha = 1.00;
}
g_object_unref (G_OBJECT (clipboard));
/* render the invalid parts of the icon */
thunar_gdk_cairo_set_source_pixbuf (cr, icon, icon_area.x, icon_area.y, scale_factor);
cairo_paint_with_alpha (cr, alpha);
/* check if we should render an insensitive icon */
if (G_UNLIKELY (gtk_widget_get_state_flags (widget) == GTK_STATE_FLAG_INSENSITIVE || !gtk_cell_renderer_get_sensitive (renderer)))
thunar_icon_renderer_color_insensitive (cr, widget);
/* paint the lighten mask */
if (color_lighten)
thunar_icon_renderer_color_lighten (cr, widget);
/* paint the selected mask */
if (color_selected)
thunar_icon_renderer_color_selected (cr, widget);
}
/* release the file's icon */
g_object_unref (G_OBJECT (icon));
/* check if we should render emblems as well */
if (G_LIKELY (icon_renderer->emblems))
{
/* display the primary emblem as well (if any) */
emblems = thunar_file_get_emblem_names (icon_renderer->file);
if (G_UNLIKELY (emblems != NULL))
{
/* render up to MAX_EMBLEMS_PER_FILE emblems */
for (lp = emblems, position = 0; lp != NULL && position < MAX_EMBLEMS_PER_FILE; lp = lp->next)
{
/* calculate the emblem size */
emblem_size = MIN ((2 * icon_renderer->size) / 4, 32);
/* check if we have the emblem in the icon theme */
emblem = thunar_icon_factory_load_icon (icon_factory, lp->data, emblem_size, scale_factor, FALSE,
icon_renderer->use_symbolic_icons, context);
if (G_UNLIKELY (emblem == NULL))
continue;
/* determine the dimensions of the emblem */
emblem_area.width = gdk_pixbuf_get_width (emblem) / scale_factor;
emblem_area.height = gdk_pixbuf_get_height (emblem) / scale_factor;
/* shrink insane emblems */
if (G_UNLIKELY (MAX (emblem_area.width, emblem_area.height) > emblem_size))
{
/* scale down the emblem */
temp = exo_gdk_pixbuf_scale_ratio (emblem, emblem_size * scale_factor);
g_object_unref (G_OBJECT (emblem));
emblem = temp;
/* determine the size again */
emblem_area.width = gdk_pixbuf_get_width (emblem) / scale_factor;
emblem_area.height = gdk_pixbuf_get_height (emblem) / scale_factor;
}
/* determine a good position for the emblem, depending on the position index */
switch (position)
{
case 0: /* right/bottom */
emblem_area.x = MIN (icon_area.x + icon_area.width - emblem_area.width / 2,
cell_area->x + cell_area->width - emblem_area.width);
emblem_area.y = MIN (icon_area.y + icon_area.height - emblem_area.height / 2,
cell_area->y + cell_area->height - emblem_area.height);
break;
case 1: /* left/bottom */
emblem_area.x = MAX (icon_area.x - emblem_area.width / 2,
cell_area->x);
emblem_area.y = MIN (icon_area.y + icon_area.height - emblem_area.height / 2,
cell_area->y + cell_area->height - emblem_area.height);
break;
case 2: /* left/top */
emblem_area.x = MAX (icon_area.x - emblem_area.width / 2,
cell_area->x);
emblem_area.y = MAX (icon_area.y - emblem_area.height / 2,
cell_area->y);
break;
case 3: /* right/top */
emblem_area.x = MIN (icon_area.x + icon_area.width - emblem_area.width / 2,
cell_area->x + cell_area->width - emblem_area.width);
emblem_area.y = MAX (icon_area.y - emblem_area.height / 2,
cell_area->y);
break;
default:
_thunar_assert_not_reached ();
}
/* render the emblem */
if (gdk_rectangle_intersect (&clip_area, &emblem_area, NULL))
{
/* render the invalid parts of the icon */
thunar_gdk_cairo_set_source_pixbuf (cr, emblem, emblem_area.x, emblem_area.y, scale_factor);
cairo_paint (cr);
/* paint the lighten mask */
if (color_lighten)
thunar_icon_renderer_color_lighten (cr, widget);
/* paint the selected mask */
if (color_selected)
thunar_icon_renderer_color_selected (cr, widget);
}
/* release the emblem */
g_object_unref (G_OBJECT (emblem));
/* advance the position index */
++position;
}
/* release the emblem name list */
g_list_free_full (emblems, g_free);
}
}
/* release our reference on the icon factory */
g_object_unref (G_OBJECT (icon_factory));
}
/**
* thunar_icon_renderer_new:
*
* Creates a new #ThunarIconRenderer. Adjust rendering
* parameters using object properties. Object properties can be
* set globally with #g_object_set. Also, with #GtkTreeViewColumn,
* you can bind a property to a value in a #GtkTreeModel.
*
* Return value: the newly allocated #ThunarIconRenderer.
**/
GtkCellRenderer *
thunar_icon_renderer_new (void)
{
return g_object_new (THUNAR_TYPE_ICON_RENDERER, NULL);
}