gimpcellrenderertoggle.c 19.7 KB
Newer Older
1 2
/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3
 *
4
 * gimpcellrenderertoggle.c
5
 * Copyright (C) 2003-2004  Sven Neumann <sven@gimp.org>
6
 *
7
 * This library is free software: you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 3 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
19
 * <https://www.gnu.org/licenses/>.
20 21 22 23 24 25
 */

#include <config.h>

#include <gtk/gtk.h>

26
#include "gimpwidgetstypes.h"
27

28
#include "gimpwidgetsmarshal.h"
29 30 31
#include "gimpcellrenderertoggle.h"


32 33 34 35 36 37 38 39 40 41
/**
 * SECTION: gimpcellrenderertoggle
 * @title: GimpCellRendererToggle
 * @short_description: A #GtkCellRendererToggle that displays icons instead
 *                     of a checkbox.
 *
 * A #GtkCellRendererToggle that displays icons instead of a checkbox.
 **/


42
#define DEFAULT_ICON_SIZE 16
43

44 45 46 47 48 49 50

enum
{
  CLICKED,
  LAST_SIGNAL
};

51 52 53
enum
{
  PROP_0,
54
  PROP_ICON_NAME,
55
  PROP_ICON_SIZE,
56
  PROP_OVERRIDE_BACKGROUND
57 58 59
};


60 61
struct _GimpCellRendererTogglePrivate
{
62
  gchar       *icon_name;
63
  gint         icon_size;
64 65 66
  gboolean     override_background;

  GdkPixbuf   *pixbuf;
67 68
};

69
#define GET_PRIVATE(obj) (((GimpCellRendererToggle *) (obj))->priv)
70 71


72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
static void gimp_cell_renderer_toggle_finalize     (GObject              *object);
static void gimp_cell_renderer_toggle_get_property (GObject              *object,
                                                    guint                 param_id,
                                                    GValue               *value,
                                                    GParamSpec           *pspec);
static void gimp_cell_renderer_toggle_set_property (GObject              *object,
                                                    guint                 param_id,
                                                    const GValue         *value,
                                                    GParamSpec           *pspec);
static void gimp_cell_renderer_toggle_get_size     (GtkCellRenderer      *cell,
                                                    GtkWidget            *widget,
                                                    const GdkRectangle   *rectangle,
                                                    gint                 *x_offset,
                                                    gint                 *y_offset,
                                                    gint                 *width,
                                                    gint                 *height);
static void gimp_cell_renderer_toggle_render       (GtkCellRenderer      *cell,
                                                    cairo_t              *cr,
                                                    GtkWidget            *widget,
                                                    const GdkRectangle   *background_area,
                                                    const GdkRectangle   *cell_area,
                                                    GtkCellRendererState  flags);
static gboolean gimp_cell_renderer_toggle_activate (GtkCellRenderer      *cell,
                                                    GdkEvent             *event,
                                                    GtkWidget            *widget,
                                                    const gchar          *path,
                                                    const GdkRectangle   *background_area,
                                                    const GdkRectangle   *cell_area,
100
                                                    GtkCellRendererState  flags);
101 102 103 104
static void gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle,
                                                     GtkWidget              *widget);


105 106
G_DEFINE_TYPE_WITH_PRIVATE (GimpCellRendererToggle, gimp_cell_renderer_toggle,
                            GTK_TYPE_CELL_RENDERER_TOGGLE)
107

108
#define parent_class gimp_cell_renderer_toggle_parent_class
109

110
static guint toggle_cell_signals[LAST_SIGNAL] = { 0 };
111 112 113 114 115


static void
gimp_cell_renderer_toggle_class_init (GimpCellRendererToggleClass *klass)
{
116 117
  GObjectClass         *object_class = G_OBJECT_CLASS (klass);
  GtkCellRendererClass *cell_class   = GTK_CELL_RENDERER_CLASS (klass);
118

119 120
  toggle_cell_signals[CLICKED] =
    g_signal_new ("clicked",
Sven Neumann's avatar
Sven Neumann committed
121 122 123 124 125 126 127
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GimpCellRendererToggleClass, clicked),
                  NULL, NULL,
                  _gimp_widgets_marshal_VOID__STRING_FLAGS,
                  G_TYPE_NONE, 2,
                  G_TYPE_STRING,
128
                  GDK_TYPE_MODIFIER_TYPE);
129

130 131 132 133 134 135
  object_class->finalize     = gimp_cell_renderer_toggle_finalize;
  object_class->get_property = gimp_cell_renderer_toggle_get_property;
  object_class->set_property = gimp_cell_renderer_toggle_set_property;

  cell_class->get_size       = gimp_cell_renderer_toggle_get_size;
  cell_class->render         = gimp_cell_renderer_toggle_render;
136
  cell_class->activate       = gimp_cell_renderer_toggle_activate;
137

138
  g_object_class_install_property (object_class, PROP_ICON_NAME,
139
                                   g_param_spec_string ("icon-name",
140 141
                                                        "Icon Name",
                                                        "The icon to display",
142 143 144
                                                        NULL,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));
145

146 147 148 149
  g_object_class_install_property (object_class, PROP_ICON_SIZE,
                                   g_param_spec_int ("icon-size",
                                                     "Icon Size",
                                                     "The desired icon size to use in pixel (before applying scaling factor)",
150 151
                                                     0, G_MAXINT,
                                                     DEFAULT_ICON_SIZE,
152
                                                     GIMP_PARAM_READWRITE |
153
                                                     G_PARAM_CONSTRUCT));
154

155 156
  g_object_class_install_property (object_class, PROP_OVERRIDE_BACKGROUND,
                                   g_param_spec_boolean ("override-background",
157 158
                                                         "Override Background",
                                                         "Draw the background if the row is selected",
159 160 161
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));
162 163
}

164 165 166
static void
gimp_cell_renderer_toggle_init (GimpCellRendererToggle *toggle)
{
167
  toggle->priv = gimp_cell_renderer_toggle_get_instance_private (toggle);
168 169
}

170 171 172
static void
gimp_cell_renderer_toggle_finalize (GObject *object)
{
173
  GimpCellRendererTogglePrivate *priv = GET_PRIVATE (object);
174

175 176
  g_clear_pointer (&priv->icon_name, g_free);
  g_clear_object (&priv->pixbuf);
177 178 179 180 181 182 183 184 185 186

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_cell_renderer_toggle_get_property (GObject    *object,
                                        guint       param_id,
                                        GValue     *value,
                                        GParamSpec *pspec)
{
187
  GimpCellRendererTogglePrivate *priv = GET_PRIVATE (object);
188 189 190

  switch (param_id)
    {
191
    case PROP_ICON_NAME:
192
      g_value_set_string (value, priv->icon_name);
193
      break;
194

195 196
    case PROP_ICON_SIZE:
      g_value_set_int (value, priv->icon_size);
197 198
      break;

199 200 201 202
    case PROP_OVERRIDE_BACKGROUND:
      g_value_set_boolean (value, priv->override_background);
      break;

203 204 205 206 207 208 209 210 211 212 213 214
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    }
}

static void
gimp_cell_renderer_toggle_set_property (GObject      *object,
                                        guint         param_id,
                                        const GValue *value,
                                        GParamSpec   *pspec)
{
215
  GimpCellRendererTogglePrivate *priv = GET_PRIVATE (object);
216 217 218

  switch (param_id)
    {
219
    case PROP_ICON_NAME:
220 221 222
      if (priv->icon_name)
        g_free (priv->icon_name);
      priv->icon_name = g_value_dup_string (value);
223
      break;
224

225 226
    case PROP_ICON_SIZE:
      priv->icon_size = g_value_get_int (value);
227 228
      break;

229 230 231 232
    case PROP_OVERRIDE_BACKGROUND:
      priv->override_background = g_value_get_boolean (value);
      break;

233 234 235 236 237
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
      break;
    }

238
  g_clear_object (&priv->pixbuf);
239 240 241
}

static void
242 243 244 245 246 247 248
gimp_cell_renderer_toggle_get_size (GtkCellRenderer    *cell,
                                    GtkWidget          *widget,
                                    const GdkRectangle *cell_area,
                                    gint               *x_offset,
                                    gint               *y_offset,
                                    gint               *width,
                                    gint               *height)
249
{
250 251 252 253
  GimpCellRendererToggle        *toggle  = GIMP_CELL_RENDERER_TOGGLE (cell);
  GimpCellRendererTogglePrivate *priv    = GET_PRIVATE (cell);
  GtkStyleContext               *context = gtk_widget_get_style_context (widget);
  GtkBorder                      border;
254
  gint                           scale_factor;
255 256 257 258 259 260 261 262 263
  gint                           calc_width;
  gint                           calc_height;
  gint                           pixbuf_width;
  gint                           pixbuf_height;
  gfloat                         xalign;
  gfloat                         yalign;
  gint                           xpad;
  gint                           ypad;

264 265
  scale_factor = gtk_widget_get_scale_factor (widget);

266
  if (! priv->icon_name)
267 268 269 270 271 272 273 274 275
    {
      GTK_CELL_RENDERER_CLASS (parent_class)->get_size (cell,
                                                        widget,
                                                        cell_area,
                                                        x_offset, y_offset,
                                                        width, height);
      return;
    }

276 277
  gtk_style_context_save (context);

278 279 280
  gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);

281
  if (! priv->pixbuf)
282 283
    gimp_cell_renderer_toggle_create_pixbuf (toggle, widget);

284 285
  pixbuf_width  = gdk_pixbuf_get_width  (priv->pixbuf);
  pixbuf_height = gdk_pixbuf_get_height (priv->pixbuf);
286

287 288 289
  /* The pixbuf size may be bigger than the logical size. */
  calc_width  = pixbuf_width / scale_factor + (gint) xpad * 2;
  calc_height = pixbuf_height / scale_factor + (gint) ypad * 2;
290 291 292 293 294 295

  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);

  gtk_style_context_get_border (context, 0, &border);
  calc_width  += border.left + border.right;
  calc_height += border.top + border.bottom;
296

297 298 299 300 301
  if (width)
    *width  = calc_width;

  if (height)
    *height = calc_height;
302 303 304 305

  if (cell_area)
    {
      if (x_offset)
Sven Neumann's avatar
Sven Neumann committed
306 307
        {
          *x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ?
308
                        (1.0 - xalign) : xalign) *
309
                       (cell_area->width - calc_width));
Sven Neumann's avatar
Sven Neumann committed
310 311
          *x_offset = MAX (*x_offset, 0);
        }
312

313
      if (y_offset)
Sven Neumann's avatar
Sven Neumann committed
314
        {
315
          *y_offset = yalign * (cell_area->height - calc_height);
Sven Neumann's avatar
Sven Neumann committed
316 317
          *y_offset = MAX (*y_offset, 0);
        }
318
    }
319 320

  gtk_style_context_restore (context);
321 322 323 324
}

static void
gimp_cell_renderer_toggle_render (GtkCellRenderer      *cell,
325
                                  cairo_t              *cr,
326
                                  GtkWidget            *widget,
327 328
                                  const GdkRectangle   *background_area,
                                  const GdkRectangle   *cell_area,
329 330
                                  GtkCellRendererState  flags)
{
331 332
  GimpCellRendererTogglePrivate *priv    = GET_PRIVATE (cell);
  GtkStyleContext               *context = gtk_widget_get_style_context (widget);
333
  GdkRectangle                   toggle_rect;
334
  GtkStateFlags                  state;
335
  gboolean                       active;
336
  gint                           scale_factor;
337 338 339
  gint                           xpad;
  gint                           ypad;

340 341
  scale_factor = gtk_widget_get_scale_factor (widget);

342
  if (! priv->icon_name)
343
    {
344
      GTK_CELL_RENDERER_CLASS (parent_class)->render (cell, cr, widget,
345
                                                      background_area,
346
                                                      cell_area,
347 348 349 350
                                                      flags);
      return;
    }

351
  if ((flags & GTK_CELL_RENDERER_SELECTED) &&
352 353 354 355 356 357 358 359 360 361
      priv->override_background)
    {
      gboolean background_set;

      g_object_get (cell,
                    "cell-background-set", &background_set,
                    NULL);

      if (background_set)
        {
362
          GdkRGBA *color;
363 364

          g_object_get (cell,
365
                        "cell-background-rgba", &color,
366 367 368
                        NULL);

          gdk_cairo_rectangle (cr, background_area);
369
          gdk_cairo_set_source_rgba (cr, color);
370 371
          cairo_fill (cr);

372
          gdk_rgba_free (color);
373 374 375
        }
    }

376 377 378 379 380
  gimp_cell_renderer_toggle_get_size (cell, widget, cell_area,
                                      &toggle_rect.x,
                                      &toggle_rect.y,
                                      &toggle_rect.width,
                                      &toggle_rect.height);
381 382 383 384
  gtk_style_context_save (context);

  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);

385 386 387 388 389 390
  gtk_cell_renderer_get_padding (cell, &xpad, &ypad);

  toggle_rect.x      += cell_area->x + xpad;
  toggle_rect.y      += cell_area->y + ypad;
  toggle_rect.width  -= xpad * 2;
  toggle_rect.height -= ypad * 2;
391 392 393 394

  if (toggle_rect.width <= 0 || toggle_rect.height <= 0)
    return;

395 396
  state = gtk_cell_renderer_get_state (cell, widget, flags);

397 398 399
  active =
    gtk_cell_renderer_toggle_get_active (GTK_CELL_RENDERER_TOGGLE (cell));

400 401 402
  if (active)
    state |= GTK_STATE_FLAG_ACTIVE;

403 404
  if (! gtk_cell_renderer_toggle_get_activatable (GTK_CELL_RENDERER_TOGGLE (cell)))
    state |= GTK_STATE_FLAG_INSENSITIVE;
405

406 407
  gtk_style_context_set_state (context, state);

408
  if (state & GTK_STATE_FLAG_PRELIGHT)
409
    gtk_render_frame (context, cr,
410 411
                      toggle_rect.x,     toggle_rect.y,
                      toggle_rect.width, toggle_rect.height);
412 413 414

  if (active)
    {
415 416
      GtkBorder border;
      gboolean  inconsistent;
417

418 419
      gtk_style_context_get_border (context, 0, &border);
      toggle_rect.x      += border.left;
420
      toggle_rect.x      *= scale_factor;
421
      toggle_rect.y      += border.top;
422
      toggle_rect.y      *= scale_factor;
423 424
      toggle_rect.width  -= border.left + border.right;
      toggle_rect.height -= border.top + border.bottom;
425

426 427
      /* For high DPI displays, pixbuf size is bigger than logical size. */
      cairo_scale (cr, (gdouble) 1.0 / scale_factor, (gdouble) 1.0 / scale_factor);
428
      gdk_cairo_set_source_pixbuf (cr, priv->pixbuf,
429 430
                                   toggle_rect.x, toggle_rect.y);
      cairo_paint (cr);
431

432
      g_object_get (cell,
433 434
                    "inconsistent", &inconsistent,
                    NULL);
435

436 437
      if (inconsistent)
        {
438 439 440 441
          GdkRGBA color;

          gtk_style_context_get_color (context, state, &color);
          gdk_cairo_set_source_rgba (cr, &color);
442 443 444 445 446 447 448 449
          cairo_set_line_width (cr, 1.5);
          cairo_move_to (cr,
                         toggle_rect.x + toggle_rect.width - 1,
                         toggle_rect.y + 1);
          cairo_line_to (cr,
                         toggle_rect.x + 1,
                         toggle_rect.y + toggle_rect.height - 1);
          cairo_stroke (cr);
450
        }
451
    }
452 453

  gtk_style_context_restore (context);
454 455
}

456 457 458 459 460
static gboolean
gimp_cell_renderer_toggle_activate (GtkCellRenderer      *cell,
                                    GdkEvent             *event,
                                    GtkWidget            *widget,
                                    const gchar          *path,
461 462
                                    const GdkRectangle   *background_area,
                                    const GdkRectangle   *cell_area,
463 464
                                    GtkCellRendererState  flags)
{
465
  GtkCellRendererToggle *toggle = GTK_CELL_RENDERER_TOGGLE (cell);
466

467
  if (gtk_cell_renderer_toggle_get_activatable (toggle))
468 469 470
    {
      GdkModifierType state = 0;

471
      if (event && ((GdkEventAny *) event)->type == GDK_BUTTON_PRESS)
472 473
        state = ((GdkEventButton *) event)->state;

474 475
      gimp_cell_renderer_toggle_clicked (GIMP_CELL_RENDERER_TOGGLE (cell),
                                         path, state);
476 477 478 479 480 481 482

      return TRUE;
    }

  return FALSE;
}

483 484 485 486
static void
gimp_cell_renderer_toggle_create_pixbuf (GimpCellRendererToggle *toggle,
                                         GtkWidget              *widget)
{
487 488
  GimpCellRendererTogglePrivate *priv = GET_PRIVATE (toggle);

489
  g_clear_object (&priv->pixbuf);
490

491
  if (priv->icon_name)
492
    {
493 494 495 496 497 498 499 500 501 502 503 504 505
      GdkScreen    *screen;
      GtkIconTheme *icon_theme;
      GtkIconInfo  *icon_info;
      gchar        *icon_name;
      gint          scale_factor;

      scale_factor = gtk_widget_get_scale_factor (widget);
      screen       = gtk_widget_get_screen (widget);
      icon_theme   = gtk_icon_theme_get_for_screen (screen);

      /* Look for symbolic and fallback to color icon. */
      icon_name = g_strdup_printf ("%s-symbolic", priv->icon_name);
      icon_info = gtk_icon_theme_lookup_icon_for_scale (icon_theme, icon_name,
506
                                                        priv->icon_size, scale_factor,
507 508 509 510
                                                        GTK_ICON_LOOKUP_GENERIC_FALLBACK);

      g_free (icon_name);
      if (icon_info)
511
        {
512
          GdkPixbuf *pixbuf;
513

514 515 516 517 518 519
          pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info,
                                                            gtk_widget_get_style_context (widget),
                                                            NULL, NULL);
          priv->pixbuf = pixbuf;
          g_object_unref (icon_info);
        }
520
    }
521 522
}

523 524 525

/**
 * gimp_cell_renderer_toggle_new:
526
 * @icon_name: the icon name of the icon to use for the active state
527 528
 *
 * Creates a custom version of the #GtkCellRendererToggle. Instead of
529
 * showing the standard toggle button, it shows a named icon if the
530 531 532 533 534 535
 * cell is active and no icon otherwise. This cell renderer is for
 * example used in the Layers treeview to indicate and control the
 * layer's visibility by showing %GIMP_STOCK_VISIBLE.
 *
 * Return value: a new #GimpCellRendererToggle
 *
536
 * Since: 2.2
537
 **/
538
GtkCellRenderer *
539
gimp_cell_renderer_toggle_new (const gchar *icon_name)
540 541
{
  return g_object_new (GIMP_TYPE_CELL_RENDERER_TOGGLE,
542
                       "icon-name", icon_name,
543 544
                       NULL);
}
545

546 547
/**
 * gimp_cell_renderer_toggle_clicked:
548 549 550
 * @cell:  a #GimpCellRendererToggle
 * @path:  the path to the clicked row
 * @state: the modifier state
551 552 553
 *
 * Emits the "clicked" signal from a #GimpCellRendererToggle.
 *
554
 * Since: 2.2
555
 **/
556 557 558 559 560 561 562 563 564 565
void
gimp_cell_renderer_toggle_clicked (GimpCellRendererToggle *cell,
                                   const gchar            *path,
                                   GdkModifierType         state)
{
  g_return_if_fail (GIMP_IS_CELL_RENDERER_TOGGLE (cell));
  g_return_if_fail (path != NULL);

  g_signal_emit (cell, toggle_cell_signals[CLICKED], 0, path, state);
}