/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2026 * * 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 #endif #include "thunar/thunar-shortcuts-progress-renderer.h" /* Height of the usage bar in pixels */ #define BAR_HEIGHT 4 /* Gap between the text and the bar */ #define BAR_TOP_PAD 2 /* Corner radius */ #define BAR_RADIUS 2 enum { PROP_0, PROP_USAGE_PERCENT, PROP_IS_SEPARATOR, }; struct _ThunarShortcutsProgressRendererClass { GtkCellRendererTextClass __parent__; }; struct _ThunarShortcutsProgressRenderer { GtkCellRendererText __parent__; gint usage_percent; /* -1 = no bar, 0..100 = percent used */ gboolean is_separator; }; G_DEFINE_TYPE (ThunarShortcutsProgressRenderer, thunar_shortcuts_progress_renderer, GTK_TYPE_CELL_RENDERER_TEXT) static void thunar_shortcuts_progress_renderer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ThunarShortcutsProgressRenderer *renderer = THUNAR_SHORTCUTS_PROGRESS_RENDERER (object); switch (prop_id) { case PROP_USAGE_PERCENT: g_value_set_int (value, renderer->usage_percent); break; case PROP_IS_SEPARATOR: g_value_set_boolean (value, renderer->is_separator); break; default: G_OBJECT_CLASS (thunar_shortcuts_progress_renderer_parent_class)->get_property (object, prop_id, value, pspec); break; } } static void thunar_shortcuts_progress_renderer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ThunarShortcutsProgressRenderer *renderer = THUNAR_SHORTCUTS_PROGRESS_RENDERER (object); switch (prop_id) { case PROP_USAGE_PERCENT: renderer->usage_percent = g_value_get_int (value); break; case PROP_IS_SEPARATOR: renderer->is_separator = g_value_get_boolean (value); break; default: G_OBJECT_CLASS (thunar_shortcuts_progress_renderer_parent_class)->set_property (object, prop_id, value, pspec); break; } } static void thunar_shortcuts_progress_renderer_get_preferred_height (GtkCellRenderer *cell, GtkWidget *widget, gint *minimum, gint *natural) { ThunarShortcutsProgressRenderer *renderer = THUNAR_SHORTCUTS_PROGRESS_RENDERER (cell); gint min_h = 0, nat_h = 0; /* let the parent (GtkCellRendererText) calculate text height */ GTK_CELL_RENDERER_CLASS (thunar_shortcuts_progress_renderer_parent_class)->get_preferred_height (cell, widget, &min_h, &nat_h); /* add space for the bar if usage data is available */ if (renderer->usage_percent >= 0) { min_h += BAR_TOP_PAD + BAR_HEIGHT; nat_h += BAR_TOP_PAD + BAR_HEIGHT; } if (minimum != NULL) *minimum = min_h; if (natural != NULL) *natural = nat_h; } static void thunar_shortcuts_progress_renderer_render (GtkCellRenderer *cell, cairo_t *cr, GtkWidget *widget, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags) { ThunarShortcutsProgressRenderer *renderer = THUNAR_SHORTCUTS_PROGRESS_RENDERER (cell); GtkStyleContext *context; GdkRGBA bar_bg; GdkRGBA bar_fg; gint text_height; gint bar_y; gint bar_width; gint xpad, ypad; GdkRectangle text_area; gtk_cell_renderer_get_padding (cell, &xpad, &ypad); /* draw a horizontal line for separator rows */ if (renderer->is_separator) { GdkRGBA fg_color; context = gtk_widget_get_style_context (widget); gtk_style_context_get_color (context, gtk_style_context_get_state (context), &fg_color); fg_color.alpha = 0.3; cairo_save (cr); gdk_cairo_set_source_rgba (cr, &fg_color); cairo_set_line_width (cr, 1.0); cairo_move_to (cr, cell_area->x + xpad, cell_area->y + cell_area->height / 2 + 0.5); cairo_line_to (cr, cell_area->x + cell_area->width - xpad, cell_area->y + cell_area->height / 2 + 0.5); cairo_stroke (cr); cairo_restore (cr); return; } if (renderer->usage_percent >= 0) { /* calculate how much space the bar needs, then shrink cell_area for text */ text_area = *cell_area; text_area.height -= (BAR_TOP_PAD + BAR_HEIGHT); /* let the parent draw the text in the reduced area */ GTK_CELL_RENDERER_CLASS (thunar_shortcuts_progress_renderer_parent_class)->render ( cell, cr, widget, background_area, &text_area, flags); /* draw the usage bar below the text */ text_height = text_area.height; bar_y = cell_area->y + ypad + text_height + BAR_TOP_PAD; bar_width = cell_area->width - 2 * xpad; if (bar_width <= 0) return; context = gtk_widget_get_style_context (widget); /* bar background: semi-transparent grey */ bar_bg.red = 0.5; bar_bg.green = 0.5; bar_bg.blue = 0.5; bar_bg.alpha = 0.3; /* bar foreground color: red for >90%, otherwise theme selection color */ if (renderer->usage_percent > 90) { bar_fg.red = 0.85; bar_fg.green = 0.2; bar_fg.blue = 0.2; bar_fg.alpha = 0.9; } else { if (!gtk_style_context_lookup_color (context, "theme_selected_bg_color", &bar_fg) || bar_fg.alpha < 0.01) { /* fallback blue */ bar_fg.red = 0.3; bar_fg.green = 0.5; bar_fg.blue = 0.8; } bar_fg.alpha = 0.9; } cairo_save (cr); /* draw background track (rounded rectangle) */ { gdouble x = cell_area->x + xpad; gdouble y = bar_y; gdouble w = bar_width; gdouble h = BAR_HEIGHT; gdouble r = BAR_RADIUS; cairo_new_sub_path (cr); cairo_arc (cr, x + w - r, y + r, r, -G_PI_2, 0); cairo_arc (cr, x + w - r, y + h - r, r, 0, G_PI_2); cairo_arc (cr, x + r, y + h - r, r, G_PI_2, G_PI); cairo_arc (cr, x + r, y + r, r, G_PI, 3 * G_PI_2); cairo_close_path (cr); } gdk_cairo_set_source_rgba (cr, &bar_bg); cairo_fill (cr); /* draw filled portion (rounded rectangle) */ if (renderer->usage_percent > 0) { gdouble x = cell_area->x + xpad; gdouble y = bar_y; gdouble w = bar_width * renderer->usage_percent / 100.0; gdouble h = BAR_HEIGHT; gdouble r = BAR_RADIUS; if (w < 2 * r) r = w / 2.0; if (r < 0.5) r = 0; cairo_new_sub_path (cr); cairo_arc (cr, x + w - r, y + r, r, -G_PI_2, 0); cairo_arc (cr, x + w - r, y + h - r, r, 0, G_PI_2); cairo_arc (cr, x + r, y + h - r, r, G_PI_2, G_PI); cairo_arc (cr, x + r, y + r, r, G_PI, 3 * G_PI_2); cairo_close_path (cr); gdk_cairo_set_source_rgba (cr, &bar_fg); cairo_fill (cr); } cairo_restore (cr); } else { /* no usage data: just draw the text normally */ GTK_CELL_RENDERER_CLASS (thunar_shortcuts_progress_renderer_parent_class)->render ( cell, cr, widget, background_area, cell_area, flags); } } static void thunar_shortcuts_progress_renderer_class_init (ThunarShortcutsProgressRendererClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); gobject_class->get_property = thunar_shortcuts_progress_renderer_get_property; gobject_class->set_property = thunar_shortcuts_progress_renderer_set_property; cell_class->get_preferred_height = thunar_shortcuts_progress_renderer_get_preferred_height; cell_class->render = thunar_shortcuts_progress_renderer_render; g_object_class_install_property (gobject_class, PROP_USAGE_PERCENT, g_param_spec_int ("usage-percent", "usage-percent", "Disk usage percentage (0-100, -1 = hidden)", -1, 100, -1, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_IS_SEPARATOR, g_param_spec_boolean ("is-separator", "is-separator", "Draw a horizontal line instead of text", FALSE, G_PARAM_READWRITE)); } static void thunar_shortcuts_progress_renderer_init (ThunarShortcutsProgressRenderer *renderer) { renderer->usage_percent = -1; /* match the original text renderer settings */ g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL); } GtkCellRenderer * thunar_shortcuts_progress_renderer_new (void) { return g_object_new (THUNAR_TYPE_SHORTCUTS_PROGRESS_RENDERER, NULL); }