Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ FILE(GLOB SOURCE_FILES
"dtgtk/gradientslider.c"
"dtgtk/icon.c"
"dtgtk/paint.c"
"dtgtk/paint_cell.c"
"dtgtk/range.c"
"dtgtk/resetlabel.c"
"dtgtk/sidepanel.c"
Expand Down
26 changes: 26 additions & 0 deletions src/dtgtk/paint.c
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,32 @@ void dtgtk_cairo_paint_help(cairo_t *cr, const gint x, const gint y, const gint
FINISH
}

void dtgtk_cairo_paint_info(cairo_t *cr, const gint x, const gint y, const gint w, const gint h, gint flags, void *data)
{
PREAMBLE(0.95, 1, 0, 0)

// dot (filled so it reads as a tittle, not a tiny ring)
cairo_arc(cr, 0.5, 0.22, 0.05, 0.0, 2.0 * M_PI);
cairo_fill(cr);

// stem
cairo_move_to(cr, 0.5, 0.40);
cairo_line_to(cr, 0.5, 0.72);
// top serif (asymmetric, ends at stem centreline)
cairo_move_to(cr, 0.35, 0.40);
cairo_line_to(cr, 0.50, 0.40);
// bottom serif
cairo_move_to(cr, 0.35, 0.72);
cairo_line_to(cr, 0.65, 0.72);
// outer circle
cairo_new_sub_path(cr);
cairo_arc(cr, 0.5, 0.5, 0.45, 0.0, 2.0 * M_PI);

cairo_stroke(cr);

FINISH
}

void dtgtk_cairo_paint_grouping(cairo_t *cr, const gint x, const gint y, const gint w, const gint h, const gint flags, void *data)
{
PREAMBLE(1, 1, 0, 0)
Expand Down
2 changes: 2 additions & 0 deletions src/dtgtk/paint.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ void dtgtk_cairo_paint_messages(cairo_t *cr, gint x, gint y, gint w, gint h, gin
void dtgtk_cairo_paint_styles(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
/** paint the ? help label */
void dtgtk_cairo_paint_help(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
/** paint the i info label */
void dtgtk_cairo_paint_info(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
/** paint the grouping icon. */
void dtgtk_cairo_paint_grouping(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data);
/** paint the preferences wheel. */
Expand Down
122 changes: 122 additions & 0 deletions src/dtgtk/paint_cell.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
This file is part of darktable,
Copyright (C) 2026 darktable developers.

darktable 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 3 of the License, or
(at your option) any later version.

darktable 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 darktable. If not, see <http://www.gnu.org/licenses/>.
*/

#include "dtgtk/paint_cell.h"
#include "common/darktable.h"
#include "gui/gtk.h"

G_DEFINE_TYPE(GtkDarktablePaintCell, dtgtk_paint_cell, GTK_TYPE_CELL_RENDERER)

// icon edge length, derived from the widget's line height
static int _paint_cell_compute_size(GtkWidget *widget)
{
int s = DT_PIXEL_APPLY_DPI(12);
if(widget)
{
PangoContext *pctx = gtk_widget_get_pango_context(widget);
const PangoFontDescription *fd = pango_context_get_font_description(pctx);
PangoFontMetrics *m = pango_context_get_metrics(pctx, fd, NULL);
const int line_h = (pango_font_metrics_get_ascent(m)
+ pango_font_metrics_get_descent(m)) / PANGO_SCALE;
pango_font_metrics_unref(m);
if(line_h > 0) s = line_h;
}
return s;
}

static void _paint_cell_get_preferred_width(GtkCellRenderer *r,
GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
(void)r;
const int s = _paint_cell_compute_size(widget);
if(minimum_size) *minimum_size = s;
if(natural_size) *natural_size = s;
}

static void _paint_cell_get_preferred_height(GtkCellRenderer *r,
GtkWidget *widget,
gint *minimum_size,
gint *natural_size)
{
(void)r;
const int s = _paint_cell_compute_size(widget);
if(minimum_size) *minimum_size = s;
if(natural_size) *natural_size = s;
}

static void _paint_cell_render(GtkCellRenderer *r,
cairo_t *cr,
GtkWidget *widget,
const GdkRectangle *bg_area,
const GdkRectangle *cell_area,
GtkCellRendererState flags)
{
(void)bg_area; (void)flags;
GtkDarktablePaintCell *self = DTGTK_PAINT_CELL(r);
if(!self->paint) return;

GdkRGBA fg;
GtkStyleContext *ctx = gtk_widget_get_style_context(widget);
gtk_style_context_get_color(ctx, gtk_widget_get_state_flags(widget), &fg);

const int mx = cell_area->width / 5;
const int my = cell_area->height / 5;

cairo_save(cr);
gdk_cairo_set_source_rgba(cr, &fg);
self->paint(cr,
cell_area->x + mx, cell_area->y + my,
cell_area->width - 2 * mx,
cell_area->height - 2 * my,
self->paint_flags, self->paint_data);
cairo_restore(cr);
}

static void dtgtk_paint_cell_class_init(GtkDarktablePaintCellClass *klass)
{
GtkCellRendererClass *cr_class = GTK_CELL_RENDERER_CLASS(klass);
cr_class->get_preferred_width = _paint_cell_get_preferred_width;
cr_class->get_preferred_height = _paint_cell_get_preferred_height;
cr_class->render = _paint_cell_render;
}

static void dtgtk_paint_cell_init(GtkDarktablePaintCell *self)
{
self->paint = NULL;
self->paint_flags = 0;
self->paint_data = NULL;
}

GtkCellRenderer *dtgtk_paint_cell_new(DTGTKCairoPaintIconFunc paint,
gint paint_flags,
void *paint_data)
{
GtkDarktablePaintCell *cell = g_object_new(dtgtk_paint_cell_get_type(), NULL);
cell->paint = paint;
cell->paint_flags = paint_flags;
cell->paint_data = paint_data;
return GTK_CELL_RENDERER(cell);
}

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on
56 changes: 56 additions & 0 deletions src/dtgtk/paint_cell.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
This file is part of darktable,
Copyright (C) 2026 darktable developers.

darktable 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 3 of the License, or
(at your option) any later version.

darktable 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 darktable. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "paint.h"
#include <gtk/gtk.h>

G_BEGIN_DECLS

#define DTGTK_TYPE_PAINT_CELL dtgtk_paint_cell_get_type()
G_DECLARE_FINAL_TYPE(GtkDarktablePaintCell, dtgtk_paint_cell,
DTGTK, PAINT_CELL, GtkCellRenderer)

struct _GtkDarktablePaintCell
{
GtkCellRenderer parent;
DTGTKCairoPaintIconFunc paint;
gint paint_flags;
void *paint_data;
};

/** Cell renderer that draws a dtgtk cairo paint function directly
* into the tree view's cairo context — the same approach
* dtgtk_button uses for its icon. avoids the GtkCellRendererPixbuf
* intermediate, which softens edges at icon sizes.
*
* Visibility is controlled via the standard GtkCellRenderer "visible"
* property, typically bound to a model column via
* gtk_tree_view_column_new_with_attributes(..., "visible", COL, NULL). */
GtkCellRenderer *dtgtk_paint_cell_new(DTGTKCairoPaintIconFunc paint,
gint paint_flags,
void *paint_data);

G_END_DECLS

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on
25 changes: 11 additions & 14 deletions src/gui/preferences_ai.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "bauhaus/bauhaus.h"
#include "dtgtk/button.h"
#include "dtgtk/paint.h"
#include "dtgtk/paint_cell.h"
#include "ai/backend.h"
#include "common/ai_models.h"
#include "common/darktable.h"
Expand Down Expand Up @@ -69,7 +70,7 @@ enum
{
COL_SELECTED,
COL_NAME,
COL_INFO, // info icon column (static "ⓘ" text)
COL_INFO, // info icon visibility flag (TRUE on downloaded rows)
COL_VERSION,
COL_TASK,
COL_ENABLED,
Expand Down Expand Up @@ -257,8 +258,7 @@ static void _refresh_model_list(dt_prefs_ai_data_t *data)
? (model->version ? model->version : "0.0") : "–",
COL_ID,
model->id,
COL_INFO,
is_downloaded ? "\xe2\x93\x98" : "", // U+24D8 CIRCLED LATIN SMALL LETTER I
COL_INFO, is_downloaded,
-1);
dt_ai_model_free(model);
}
Expand Down Expand Up @@ -1220,12 +1220,9 @@ static gboolean _info_active_at_bin(dt_prefs_ai_data_t *data,
&& column == data->info_col)
{
GtkTreeIter iter;
gchar *info = NULL;
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model_store), &iter, path))
gtk_tree_model_get(GTK_TREE_MODEL(data->model_store),
&iter, COL_INFO, &info, -1);
active = (info && info[0]);
g_free(info);
&iter, COL_INFO, &active, -1);
}
if(path) gtk_tree_path_free(path);
return active;
Expand Down Expand Up @@ -1309,13 +1306,12 @@ static gboolean _on_info_button_press(GtkWidget *widget,
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model_store), &iter, path))
{
gchar *model_id = NULL;
gchar *info = NULL;
gboolean has_info = FALSE;
gtk_tree_model_get(GTK_TREE_MODEL(data->model_store),
&iter, COL_ID, &model_id, COL_INFO, &info, -1);
if(model_id && info && info[0])
&iter, COL_ID, &model_id, COL_INFO, &has_info, -1);
if(model_id && has_info)
_show_model_card(data, model_id);
g_free(model_id);
g_free(info);
}
gtk_tree_path_free(path);
return TRUE;
Expand Down Expand Up @@ -1713,7 +1709,7 @@ void init_tab_ai(GtkWidget *dialog, GtkWidget *stack)
NUM_COLS,
G_TYPE_BOOLEAN, // selected
G_TYPE_STRING, // name
G_TYPE_STRING, // info icon
G_TYPE_BOOLEAN, // info icon visible
G_TYPE_STRING, // version
G_TYPE_STRING, // task
G_TYPE_BOOLEAN, // enabled
Expand Down Expand Up @@ -1782,11 +1778,12 @@ void init_tab_ai(GtkWidget *dialog, GtkWidget *stack)
gtk_tree_view_append_column(GTK_TREE_VIEW(data->model_list), name_col);

// info icon column — click opens model card
GtkCellRenderer *info_renderer = gtk_cell_renderer_text_new();
GtkCellRenderer *info_renderer
= dtgtk_paint_cell_new(dtgtk_cairo_paint_info, 0, NULL);
data->info_col = gtk_tree_view_column_new_with_attributes(
"",
info_renderer,
"text",
"visible",
COL_INFO,
NULL);
gtk_tree_view_column_set_clickable(data->info_col, FALSE);
Expand Down
Loading