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

#include "config.h"

#include <string.h>

22
#include <gegl.h>
23
#include <gtk/gtk.h>
24
#include <gdk/gdkkeysyms.h>
25 26

#include "libgimpmath/gimpmath.h"
27 28
#include "libgimpcolor/gimpcolor.h"
#include "libgimpconfig/gimpconfig.h"
29
#include "libgimpwidgets/gimpwidgets.h"
30 31 32

#include "widgets-types.h"

33
#include "core/gimp.h"
34
#include "core/gimpcurve.h"
Sven Neumann's avatar
Sven Neumann committed
35
#include "core/gimpcurve-map.h"
36 37
#include "core/gimpmarshal.h"

38
#include "gimpclipboard.h"
39
#include "gimpcurveview.h"
40
#include "gimpwidgets-utils.h"
41 42


43 44 45
enum
{
  PROP_0,
46
  PROP_GIMP,
47
  PROP_BASE_LINE,
48
  PROP_GRID_ROWS,
49 50 51
  PROP_GRID_COLUMNS,
  PROP_X_AXIS_LABEL,
  PROP_Y_AXIS_LABEL
52 53
};

54 55 56 57 58 59 60
enum
{
  CUT_CLIPBOARD,
  COPY_CLIPBOARD,
  PASTE_CLIPBOARD,
  LAST_SIGNAL
};
61

62

63 64 65 66
typedef struct
{
  GimpCurve *curve;
  GimpRGB    color;
67
  gboolean   color_set;
68 69 70
} BGCurve;


71 72 73 74 75 76 77 78 79 80 81
static void       gimp_curve_view_finalize        (GObject          *object);
static void       gimp_curve_view_dispose         (GObject          *object);
static void       gimp_curve_view_set_property    (GObject          *object,
                                                   guint             property_id,
                                                   const GValue     *value,
                                                   GParamSpec       *pspec);
static void       gimp_curve_view_get_property    (GObject          *object,
                                                   guint             property_id,
                                                   GValue           *value,
                                                   GParamSpec       *pspec);

82
static void       gimp_curve_view_style_updated   (GtkWidget        *widget);
83 84
static gboolean   gimp_curve_view_draw            (GtkWidget        *widget,
                                                   cairo_t          *cr);
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
static gboolean   gimp_curve_view_button_press    (GtkWidget        *widget,
                                                   GdkEventButton   *bevent);
static gboolean   gimp_curve_view_button_release  (GtkWidget        *widget,
                                                   GdkEventButton   *bevent);
static gboolean   gimp_curve_view_motion_notify   (GtkWidget        *widget,
                                                   GdkEventMotion   *bevent);
static gboolean   gimp_curve_view_leave_notify    (GtkWidget        *widget,
                                                   GdkEventCrossing *cevent);
static gboolean   gimp_curve_view_key_press       (GtkWidget        *widget,
                                                   GdkEventKey      *kevent);

static void       gimp_curve_view_cut_clipboard   (GimpCurveView    *view);
static void       gimp_curve_view_copy_clipboard  (GimpCurveView    *view);
static void       gimp_curve_view_paste_clipboard (GimpCurveView    *view);

static void       gimp_curve_view_set_cursor      (GimpCurveView    *view,
                                                   gdouble           x,
                                                   gdouble           y);
static void       gimp_curve_view_unset_cursor    (GimpCurveView *view);
104

105 106 107 108 109 110

G_DEFINE_TYPE (GimpCurveView, gimp_curve_view,
               GIMP_TYPE_HISTOGRAM_VIEW)

#define parent_class gimp_curve_view_parent_class

111 112
static guint curve_view_signals[LAST_SIGNAL] = { 0 };

113 114 115 116

static void
gimp_curve_view_class_init (GimpCurveViewClass *klass)
{
117 118
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
119
  GtkBindingSet  *binding_set;
120

121 122
  object_class->finalize             = gimp_curve_view_finalize;
  object_class->dispose              = gimp_curve_view_dispose;
123 124
  object_class->set_property         = gimp_curve_view_set_property;
  object_class->get_property         = gimp_curve_view_get_property;
125

126
  widget_class->style_updated        = gimp_curve_view_style_updated;
127
  widget_class->draw                 = gimp_curve_view_draw;
128 129 130
  widget_class->button_press_event   = gimp_curve_view_button_press;
  widget_class->button_release_event = gimp_curve_view_button_release;
  widget_class->motion_notify_event  = gimp_curve_view_motion_notify;
131 132
  widget_class->leave_notify_event   = gimp_curve_view_leave_notify;
  widget_class->key_press_event      = gimp_curve_view_key_press;
133

134 135 136 137 138 139 140 141 142 143
  klass->cut_clipboard               = gimp_curve_view_cut_clipboard;
  klass->copy_clipboard              = gimp_curve_view_copy_clipboard;
  klass->paste_clipboard             = gimp_curve_view_paste_clipboard;

  g_object_class_install_property (object_class, PROP_GIMP,
                                   g_param_spec_object ("gimp",
                                                        NULL, NULL,
                                                        GIMP_TYPE_GIMP,
                                                        GIMP_PARAM_READWRITE));

144 145 146 147 148 149
  g_object_class_install_property (object_class, PROP_BASE_LINE,
                                   g_param_spec_boolean ("base-line",
                                                         NULL, NULL,
                                                         TRUE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT_ONLY));
150

151 152 153 154 155
  g_object_class_install_property (object_class, PROP_GRID_ROWS,
                                   g_param_spec_int ("grid-rows", NULL, NULL,
                                                     0, 100, 8,
                                                     GIMP_PARAM_READWRITE |
                                                     G_PARAM_CONSTRUCT_ONLY));
156

157 158 159 160 161
  g_object_class_install_property (object_class, PROP_GRID_COLUMNS,
                                   g_param_spec_int ("grid-columns", NULL, NULL,
                                                     0, 100, 8,
                                                     GIMP_PARAM_READWRITE |
                                                     G_PARAM_CONSTRUCT_ONLY));
162

163 164 165 166 167 168 169 170 171 172
  g_object_class_install_property (object_class, PROP_X_AXIS_LABEL,
                                   g_param_spec_string ("x-axis-label", NULL, NULL,
                                                        NULL,
                                                        GIMP_PARAM_READWRITE));

  g_object_class_install_property (object_class, PROP_Y_AXIS_LABEL,
                                   g_param_spec_string ("y-axis-label", NULL, NULL,
                                                        NULL,
                                                        GIMP_PARAM_READWRITE));

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
  curve_view_signals[CUT_CLIPBOARD] =
    g_signal_new ("cut-clipboard",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GimpCurveViewClass, cut_clipboard),
                  NULL, NULL,
                  gimp_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  curve_view_signals[COPY_CLIPBOARD] =
    g_signal_new ("copy-clipboard",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GimpCurveViewClass, copy_clipboard),
                  NULL, NULL,
                  gimp_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  curve_view_signals[PASTE_CLIPBOARD] =
    g_signal_new ("paste-clipboard",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GimpCurveViewClass, paste_clipboard),
                  NULL, NULL,
                  gimp_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  binding_set = gtk_binding_set_by_class (klass);

202
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK,
203
                                "cut-clipboard", 0);
204
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK,
205
                                "copy-clipboard", 0);
206
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK,
207
                                "paste-clipboard", 0);
208 209 210 211 212
}

static void
gimp_curve_view_init (GimpCurveView *view)
{
213 214
  view->curve       = NULL;
  view->selected    = 0;
215 216
  view->last_x      = 0.0;
  view->last_y      = 0.0;
217
  view->cursor_type = -1;
218
  view->xpos        = -1.0;
219 220
  view->cursor_x    = -1.0;
  view->cursor_y    = -1.0;
221 222 223 224
  view->range_x_min = 0.0;
  view->range_x_max = 1.0;
  view->range_y_min = 0.0;
  view->range_y_max = 1.0;
225

226 227 228
  view->x_axis_label = NULL;
  view->y_axis_label = NULL;

229
  gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE);
230 231 232
  gtk_widget_add_events (GTK_WIDGET (view),
                         GDK_BUTTON_PRESS_MASK   |
                         GDK_BUTTON_RELEASE_MASK |
233
                         GDK_BUTTON1_MOTION_MASK |
234 235 236 237 238 239 240 241 242 243
                         GDK_POINTER_MOTION_MASK |
                         GDK_KEY_PRESS_MASK      |
                         GDK_LEAVE_NOTIFY_MASK);
}

static void
gimp_curve_view_finalize (GObject *object)
{
  GimpCurveView *view = GIMP_CURVE_VIEW (object);

244 245
  g_clear_object (&view->layout);
  g_clear_object (&view->cursor_layout);
246

247 248
  g_clear_pointer (&view->x_axis_label, g_free);
  g_clear_pointer (&view->y_axis_label, g_free);
249

250 251 252 253 254 255 256 257
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_curve_view_dispose (GObject *object)
{
  GimpCurveView *view = GIMP_CURVE_VIEW (object);

258
  gimp_curve_view_set_curve (view, NULL, NULL);
259

260 261
  if (view->bg_curves)
    gimp_curve_view_remove_all_backgrounds (view);
262

263 264 265
  G_OBJECT_CLASS (parent_class)->dispose (object);
}

266
static void
267 268 269 270
gimp_curve_view_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
271 272 273 274 275
{
  GimpCurveView *view = GIMP_CURVE_VIEW (object);

  switch (property_id)
    {
276 277 278
    case PROP_GIMP:
      view->gimp = g_value_get_object (value); /* don't ref */
      break;
279 280 281 282 283 284
    case PROP_GRID_ROWS:
      view->grid_rows = g_value_get_int (value);
      break;
    case PROP_GRID_COLUMNS:
      view->grid_columns = g_value_get_int (value);
      break;
285 286 287
    case PROP_BASE_LINE:
      view->draw_base_line = g_value_get_boolean (value);
      break;
288
    case PROP_X_AXIS_LABEL:
289
      gimp_curve_view_set_x_axis_label (view, g_value_get_string (value));
290 291
      break;
    case PROP_Y_AXIS_LABEL:
292
      gimp_curve_view_set_y_axis_label (view, g_value_get_string (value));
293
      break;
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_curve_view_get_property (GObject    *object,
                              guint       property_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  GimpCurveView *view = GIMP_CURVE_VIEW (object);

  switch (property_id)
    {
310 311 312
    case PROP_GIMP:
      g_value_set_object (value, view->gimp);
      break;
313 314 315 316 317 318
    case PROP_GRID_ROWS:
      g_value_set_int (value, view->grid_rows);
      break;
    case PROP_GRID_COLUMNS:
      g_value_set_int (value, view->grid_columns);
      break;
319 320 321
    case PROP_BASE_LINE:
      g_value_set_boolean (value, view->draw_base_line);
      break;
322
    case PROP_X_AXIS_LABEL:
323
      g_value_set_string (value, view->x_axis_label);
324 325
      break;
    case PROP_Y_AXIS_LABEL:
326
      g_value_set_string (value, view->y_axis_label);
327
      break;
328 329 330 331 332 333
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

334
static void
335
gimp_curve_view_style_updated (GtkWidget *widget)
336 337 338
{
  GimpCurveView *view = GIMP_CURVE_VIEW (widget);

339
  GTK_WIDGET_CLASS (parent_class)->style_updated (widget);
340

341 342
  g_clear_object (&view->layout);
  g_clear_object (&view->cursor_layout);
343 344
}

345 346 347 348 349 350 351 352 353 354 355
static void
gimp_curve_view_draw_grid (GimpCurveView *view,
                           cairo_t       *cr,
                           gint           width,
                           gint           height,
                           gint           border)
{
  gint i;

  for (i = 1; i < view->grid_rows; i++)
    {
356
      gint y = i * (height - 1) / view->grid_rows;
357 358 359 360 361 362 363 364 365 366

      if ((view->grid_rows % 2) == 0 && (i == view->grid_rows / 2))
        continue;

      cairo_move_to (cr, border,             border + y);
      cairo_line_to (cr, border + width - 1, border + y);
    }

  for (i = 1; i < view->grid_columns; i++)
    {
367
      gint x = i * (width - 1) / view->grid_columns;
368 369 370 371 372 373 374 375

      if ((view->grid_columns % 2) == 0 && (i == view->grid_columns / 2))
        continue;

      cairo_move_to (cr, border + x, border);
      cairo_line_to (cr, border + x, border + height - 1);
    }

376 377 378 379 380 381
  if (view->draw_base_line)
    {
      cairo_move_to (cr, border, border + height - 1);
      cairo_line_to (cr, border + width - 1, border);
    }

382 383 384 385 386
  cairo_set_line_width (cr, 0.6);
  cairo_stroke (cr);

  if ((view->grid_rows % 2) == 0)
    {
387
      gint y = (height - 1) / 2;
388 389 390 391 392 393 394

      cairo_move_to (cr, border,             border + y);
      cairo_line_to (cr, border + width - 1, border + y);
    }

  if ((view->grid_columns % 2) == 0)
    {
395
      gint x = (width - 1) / 2;
396 397 398 399 400 401 402 403 404

      cairo_move_to (cr, border + x, border);
      cairo_line_to (cr, border + x, border + height - 1);
    }

  cairo_set_line_width (cr, 1.0);
  cairo_stroke (cr);
}

405 406 407 408 409 410 411 412
static void
gimp_curve_view_draw_point (GimpCurveView *view,
                            cairo_t       *cr,
                            gint           i,
                            gint           width,
                            gint           height,
                            gint           border)
{
413
  gdouble x, y;
414

415
  gimp_curve_get_point (view->curve, i, &x, &y);
Michael Natterer's avatar
Michael Natterer committed
416

417
  if (x < 0.0)
418 419
    return;

420 421
  y = 1.0 - y;

Michael Natterer's avatar
Michael Natterer committed
422
#define RADIUS 3
423 424

  cairo_move_to (cr,
425 426
                 border + (gdouble) (width  - 1) * x + RADIUS,
                 border + (gdouble) (height - 1) * y);
427
  cairo_arc (cr,
428 429
             border + (gdouble) (width  - 1) * x,
             border + (gdouble) (height - 1) * y,
Michael Natterer's avatar
Michael Natterer committed
430
             RADIUS,
431 432 433
             0, 2 * G_PI);
}

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
static void
gimp_curve_view_draw_curve (GimpCurveView *view,
                            cairo_t       *cr,
                            GimpCurve     *curve,
                            gint           width,
                            gint           height,
                            gint           border)
{
  gdouble x, y;
  gint    i;

  x = 0.0;
  y = 1.0 - gimp_curve_map_value (curve, 0.0);

  cairo_move_to (cr,
449 450
                 border + (gdouble) (width  - 1) * x,
                 border + (gdouble) (height - 1)* y);
451 452 453 454 455 456 457

  for (i = 1; i < 256; i++)
    {
      x = (gdouble) i / 255.0;
      y = 1.0 - gimp_curve_map_value (curve, x);

      cairo_line_to (cr,
458 459
                     border + (gdouble) (width  - 1) * x,
                     border + (gdouble) (height - 1) * y);
460 461 462 463 464
    }

  cairo_stroke (cr);
}

465
static gboolean
466 467
gimp_curve_view_draw (GtkWidget *widget,
                      cairo_t   *cr)
468
{
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
  GimpCurveView   *view  = GIMP_CURVE_VIEW (widget);
  GtkStyleContext *style = gtk_widget_get_style_context (widget);
  GtkAllocation    allocation;
  GdkRGBA          grid_color;
  GdkRGBA          fg_color;
  GdkRGBA          bg_color;
  GList           *list;
  gint             border;
  gint             width;
  gint             height;
  gint             layout_x;
  gint             layout_y;
  gdouble          x, y;
  gint             i;

  cairo_save (cr);
485
  GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
486
  cairo_restore (cr);
487 488 489 490

  if (! view->curve)
    return FALSE;

491 492 493
  gtk_style_context_save (style);
  gtk_style_context_add_class (style, "view");

494
  gtk_widget_get_allocation (widget, &allocation);
495

496
  border = GIMP_HISTOGRAM_VIEW (view)->border_width;
497 498
  width  = allocation.width  - 2 * border;
  height = allocation.height - 2 * border;
499

500 501
  if (gtk_widget_has_focus (widget))
    {
502 503 504
      gtk_render_focus (style, cr,
                        border - 2, border - 2,
                        width + 4, height + 4);
505 506
    }

507 508 509 510
  cairo_set_line_width (cr, 1.0);
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
  cairo_translate (cr, 0.5, 0.5);

511 512 513 514 515 516 517 518 519 520 521
  gtk_style_context_get_color (style, gtk_style_context_get_state (style),
                               &fg_color);
  bg_color       = fg_color;
  bg_color.red   = 1 - bg_color.red;
  bg_color.green = 1 - bg_color.green;
  bg_color.blue  = 1 - bg_color.blue;

  gtk_style_context_add_class (style, "grid");
  gtk_style_context_get_color (style, gtk_style_context_get_state (style),
                               &grid_color);
  gtk_style_context_remove_class (style, "grid");
522

523
  /*  Draw the grid lines  */
524
  gdk_cairo_set_source_rgba (cr, &grid_color);
525 526

  gimp_curve_view_draw_grid (view, cr, width, height, border);
Michael Natterer's avatar
Michael Natterer committed
527

528 529 530 531 532
  /*  Draw the axis labels  */

  if (view->x_axis_label)
    {
      if (! view->layout)
533
        view->layout = gtk_widget_create_pango_layout (widget, NULL);
534 535 536 537 538 539

      pango_layout_set_text (view->layout, view->x_axis_label, -1);
      pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y);

      cairo_move_to (cr,
                     width - border - layout_x,
540 541
                     height - border - layout_y);

542 543 544 545 546 547
      pango_cairo_show_layout (cr, view->layout);
    }

  if (view->y_axis_label)
    {
      if (! view->layout)
548
        view->layout = gtk_widget_create_pango_layout (widget, NULL);
549 550 551 552 553

      pango_layout_set_text (view->layout, view->y_axis_label, -1);
      pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y);

      cairo_save (cr);
554

555 556
      cairo_move_to (cr,
                     2 * border,
557 558 559
                     2 * border + layout_x);
      cairo_rotate (cr, - G_PI / 2);

560
      pango_cairo_show_layout (cr, view->layout);
561

562 563 564 565
      cairo_restore (cr);
    }


566 567
  /*  Draw the background curves  */
  for (list = view->bg_curves; list; list = g_list_next (list))
568
    {
569
      BGCurve *bg = list->data;
570

571 572 573 574 575 576 577 578 579 580 581
      if (bg->color_set)
        {
          cairo_set_source_rgba (cr,
                                 bg->color.r,
                                 bg->color.g,
                                 bg->color.b,
                                 0.5);
        }
      else
        {
          cairo_set_source_rgba (cr,
582 583 584
                                 fg_color.red,
                                 fg_color.green,
                                 fg_color.blue,
585 586
                                 0.5);
        }
587 588 589

      gimp_curve_view_draw_curve (view, cr, bg->curve,
                                  width, height, border);
590 591
    }

592
  /*  Draw the curve  */
593 594 595
  if (view->curve_color)
    gimp_cairo_set_source_rgb (cr, view->curve_color);
  else
596
    gdk_cairo_set_source_rgba (cr, &fg_color);
597 598 599

  gimp_curve_view_draw_curve (view, cr, view->curve,
                              width, height, border);
600

601
  /*  Draw the points  */
602
  if (gimp_curve_get_curve_type (view->curve) == GIMP_CURVE_SMOOTH)
603
    {
604
      /*  Draw the unselected points  */
605
      for (i = 0; i < view->curve->n_points; i++)
606 607
        {
          if (i == view->selected)
608
            continue;
Michael Natterer's avatar
Michael Natterer committed
609

610
          gimp_curve_view_draw_point (view, cr, i, width, height, border);
611
        }
Michael Natterer's avatar
Michael Natterer committed
612

613 614 615 616 617 618 619 620 621
      cairo_stroke (cr);

      /*  Draw the selected point  */
      if (view->selected != -1)
        {
          gimp_curve_view_draw_point (view, cr, view->selected,
                                      width, height, border);
          cairo_fill (cr);
       }
622 623
    }

624
  if (view->xpos >= 0.0)
625 626 627 628
    {
      gchar buf[32];

      /* draw the color line */
Michael Natterer's avatar
Michael Natterer committed
629
      cairo_move_to (cr,
630 631
                     border + ROUND ((gdouble) (width - 1) * view->xpos),
                     border + 1);
Michael Natterer's avatar
Michael Natterer committed
632
      cairo_line_to (cr,
633
                     border + ROUND ((gdouble) (width - 1) * view->xpos),
634
                     border + height - 1);
Michael Natterer's avatar
Michael Natterer committed
635
      cairo_stroke (cr);
636

637 638
      if (view->range_x_max == 255.0)
        {
639 640
          /*  stupid heuristic: special-case for 0..255  */

641 642 643 644 645
          g_snprintf (buf, sizeof (buf), "x:%3d",
                      (gint) (view->xpos *
                              (view->range_x_max - view->range_x_min) +
                              view->range_x_min));
        }
646 647 648 649 650 651 652 653 654
      else if (view->range_x_max == 100.0)
        {
          /*  and for 0..100  */

          g_snprintf (buf, sizeof (buf), "x:%0.2f",
                      view->xpos *
                      (view->range_x_max - view->range_x_min) +
                      view->range_x_min);
        }
655 656 657 658 659 660 661
      else
        {
          g_snprintf (buf, sizeof (buf), "x:%0.3f",
                      view->xpos *
                      (view->range_x_max - view->range_x_min) +
                      view->range_x_min);
        }
662

663 664
      if (! view->layout)
        view->layout = gtk_widget_create_pango_layout (widget, NULL);
665

666 667
      pango_layout_set_text (view->layout, buf, -1);
      pango_layout_get_pixel_size (view->layout, &layout_x, &layout_y);
668

669
      if (view->xpos < 0.5)
670
        layout_x = border;
671
      else
672
        layout_x = -(layout_x + border);
673

Michael Natterer's avatar
Michael Natterer committed
674
      cairo_move_to (cr,
675
                     border + (gdouble) width * view->xpos + layout_x,
676
                     border + height - border - layout_y);
677
      pango_cairo_show_layout (cr, view->layout);
678 679
    }

680 681
  if (view->cursor_x >= 0.0 && view->cursor_x <= 1.0 &&
      view->cursor_y >= 0.0 && view->cursor_y <= 1.0)
682
    {
683 684
      gchar  buf[32];
      gint   w, h;
685 686

      if (! view->cursor_layout)
687
        view->cursor_layout = gtk_widget_create_pango_layout (widget, NULL);
688

689 690 691
      if (view->range_x_max == 255.0 &&
          view->range_y_max == 255.0)
        {
692 693
          /*  stupid heuristic: special-case for 0..255  */

694 695 696 697 698 699 700 701
          g_snprintf (buf, sizeof (buf), "x:%3d y:%3d",
                      (gint) (view->cursor_x *
                              (view->range_x_max - view->range_x_min) +
                              view->range_x_min),
                      (gint) ((1.0 - view->cursor_y) *
                              (view->range_y_max - view->range_y_min) +
                              view->range_y_min));
        }
702 703 704 705 706 707 708 709 710 711 712 713 714
      else if (view->range_x_max == 100.0 &&
               view->range_y_max == 100.0)
        {
          /*  and for 0..100  */

          g_snprintf (buf, sizeof (buf), "x:%0.2f y:%0.2f",
                      view->cursor_x *
                      (view->range_x_max - view->range_x_min) +
                      view->range_x_min,
                      (1.0 - view->cursor_y) *
                      (view->range_y_max - view->range_y_min) +
                      view->range_y_min);
        }
715 716 717 718 719 720 721 722 723 724 725
      else
        {
          g_snprintf (buf, sizeof (buf), "x:%0.3f y:%0.3f",
                      view->cursor_x *
                      (view->range_x_max - view->range_x_min) +
                      view->range_x_min,
                      (1.0 - view->cursor_y) *
                      (view->range_y_max - view->range_y_min) +
                      view->range_y_min);
        }

Michael Natterer's avatar
Michael Natterer committed
726
      pango_layout_set_text (view->cursor_layout, buf, -1);
727 728
      pango_layout_get_pixel_extents (view->cursor_layout,
                                      NULL, &view->cursor_rect);
729

730 731 732 733 734 735 736 737 738
      x = border * 2 + 3;
      y = border * 2 + 3;
      w = view->cursor_rect.width;
      h = view->cursor_rect.height;

      if (view->x_axis_label)
        x += border + view->cursor_rect.height; /* coincidentially the right value */

      cairo_push_group (cr);
739

740 741 742 743 744 745 746
      cairo_rectangle (cr, x + 0.5, y + 0.5, w, h);
      cairo_fill_preserve (cr);

      cairo_set_line_width (cr, 6);
      cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
      cairo_stroke (cr);

747 748
      gdk_cairo_set_source_rgba (cr, &bg_color);

749
      cairo_move_to (cr, x, y);
Michael Natterer's avatar
Michael Natterer committed
750
      pango_cairo_show_layout (cr, view->cursor_layout);
751 752 753

      cairo_pop_group_to_source (cr);
      cairo_paint_with_alpha (cr, 0.6);
754 755
    }

756 757
  gtk_style_context_restore (style);

758 759 760
  return FALSE;
}

761 762 763 764 765 766 767 768 769
static void
set_cursor (GimpCurveView *view,
            GdkCursorType  new_cursor)
{
  if (new_cursor != view->cursor_type)
    {
      GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (view));
      GdkCursor  *cursor  = gdk_cursor_new_for_display (display, new_cursor);

770
      gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (view)), cursor);
771
      g_object_unref (cursor);
772 773 774 775 776

      view->cursor_type = new_cursor;
    }
}

777 778 779 780
static gboolean
gimp_curve_view_button_press (GtkWidget      *widget,
                              GdkEventButton *bevent)
{
781 782
  GimpCurveView *view  = GIMP_CURVE_VIEW (widget);
  GimpCurve     *curve = view->curve;
783
  GtkAllocation  allocation;
784 785
  gint           border;
  gint           width, height;
786 787
  gdouble        x;
  gdouble        y;
788 789 790 791 792
  gint           closest_point;
  gint           i;

  if (! curve || bevent->button != 1)
    return TRUE;
793

794 795
  gtk_widget_get_allocation (widget, &allocation);

796
  border = GIMP_HISTOGRAM_VIEW (view)->border_width;
797 798
  width  = allocation.width  - 2 * border;
  height = allocation.height - 2 * border;
799

800 801
  x = (gdouble) (bevent->x - border) / (gdouble) width;
  y = (gdouble) (bevent->y - border) / (gdouble) height;
802

803 804
  x = CLAMP (x, 0.0, 1.0);
  y = CLAMP (y, 0.0, 1.0);
805 806 807 808 809 810 811

  closest_point = gimp_curve_get_closest_point (curve, x);

  view->grabbed = TRUE;

  set_cursor (view, GDK_TCROSS);

812
  switch (gimp_curve_get_curve_type (curve))
813 814 815
    {
    case GIMP_CURVE_SMOOTH:
      /*  determine the leftmost and rightmost points  */
816
      view->leftmost = -1.0;
817
      for (i = closest_point - 1; i >= 0; i--)
818 819 820 821 822 823 824 825 826 827 828
        {
          gdouble point_x;

          gimp_curve_get_point (curve, i, &point_x, NULL);

          if (point_x >= 0.0)
            {
              view->leftmost = point_x;
              break;
            }
        }
829

830
      view->rightmost = 2.0;
831
      for (i = closest_point + 1; i < curve->n_points; i++)
832 833 834 835 836 837 838 839 840 841 842
        {
          gdouble point_x;

          gimp_curve_get_point (curve, i, &point_x, NULL);

          if (point_x >= 0.0)
            {
              view->rightmost = point_x;
              break;
            }
        }
843 844 845

      gimp_curve_view_set_selected (view, closest_point);

846
      gimp_curve_set_point (curve, view->selected, x, 1.0 - y);
847 848 849
      break;

    case GIMP_CURVE_FREE:
850 851
      view->last_x = x;
      view->last_y = y;
852

853
      gimp_curve_set_curve (curve, x, 1.0 - y);
854 855 856
      break;
    }

857
  if (! gtk_widget_has_focus (widget))
858 859 860
    gtk_widget_grab_focus (widget);

  return TRUE;
861 862 863 864 865 866 867 868
}

static gboolean
gimp_curve_view_button_release (GtkWidget      *widget,
                                GdkEventButton *bevent)
{
  GimpCurveView *view = GIMP_CURVE_VIEW (widget);

869 870 871 872 873 874 875 876
  if (bevent->button != 1)
    return TRUE;

  view->grabbed = FALSE;

  set_cursor (view, GDK_FLEUR);

  return TRUE;
877 878 879 880 881
}

static gboolean
gimp_curve_view_motion_notify (GtkWidget      *widget,
                               GdkEventMotion *mevent)
882 883 884
{
  GimpCurveView  *view       = GIMP_CURVE_VIEW (widget);
  GimpCurve      *curve      = view->curve;
885
  GtkAllocation   allocation;
886
  GdkCursorType   new_cursor = GDK_X_CURSOR;
887 888
  gint            border;
  gint            width, height;
889 890
  gdouble         x;
  gdouble         y;
891
  gdouble         point_x;
892 893 894 895 896
  gint            closest_point;

  if (! curve)
    return TRUE;

897 898
  gtk_widget_get_allocation (widget, &allocation);

899
  border = GIMP_HISTOGRAM_VIEW (view)->border_width;
900 901
  width  = allocation.width  - 2 * border;
  height = allocation.height - 2 * border;
902

903 904
  x = (gdouble) (mevent->x - border) / (gdouble) width;
  y = (gdouble) (mevent->y - border) / (gdouble) height;
905

906 907
  x = CLAMP (x, 0.0, 1.0);
  y = CLAMP (y, 0.0, 1.0);
908 909 910

  closest_point = gimp_curve_get_closest_point (curve, x);

911
  switch (gimp_curve_get_curve_type (curve))
912 913 914 915
    {
    case GIMP_CURVE_SMOOTH:
      if (! view->grabbed) /*  If no point is grabbed...  */
        {
916
          gimp_curve_get_point (curve, closest_point, &point_x, NULL);
917 918

          if (point_x >= 0.0)
919 920 921 922 923 924 925 926
            new_cursor = GDK_FLEUR;
          else
            new_cursor = GDK_TCROSS;
        }
      else /*  Else, drag the grabbed point  */
        {
          new_cursor = GDK_TCROSS;

927 928
          gimp_data_freeze (GIMP_DATA (curve));

929
          gimp_curve_set_point (curve, view->selected, -1.0, -1.0);
930 931 932

          if (x > view->leftmost && x < view->rightmost)
            {
933 934 935
              gint n_points = gimp_curve_get_n_points (curve);

              closest_point = ROUND (x * (gdouble) (n_points - 1));
936 937 938 939

              gimp_curve_get_point (curve, closest_point, &point_x, NULL);

              if (point_x < 0.0)
940 941
                gimp_curve_view_set_selected (view, closest_point);

942
              gimp_curve_set_point (curve, view->selected, x, 1.0 - y);
943
            }
944 945

          gimp_data_thaw (GIMP_DATA (curve));
946 947 948 949 950 951
        }
      break;

    case GIMP_CURVE_FREE:
      if (view->grabbed)
        {
952
          gint    n_samples = gimp_curve_get_n_samples (curve);
953 954
          gdouble x1, x2;
          gdouble y1, y2;
955

956
          if (view->last_x > x)
957 958
            {
              x1 = x;
959
              x2 = view->last_x;
960
              y1 = y;
961
              y2 = view->last_y;
962 963 964
            }
          else
            {
965
              x1 = view->last_x;
966
              x2 = x;
967
              y1 = view->last_y;
968 969 970 971 972
              y2 = y;
            }

          if (x2 != x1)
            {
973 974
              gint from = ROUND (x1 * (gdouble) (n_samples - 1));
              gint to   = ROUND (x2 * (gdouble) (n_samples - 1));
975 976
              gint i;

977 978
              gimp_data_freeze (GIMP_DATA (curve));

979 980 981 982 983
              for (i = from; i <= to; i++)
                {
                  gdouble xpos = (gdouble) i / (gdouble) (n_samples - 1);
                  gdouble ypos = (y1 + ((y2 - y1) * (xpos - x1)) / (x2 - x1));

984 985 986
                  xpos = CLAMP (xpos, 0.0, 1.0);
                  ypos = CLAMP (ypos, 0.0, 1.0);

987 988
                  gimp_curve_set_curve (curve, xpos, 1.0 - ypos);
                }
989 990

              gimp_data_thaw (GIMP_DATA (curve));
991 992 993
            }
          else
            {
994
              gimp_curve_set_curve (curve, x, 1.0 - y);
995 996
            }

997 998
          view->last_x = x;
          view->last_y = y;
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
        }

      if (mevent->state & GDK_BUTTON1_MASK)
        new_cursor = GDK_TCROSS;
      else
        new_cursor = GDK_PENCIL;

      break;
    }

  set_cursor (view, new_cursor);

  gimp_curve_view_set_cursor (view, x, y);

  return TRUE;
}

static gboolean
gimp_curve_view_leave_notify (GtkWidget        *widget,
                              GdkEventCrossing *cevent)
1019 1020 1021
{
  GimpCurveView *view = GIMP_CURVE_VIEW (widget);

1022
  gimp_curve_view_unset_cursor (view);
1023 1024 1025 1026 1027 1028 1029 1030

  return TRUE;
}

static gboolean
gimp_curve_view_key_press (GtkWidget   *widget,
                           GdkEventKey *kevent)
{
1031 1032 1033
  GimpCurveView *view    = GIMP_CURVE_VIEW (widget);
  GimpCurve     *curve   = view->curve;
  gboolean       handled = FALSE;
1034

1035 1036 1037 1038 1039
  if (! view->grabbed && curve &&
      gimp_curve_get_curve_type (curve) == GIMP_CURVE_SMOOTH)
    {
      gint    i = view->selected;
      gdouble x, y;
1040

1041
      gimp_curve_get_point (curve, i, NULL, &y);
1042

1043
      switch (kevent->keyval)
1044
        {
1045
        case GDK_KEY_Left:
1046 1047 1048 1049 1050 1051 1052
          for (i = i - 1; i >= 0 && ! handled; i--)
            {
              gimp_curve_get_point (curve, i, &x, NULL);

              if (x >= 0.0)
                {
                  gimp_curve_view_set_selected (view, i);
1053

1054 1055 1056 1057 1058
                  handled = TRUE;
                }
            }
          break;

1059
        case GDK_KEY_Right:
1060
          for (i = i + 1; i < curve->n_points && ! handled; i++)
1061
            {
1062
              gimp_curve_get_point (curve, i, &x, NULL);
1063

1064 1065 1066 1067 1068 1069
              if (x >= 0.0)
                {
                  gimp_curve_view_set_selected (view, i);

                  handled = TRUE;
                }
1070
            }
1071
          break;
1072

1073
        case GDK_KEY_Up:
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
          if (y < 1.0)
            {
              y = y + (kevent->state & GDK_SHIFT_MASK ?
                       (16.0 / 255.0) : (1.0 / 255.0));

              gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0));

              handled = TRUE;
            }
          break;
1084

1085
        case GDK_KEY_Down:
1086
          if (y > 0)
1087
            {
1088 1089
              y = y - (kevent->state & GDK_SHIFT_MASK ?
                       (16.0 / 255.0) : (1.0 / 255.0));
1090

1091 1092 1093
              gimp_curve_move_point (curve, i, CLAMP (y, 0.0, 1.0));

              handled = TRUE;
1094
            }
1095 1096
          break;

1097
        case GDK_KEY_Delete:
1098 1099 1100
          gimp_curve_delete_point (curve, i);
          break;

1101 1102
        default:
          break;
1103
        }
1104
    }
1105

1106 1107 1108
  if (handled)
    {
      set_cursor (view, GDK_TCROSS);
1109

1110 1111
      return TRUE;
    }
1112

1113 1114
  return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, kevent);
}
1115

1116 1117 1118 1119
static void
gimp_curve_view_cut_clipboard (GimpCurveView *view)
{
  g_printerr ("%s\n", G_STRFUNC);
1120

1121 1122 1123 1124 1125
  if (! view->curve || ! view->gimp)
    {
      gtk_widget_error_bell (GTK_WIDGET (view));
      return;
    }
1126

1127
  gimp_curve_view_copy_clipboard (view);
1128

1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
  gimp_curve_reset (view->curve, FALSE);
}

static void
gimp_curve_view_copy_clipboard (GimpCurveView *view)
{
  GimpCurve *copy;

  g_printerr ("%s\n", G_STRFUNC);

  if (! view->curve || ! view->gimp)
    {
      gtk_widget_error_bell (GTK_WIDGET (view));
      return;
1143 1144
    }

1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
  copy = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (view->curve)));
  gimp_clipboard_set_curve (view->gimp, copy);
  g_object_unref (copy);
}

static void
gimp_curve_view_paste_clipboard (GimpCurveView *view)
{
  GimpCurve *copy;

  g_printerr ("%s\n", G_STRFUNC);
1156

1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
  if (! view->curve || ! view->gimp)
    {
      gtk_widget_error_bell (GTK_WIDGET (view));
      return;
    }

  copy = gimp_clipboard_get_curve (view->gimp);

  if (copy)
    {
      gimp_config_copy (GIMP_CONFIG (copy),
                        GIMP_CONFIG (view->curve), 0);
      g_object_unref (copy);
    }
1171 1172
}

1173 1174 1175

/*  public functions  */

1176 1177 1178
GtkWidget *
gimp_curve_view_new (void)
{
1179
  return g_object_new (GIMP_TYPE_CURVE_VIEW, NULL);
1180 1181 1182
}

static void
1183 1184
gimp_curve_view_curve_dirty (GimpCurve     *curve,
                             GimpCurveView *view)
1185
{
1186
  gtk_widget_queue_draw (GTK_WIDGET (view));
1187 1188 1189 1190
}

void
gimp_curve_view_set_curve (GimpCurveView *view,
1191 1192
                           GimpCurve     *curve,
                           const GimpRGB *color)
1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
  g_return_if_fail (curve == NULL || GIMP_IS_CURVE (curve));

  if (view->curve == curve)
    return;

  if (view->curve)
    {
      g_signal_handlers_disconnect_by_func (view->curve,
1203
                                            gimp_curve_view_curve_dirty,
1204 1205 1206 1207 1208 1209 1210 1211 1212
                                            view);
      g_object_unref (view->curve);
    }

  view->curve = curve;

  if (view->curve)
    {
      g_object_ref (view->curve);
1213 1214
      g_signal_connect (view->curve, "dirty",
                        G_CALLBACK (gimp_curve_view_curve_dirty),
1215 1216
                        view);
    }
1217

1218 1219 1220 1221 1222 1223 1224 1225
  if (view->curve_color)
    g_free (view->curve_color);

  if (color)
    view->curve_color = g_memdup (color, sizeof (GimpRGB));
  else
    view->curve_color = NULL;

1226
  gtk_widget_queue_draw (GTK_WIDGET (view));
1227 1228 1229 1230 1231 1232 1233 1234 1235 1236
}

GimpCurve *
gimp_curve_view_get_curve (GimpCurveView *view)
{
  g_return_val_if_fail (GIMP_IS_CURVE_VIEW (view), NULL);

  return view->curve;
}

1237 1238 1239 1240 1241
void
gimp_curve_view_add_background (GimpCurveView *view,
                                GimpCurve     *curve,
                                const GimpRGB *color)
{
1242
  GList   *list;
1243 1244 1245 1246 1247
  BGCurve *bg;

  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
  g_return_if_fail (GIMP_IS_CURVE (curve));

1248 1249 1250 1251 1252 1253 1254
  for (list = view->bg_curves; list; list = g_list_next (list))
    {
      bg = list->data;

      g_return_if_fail (curve != bg->curve);
    }

1255 1256 1257
  bg = g_slice_new0 (BGCurve);

  bg->curve = g_object_ref (curve);
1258 1259 1260 1261 1262 1263

  if (color)
    {
      bg->color     = *color;
      bg->color_set = TRUE;
    }
1264

1265 1266 1267 1268
  g_signal_connect (bg->curve, "dirty",
                    G_CALLBACK (gimp_curve_view_curve_dirty),
                    view);

1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288
  view->bg_curves = g_list_append (view->bg_curves, bg);

  gtk_widget_queue_draw (GTK_WIDGET (view));
}

void
gimp_curve_view_remove_background (GimpCurveView *view,
                                   GimpCurve     *curve)
{
  GList *list;

  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));
  g_return_if_fail (GIMP_IS_CURVE (curve));

  for (list = view->bg_curves; list; list = g_list_next (list))
    {
      BGCurve *bg = list->data;

      if (bg->curve == curve)
        {
1289 1290 1291
          g_signal_handlers_disconnect_by_func (bg->curve,
                                                gimp_curve_view_curve_dirty,
                                                view);
1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
          g_object_unref (bg->curve);

          view->bg_curves = g_list_remove (view->bg_curves, bg);

          g_slice_free (BGCurve, bg);

          gtk_widget_queue_draw (GTK_WIDGET (view));

          break;
        }
    }
1303 1304 1305

  if (! list)
    g_return_if_reached ();
1306 1307
}

1308 1309 1310 1311 1312
void
gimp_curve_view_remove_all_backgrounds (GimpCurveView *view)
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

1313
  while (view->bg_curves)
1314
    {
1315
      BGCurve *bg = view->bg_curves->data;
1316

1317 1318 1319
      g_signal_handlers_disconnect_by_func (bg->curve,
                                            gimp_curve_view_curve_dirty,
                                            view);
1320 1321 1322 1323 1324 1325 1326 1327 1328 1329
      g_object_unref (bg->curve);

      view->bg_curves = g_list_remove (view->bg_curves, bg);

      g_slice_free (BGCurve, bg);
    }

  gtk_widget_queue_draw (GTK_WIDGET (view));
}

1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
void
gimp_curve_view_set_selected (GimpCurveView *view,
                              gint           selected)
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

  view->selected = selected;

  gtk_widget_queue_draw (GTK_WIDGET (view));
}

1341 1342 1343 1344 1345 1346 1347 1348 1349
void
gimp_curve_view_set_range_x (GimpCurveView *view,
                             gdouble        min,
                             gdouble        max)
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

  view->range_x_min = min;
  view->range_x_max = max;
1350 1351

  gtk_widget_queue_draw (GTK_WIDGET (view));
1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362
}

void
gimp_curve_view_set_range_y (GimpCurveView *view,
                             gdouble        min,
                             gdouble        max)
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

  view->range_y_min = min;
  view->range_y_max = max;
1363 1364

  gtk_widget_queue_draw (GTK_WIDGET (view));
1365 1366
}

1367 1368
void
gimp_curve_view_set_xpos (GimpCurveView *view,
1369
                          gdouble        x)
1370 1371 1372 1373 1374 1375 1376 1377
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

  view->xpos = x;

  gtk_widget_queue_draw (GTK_WIDGET (view));
}

1378 1379 1380 1381 1382 1383 1384
void
gimp_curve_view_set_x_axis_label (GimpCurveView *view,
                                  const gchar   *label)
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

  if (view->x_axis_label)
1385 1386
    g_free (view->x_axis_label);

1387 1388 1389 1390 1391 1392
  view->x_axis_label = g_strdup (label);

  g_object_notify (G_OBJECT (view), "x-axis-label");

  gtk_widget_queue_draw (GTK_WIDGET (view));
}
1393

1394 1395 1396 1397 1398 1399 1400
void
gimp_curve_view_set_y_axis_label (GimpCurveView *view,
                                  const gchar   *label)
{
  g_return_if_fail (GIMP_IS_CURVE_VIEW (view));

  if (view->y_axis_label)
1401 1402
    g_free (view->y_axis_label);

1403 1404 1405 1406 1407 1408
  view->y_axis_label = g_strdup (label);

  g_object_notify (G_OBJECT (view), "y-axis-label");

  gtk_widget_queue_draw (GTK_WIDGET (view));
}
1409

1410

1411 1412 1413
/*  private functions  */

static void
1414
gimp_curve_view_set_cursor (GimpCurveView *view,
1415 1416
                            gdouble        x,
                            gdouble        y)
1417
{
1418 1419
  view->cursor_x = x;
  view->cursor_y = y;
1420

1421
  /* TODO: only invalidate the cursor label area */
1422 1423
  gtk_widget_queue_draw (GTK_WIDGET (view));
}
1424