gimpsamplepointeditor.c 22 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3 4
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpsamplepointeditor.c
5
 * Copyright (C) 2005-2016 Michael Natterer <mitch@gimp.org>
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10 11 12 13 14 15 16 17
 * (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
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 20 21 22
 */

#include "config.h"

23
#include <gegl.h>
24 25 26 27 28 29
#include <gtk/gtk.h>

#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

30 31
#include "config/gimpcoreconfig.h"

32 33 34
#include "core/gimp.h"
#include "core/gimpimage.h"
#include "core/gimpimage-pick-color.h"
35
#include "core/gimpimage-sample-points.h"
36
#include "core/gimpsamplepoint.h"
37 38 39 40 41 42 43 44 45

#include "gimpcolorframe.h"
#include "gimpmenufactory.h"
#include "gimpsamplepointeditor.h"
#include "gimpwidgets-utils.h"

#include "gimp-intl.h"


46 47 48 49 50 51 52
enum
{
  PROP_0,
  PROP_SAMPLE_MERGED
};


53 54
static void   gimp_sample_point_editor_constructed    (GObject               *object);
static void   gimp_sample_point_editor_dispose        (GObject               *object);
55 56 57 58 59 60 61 62
static void   gimp_sample_point_editor_set_property   (GObject               *object,
                                                       guint                  property_id,
                                                       const GValue          *value,
                                                       GParamSpec            *pspec);
static void   gimp_sample_point_editor_get_property   (GObject               *object,
                                                       guint                  property_id,
                                                       GValue                *value,
                                                       GParamSpec            *pspec);
63

64
static void   gimp_sample_point_editor_style_updated  (GtkWidget             *widget);
65
static void   gimp_sample_point_editor_set_image      (GimpImageEditor       *editor,
66
                                                       GimpImage             *image);
67

68
static void   gimp_sample_point_editor_point_added    (GimpImage             *image,
69 70
                                                       GimpSamplePoint       *sample_point,
                                                       GimpSamplePointEditor *editor);
71
static void   gimp_sample_point_editor_point_removed  (GimpImage             *image,
72 73
                                                       GimpSamplePoint       *sample_point,
                                                       GimpSamplePointEditor *editor);
74
static void   gimp_sample_point_editor_point_moved    (GimpImage             *image,
75 76
                                                       GimpSamplePoint       *sample_point,
                                                       GimpSamplePointEditor *editor);
77
static void   gimp_sample_point_editor_proj_update    (GimpImage             *image,
78 79 80 81 82 83
                                                       gboolean               now,
                                                       gint                   x,
                                                       gint                   y,
                                                       gint                   width,
                                                       gint                   height,
                                                       GimpSamplePointEditor *editor);
84
static void   gimp_sample_point_editor_points_changed (GimpSamplePointEditor *editor);
85 86 87
static void   gimp_sample_point_editor_dirty          (GimpSamplePointEditor *editor,
                                                       gint                   index);
static gboolean gimp_sample_point_editor_update       (GimpSamplePointEditor *editor);
88 89 90
static void     gimp_sample_point_editor_mode_notify  (GimpColorFrame        *frame,
                                                       const GParamSpec      *pspec,
                                                       GimpSamplePointEditor *editor);
91 92


93
G_DEFINE_TYPE (GimpSamplePointEditor, gimp_sample_point_editor,
94
               GIMP_TYPE_IMAGE_EDITOR)
95

96
#define parent_class gimp_sample_point_editor_parent_class
97 98 99


static void
100
gimp_sample_point_editor_class_init (GimpSamplePointEditorClass *klass)
101 102 103 104 105
{
  GObjectClass         *object_class       = G_OBJECT_CLASS (klass);
  GtkWidgetClass       *widget_class       = GTK_WIDGET_CLASS (klass);
  GimpImageEditorClass *image_editor_class = GIMP_IMAGE_EDITOR_CLASS (klass);

106 107
  object_class->constructed     = gimp_sample_point_editor_constructed;
  object_class->dispose         = gimp_sample_point_editor_dispose;
108 109
  object_class->get_property    = gimp_sample_point_editor_get_property;
  object_class->set_property    = gimp_sample_point_editor_set_property;
110

111
  widget_class->style_updated   = gimp_sample_point_editor_style_updated;
112 113

  image_editor_class->set_image = gimp_sample_point_editor_set_image;
114 115 116 117 118

  g_object_class_install_property (object_class, PROP_SAMPLE_MERGED,
                                   g_param_spec_boolean ("sample-merged",
                                                         NULL, NULL,
                                                         TRUE,
119
                                                         GIMP_PARAM_READWRITE |
120
                                                         G_PARAM_CONSTRUCT));
121 122 123 124 125
}

static void
gimp_sample_point_editor_init (GimpSamplePointEditor *editor)
{
126 127 128 129
  GtkWidget *scrolled_window;
  GtkWidget *viewport;
  GtkWidget *vbox;
  gint       content_spacing;
130

131 132
  editor->sample_merged = TRUE;

133
  gtk_widget_style_get (GTK_WIDGET (editor),
134
                        "content-spacing", &content_spacing,
135
                        NULL);
136

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  scrolled_window = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
                                  GTK_POLICY_NEVER,
                                  GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
                                       GTK_SHADOW_NONE);
  gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0);
  gtk_widget_show (scrolled_window);

  viewport = gtk_viewport_new (NULL, NULL);
  gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
  gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
  gtk_widget_show (viewport);

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_container_add (GTK_CONTAINER (viewport), vbox);
  gtk_widget_show (vbox);

  editor->empty_icon = gtk_image_new_from_icon_name (GIMP_ICON_SAMPLE_POINT,
                                                     GTK_ICON_SIZE_BUTTON);
  gtk_box_pack_start (GTK_BOX (vbox), editor->empty_icon, TRUE, TRUE, 0);
  gtk_widget_show (editor->empty_icon);

  editor->empty_label = gtk_label_new (_("This image\nhas no\nsample points"));
  gtk_label_set_justify (GTK_LABEL (editor->empty_label), GTK_JUSTIFY_CENTER);
  gimp_label_set_attributes (GTK_LABEL (editor->empty_label),
                             PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
                             -1);
  gtk_box_pack_start (GTK_BOX (vbox), editor->empty_label, TRUE, TRUE, 0);

167 168 169 170 171
  editor->grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (editor->grid), content_spacing);
  gtk_grid_set_column_spacing (GTK_GRID (editor->grid), content_spacing);
  gtk_box_pack_start (GTK_BOX (vbox), editor->grid, FALSE, FALSE, 0);
  gtk_widget_show (editor->grid);
172 173
}

174 175
static void
gimp_sample_point_editor_constructed (GObject *object)
176
{
177
  G_OBJECT_CLASS (parent_class)->constructed (object);
178
}
179

180 181 182 183
static void
gimp_sample_point_editor_dispose (GObject *object)
{
  GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (object);
184

185 186
  g_clear_pointer (&editor->color_frames, g_free);

187 188 189 190 191
  if (editor->dirty_idle_id)
    {
      g_source_remove (editor->dirty_idle_id);
      editor->dirty_idle_id = 0;
    }
192

193
  G_OBJECT_CLASS (parent_class)->dispose (object);
194 195
}

196 197 198 199 200 201 202 203 204 205 206 207 208 209
static void
gimp_sample_point_editor_set_property (GObject      *object,
                                       guint         property_id,
                                       const GValue *value,
                                       GParamSpec   *pspec)
{
  GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (object);

  switch (property_id)
    {
    case PROP_SAMPLE_MERGED:
      gimp_sample_point_editor_set_sample_merged (editor,
                                                  g_value_get_boolean (value));
      break;
210 211

    default:
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_sample_point_editor_get_property (GObject    *object,
                                       guint       property_id,
                                       GValue     *value,
                                       GParamSpec *pspec)
{
  GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (object);

  switch (property_id)
    {
    case PROP_SAMPLE_MERGED:
      g_value_set_boolean (value, editor->sample_merged);
      break;
230 231

    default:
232 233 234 235 236
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

237
static void
238
gimp_sample_point_editor_style_updated (GtkWidget *widget)
239 240 241
{
  GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (widget);

242
  GTK_WIDGET_CLASS (parent_class)->style_updated (widget);
243

244
  if (editor->grid)
245 246
    {
      gint content_spacing;
247

248 249 250 251
      gtk_widget_style_get (widget,
                            "content-spacing", &content_spacing,
                            NULL);

252 253
      gtk_grid_set_row_spacing (GTK_GRID (editor->grid), content_spacing);
      gtk_grid_set_column_spacing (GTK_GRID (editor->grid), content_spacing);
254
    }
255 256 257 258
}

static void
gimp_sample_point_editor_set_image (GimpImageEditor *image_editor,
259
                                    GimpImage       *image)
260 261 262
{
  GimpSamplePointEditor *editor = GIMP_SAMPLE_POINT_EDITOR (image_editor);

263
  if (image_editor->image)
264
    {
265
      g_signal_handlers_disconnect_by_func (image_editor->image,
266 267
                                            gimp_sample_point_editor_point_added,
                                            editor);
268
      g_signal_handlers_disconnect_by_func (image_editor->image,
269 270
                                            gimp_sample_point_editor_point_removed,
                                            editor);
271
      g_signal_handlers_disconnect_by_func (image_editor->image,
272
                                            gimp_sample_point_editor_point_moved,
273
                                            editor);
274 275

      g_signal_handlers_disconnect_by_func (gimp_image_get_projection (image_editor->image),
276 277
                                            gimp_sample_point_editor_proj_update,
                                            editor);
278 279
    }

280
  GIMP_IMAGE_EDITOR_CLASS (parent_class)->set_image (image_editor, image);
281

282
  if (image)
283
    {
284
      g_signal_connect (image, "sample-point-added",
285 286
                        G_CALLBACK (gimp_sample_point_editor_point_added),
                        editor);
287
      g_signal_connect (image, "sample-point-removed",
288 289
                        G_CALLBACK (gimp_sample_point_editor_point_removed),
                        editor);
290 291
      g_signal_connect (image, "sample-point-moved",
                        G_CALLBACK (gimp_sample_point_editor_point_moved),
292
                        editor);
293 294

      g_signal_connect (gimp_image_get_projection (image), "update",
295 296
                        G_CALLBACK (gimp_sample_point_editor_proj_update),
                        editor);
297 298
    }

299 300
  gtk_widget_set_visible (editor->empty_icon,
                          image_editor->image == NULL);
301 302 303 304 305 306 307 308 309 310 311 312 313 314

  gimp_sample_point_editor_points_changed (editor);
}


/*  public functions  */

GtkWidget *
gimp_sample_point_editor_new (GimpMenuFactory *menu_factory)
{
  g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL);

  return g_object_new (GIMP_TYPE_SAMPLE_POINT_EDITOR,
                       "menu-factory",    menu_factory,
315 316
                       "menu-identifier", "<SamplePoints>",
                       "ui-path",         "/sample-points-popup",
317 318 319
                       NULL);
}

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
void
gimp_sample_point_editor_set_sample_merged (GimpSamplePointEditor *editor,
                                            gboolean               sample_merged)
{
  g_return_if_fail (GIMP_IS_SAMPLE_POINT_EDITOR (editor));

  sample_merged = sample_merged ? TRUE : FALSE;

  if (editor->sample_merged != sample_merged)
    {
      editor->sample_merged = sample_merged;

      gimp_sample_point_editor_dirty (editor, -1);

      g_object_notify (G_OBJECT (editor), "sample-merged");
    }
}

gboolean
gimp_sample_point_editor_get_sample_merged (GimpSamplePointEditor *editor)
{
  g_return_val_if_fail (GIMP_IS_SAMPLE_POINT_EDITOR (editor), FALSE);

  return editor->sample_merged;
}
345 346 347 348

/*  private functions  */

static void
349
gimp_sample_point_editor_point_added (GimpImage             *image,
350 351 352 353 354 355 356
                                      GimpSamplePoint       *sample_point,
                                      GimpSamplePointEditor *editor)
{
  gimp_sample_point_editor_points_changed (editor);
}

static void
357
gimp_sample_point_editor_point_removed (GimpImage             *image,
358 359 360 361 362 363
                                        GimpSamplePoint       *sample_point,
                                        GimpSamplePointEditor *editor)
{
  gimp_sample_point_editor_points_changed (editor);
}

364
static void
365 366 367
gimp_sample_point_editor_point_moved (GimpImage             *image,
                                      GimpSamplePoint       *sample_point,
                                      GimpSamplePointEditor *editor)
368
{
369
  gint i = g_list_index (gimp_image_get_sample_points (image), sample_point);
370

371
  gimp_sample_point_editor_dirty (editor, i);
372 373 374
}

static void
375
gimp_sample_point_editor_proj_update (GimpImage             *image,
376 377 378 379 380 381 382 383
                                      gboolean               now,
                                      gint                   x,
                                      gint                   y,
                                      gint                   width,
                                      gint                   height,
                                      GimpSamplePointEditor *editor)
{
  GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor);
384
  GList           *sample_points;
385 386 387 388
  gint             n_points     = 0;
  GList           *list;
  gint             i;

389
  sample_points = gimp_image_get_sample_points (image_editor->image);
390

391
  n_points = MIN (editor->n_color_frames, g_list_length (sample_points));
392 393

  for (i = 0, list = sample_points;
394 395 396 397
       i < n_points;
       i++, list = g_list_next (list))
    {
      GimpSamplePoint *sample_point = list->data;
398 399
      gint             sp_x;
      gint             sp_y;
400

401 402 403 404
      gimp_sample_point_get_position (sample_point, &sp_x, &sp_y);

      if (sp_x >= x && sp_x < (x + width) &&
          sp_y >= y && sp_y < (y + height))
405 406 407 408 409 410
        {
          gimp_sample_point_editor_dirty (editor, i);
        }
    }
}

411 412 413 414
static void
gimp_sample_point_editor_points_changed (GimpSamplePointEditor *editor)
{
  GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor);
415
  GList           *sample_points;
416 417 418
  gint             n_points     = 0;
  gint             i;

419
  if (image_editor->image)
420 421
    {
      sample_points = gimp_image_get_sample_points (image_editor->image);
422
      n_points = g_list_length (sample_points);
423
    }
424

425 426 427
  gtk_widget_set_visible (editor->empty_label,
                          image_editor->image && n_points == 0);

428 429 430 431 432 433 434 435 436
  /*  Keep that many color frames around so they remember their color
   *  model. Let's hope nobody uses more and notices they get reset to
   *  "pixel". See https://gitlab.gnome.org/GNOME/gimp/issues/1805
   */
#define RANDOM_MAGIC 16

  if (n_points < editor->n_color_frames &&
      n_points < RANDOM_MAGIC           &&
      editor->n_color_frames > RANDOM_MAGIC)
437
    {
438
      for (i = RANDOM_MAGIC; i < editor->n_color_frames; i++)
439 440 441
        {
          gtk_widget_destroy (editor->color_frames[i]);
        }
442

443
      editor->color_frames = g_renew (GtkWidget *, editor->color_frames,
444 445 446
                                      RANDOM_MAGIC);

      editor->n_color_frames = RANDOM_MAGIC;
447 448
    }
  else if (n_points > editor->n_color_frames)
449
    {
450 451 452 453 454 455 456 457 458 459 460 461 462 463
      GimpColorConfig *config;

      config = image_editor->image->gimp->config->color_management;

      editor->color_frames = g_renew (GtkWidget *, editor->color_frames,
                                      n_points);

      for (i = editor->n_color_frames; i < n_points; i++)
        {
          gint row    = i / 2;
          gint column = i % 2;

          editor->color_frames[i] =
            g_object_new (GIMP_TYPE_COLOR_FRAME,
464
                          "mode",           GIMP_COLOR_PICK_MODE_PIXEL,
465 466 467 468
                          "has-number",     TRUE,
                          "number",         i + 1,
                          "has-color-area", TRUE,
                          "has-coords",     TRUE,
469
                          "hexpand",        TRUE,
470 471 472 473 474
                          NULL);

          gimp_color_frame_set_color_config (GIMP_COLOR_FRAME (editor->color_frames[i]),
                                             config);

475 476
          gtk_grid_attach (GTK_GRID (editor->grid), editor->color_frames[i],
                           column, row, 1, 1);
477

478 479 480 481
          g_signal_connect_object (editor->color_frames[i], "notify::mode",
                                   G_CALLBACK (gimp_sample_point_editor_mode_notify),
                                   editor, 0);

482 483 484
          g_object_set_data (G_OBJECT (editor->color_frames[i]),
                             "dirty", GINT_TO_POINTER (TRUE));
        }
485 486

      editor->n_color_frames = n_points;
487
    }
488

489 490 491 492
  for (i = 0; i < editor->n_color_frames; i++)
    {
      gtk_widget_set_visible (editor->color_frames[i], i < n_points);
    }
493

494 495 496 497 498 499 500 501 502
  if (n_points > 0)
    gimp_sample_point_editor_dirty (editor, -1);
}

static void
gimp_sample_point_editor_dirty (GimpSamplePointEditor *editor,
                                gint                   index)
{
  if (index >= 0)
503 504 505 506 507 508 509 510 511 512 513 514
    {
      g_object_set_data (G_OBJECT (editor->color_frames[index]),
                         "dirty", GINT_TO_POINTER (TRUE));
    }
  else
    {
      gint i;

      for (i = 0; i < editor->n_color_frames; i++)
        g_object_set_data (G_OBJECT (editor->color_frames[i]),
                           "dirty", GINT_TO_POINTER (TRUE));
    }
515 516 517 518 519 520 521 522 523 524 525 526 527

  if (editor->dirty_idle_id)
    g_source_remove (editor->dirty_idle_id);

  editor->dirty_idle_id =
    g_idle_add ((GSourceFunc) gimp_sample_point_editor_update,
                editor);
}

static gboolean
gimp_sample_point_editor_update (GimpSamplePointEditor *editor)
{
  GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor);
528
  GList           *sample_points;
529
  gint             n_points;
530 531 532 533 534
  GList           *list;
  gint             i;

  editor->dirty_idle_id = 0;

535
  if (! image_editor->image)
536 537
    return FALSE;

538 539
  sample_points = gimp_image_get_sample_points (image_editor->image);

540
  n_points = MIN (editor->n_color_frames, g_list_length (sample_points));
541

542
  for (i = 0, list = sample_points;
543 544 545
       i < n_points;
       i++, list = g_list_next (list))
    {
546
      GimpColorFrame *color_frame = GIMP_COLOR_FRAME (editor->color_frames[i]);
547 548

      if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (color_frame),
549
                                              "dirty")))
550
        {
551 552 553 554 555 556 557
          GimpSamplePoint   *sample_point = list->data;
          const Babl        *format;
          guchar             pixel[32];
          GimpRGB            color;
          GimpColorPickMode  pick_mode;
          gint               x;
          gint               y;
558

559 560
          g_object_set_data (G_OBJECT (color_frame),
                             "dirty", GINT_TO_POINTER (FALSE));
561

562 563
          gimp_sample_point_get_position (sample_point, &x, &y);

564
          if (gimp_image_pick_color (image_editor->image, NULL,
565
                                     x, y,
566 567
                                     editor->sample_merged,
                                     FALSE, 0.0,
568
                                     &format,
569 570
                                     pixel,
                                     &color))
571
            {
572
              gimp_color_frame_set_color (color_frame, FALSE,
573 574
                                          format, pixel, &color,
                                          x, y);
575 576 577 578 579
            }
          else
            {
              gimp_color_frame_set_invalid (color_frame);
            }
580 581 582 583

          pick_mode = gimp_sample_point_get_pick_mode (sample_point);

          gimp_color_frame_set_mode (color_frame, pick_mode);
584 585 586 587
        }
    }

  return FALSE;
588
}
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624

static void
gimp_sample_point_editor_mode_notify (GimpColorFrame        *frame,
                                      const GParamSpec      *pspec,
                                      GimpSamplePointEditor *editor)
{
  GimpImageEditor *image_editor = GIMP_IMAGE_EDITOR (editor);
  GList           *sample_points;
  gint             n_points;
  GList           *list;
  gint             i;

  sample_points = gimp_image_get_sample_points (image_editor->image);

  n_points = MIN (editor->n_color_frames, g_list_length (sample_points));

  for (i = 0, list = sample_points;
       i < n_points;
       i++, list = g_list_next (list))
    {
      if (GIMP_COLOR_FRAME (editor->color_frames[i]) == frame)
        {
          GimpSamplePoint   *sample_point = list->data;
          GimpColorPickMode  pick_mode;

          g_object_get (frame, "mode", &pick_mode, NULL);

          if (pick_mode != gimp_sample_point_get_pick_mode (sample_point))
            gimp_image_set_sample_point_pick_mode (image_editor->image,
                                                   sample_point,
                                                   pick_mode,
                                                   TRUE);
          break;
        }
    }
}