thunar-usagebars/thunar/thunar-shortcuts-progress-renderer.c
4aiman 0af7078cfa feat: add disk usage bars to side panel and computer:// view
Side panel devices show a thin progress bar below each drive name,
proportional to disk usage. Bar turns red when usage exceeds 90%.
The computer:// icon view also shows usage bars under volume names.

New files:
- thunar-shortcuts-progress-renderer.c/.h: custom cell renderer
  (extends GtkCellRendererText) that draws text + usage bar

Modified:
- thunar-shortcuts-model: added USAGE_PERCENT column
- thunar-shortcuts-view: uses progress renderer instead of plain text
- thunar-text-renderer: added usage-percent property with bar drawing
- thunar-standard-view: sets usage-percent via cell_data_func for
  computer:// entries using thunar_g_file_get_free_space()
2026-11-03 22:42:48 +03:00

296 lines
9 KiB
C

/* 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 <config.h>
#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,
};
struct _ThunarShortcutsProgressRendererClass
{
GtkCellRendererTextClass __parent__;
};
struct _ThunarShortcutsProgressRenderer
{
GtkCellRendererText __parent__;
gint usage_percent; /* -1 = no bar, 0..100 = percent used */
};
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;
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;
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);
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));
}
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);
}