gimptoolline.c 33.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimptoolline.c
 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
 *
 * Major improvements for interactivity
 * Copyright (C) 2014 Michael Henning <drawoc@darkrefraction.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"

#include "display-types.h"

#include "core/gimp-utils.h"

#include "widgets/gimpwidgets-utils.h"

#include "gimpcanvashandle.h"
#include "gimpcanvasline.h"
#include "gimpdisplayshell.h"
#include "gimptoolline.h"

43 44
#include "gimp-intl.h"

45

46
#define SHOW_LINE TRUE
47 48 49 50
#define ENDPOINT_GRIP_HANDLE_TYPE GIMP_HANDLE_CROSS
#define ENDPOINT_GRIP_HANDLE_SIZE GIMP_CANVAS_HANDLE_SIZE_CROSS
#define SLIDER_GRIP_HANDLE_TYPE   GIMP_HANDLE_FILLED_DIAMOND
#define SLIDER_GRIP_HANDLE_SIZE   (ENDPOINT_GRIP_HANDLE_SIZE * 2 / 3)
51 52 53 54 55 56 57 58


typedef enum
{
  /* POINT_NONE evaluates to FALSE */
  POINT_NONE = 0,
  POINT_START,
  POINT_END,
59 60
  POINT_BOTH,
  POINT_SLIDER
61 62 63 64 65 66 67 68
} GimpToolLinePoint;

enum
{
  PROP_0,
  PROP_X1,
  PROP_Y1,
  PROP_X2,
69
  PROP_Y2,
70
  PROP_SLIDERS,
71
  PROP_STATUS_TITLE,
72 73 74 75 76 77 78 79
};

struct _GimpToolLinePrivate
{
  gdouble            x1;
  gdouble            y1;
  gdouble            x2;
  gdouble            y2;
80
  GArray            *sliders;
81
  gchar             *status_title;
82 83 84 85 86

  gdouble            saved_x1;
  gdouble            saved_y1;
  gdouble            saved_x2;
  gdouble            saved_y2;
87
  gdouble            saved_slider_value;
88 89 90

  gdouble            mouse_x;
  gdouble            mouse_y;
91
  GimpToolLinePoint  point;
92
  gint               slider_index;
93
  gboolean           point_grabbed;
94 95 96

  GimpCanvasItem    *line;
  GimpCanvasItem    *start_handle_circle;
97
  GimpCanvasItem    *start_handle_grip;
98
  GimpCanvasItem    *end_handle_circle;
99 100 101
  GimpCanvasItem    *end_handle_grip;
  GArray            *slider_handle_circles;
  GArray            *slider_handle_grips;
102 103 104 105 106 107
};


/*  local function prototypes  */

static void     gimp_tool_line_constructed     (GObject               *object);
108
static void     gimp_tool_line_finalize        (GObject               *object);
109 110 111 112 113 114 115 116 117 118
static void     gimp_tool_line_set_property    (GObject               *object,
                                                guint                  property_id,
                                                const GValue          *value,
                                                GParamSpec            *pspec);
static void     gimp_tool_line_get_property    (GObject               *object,
                                                guint                  property_id,
                                                GValue                *value,
                                                GParamSpec            *pspec);

static void     gimp_tool_line_changed         (GimpToolWidget        *widget);
119
static gint     gimp_tool_line_button_press    (GimpToolWidget        *widget,
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
                                                const GimpCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                GimpButtonPressType    press_type);
static void     gimp_tool_line_button_release  (GimpToolWidget        *widget,
                                                const GimpCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                GimpButtonReleaseType  release_type);
static void     gimp_tool_line_motion          (GimpToolWidget        *widget,
                                                const GimpCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state);
static void     gimp_tool_line_hover           (GimpToolWidget        *widget,
                                                const GimpCoords      *coords,
                                                GdkModifierType        state,
                                                gboolean               proximity);
static void     gimp_tool_line_motion_modifier (GimpToolWidget        *widget,
                                                GdkModifierType        key,
                                                gboolean               press,
                                                GdkModifierType        state);
static gboolean gimp_tool_line_get_cursor      (GimpToolWidget        *widget,
                                                const GimpCoords      *coords,
                                                GdkModifierType        state,
                                                GimpCursorType        *cursor,
                                                GimpToolCursorType    *tool_cursor,
146
                                                GimpCursorModifier    *modifier);
147

148 149 150
static GimpControllerSlider *
                gimp_tool_line_get_slider      (GimpToolLine          *line,
                                                gint                   slider);
151
static gboolean gimp_tool_line_point_motion    (GimpToolLine          *line,
152
                                                gboolean               constrain);
153

154
static void     gimp_tool_line_update_handles  (GimpToolLine          *line);
155
static void     gimp_tool_line_update_hilight  (GimpToolLine          *line);
156 157 158
static void     gimp_tool_line_update_status   (GimpToolLine          *line,
                                                GdkModifierType        state,
                                                gboolean               proximity);
159 160 161 162 163 164 165 166 167 168 169 170 171 172


G_DEFINE_TYPE (GimpToolLine, gimp_tool_line, GIMP_TYPE_TOOL_WIDGET)

#define parent_class gimp_tool_line_parent_class


static void
gimp_tool_line_class_init (GimpToolLineClass *klass)
{
  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
  GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);

  object_class->constructed     = gimp_tool_line_constructed;
173
  object_class->finalize        = gimp_tool_line_finalize;
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 202 203 204 205 206 207 208 209 210 211 212
  object_class->set_property    = gimp_tool_line_set_property;
  object_class->get_property    = gimp_tool_line_get_property;

  widget_class->changed         = gimp_tool_line_changed;
  widget_class->button_press    = gimp_tool_line_button_press;
  widget_class->button_release  = gimp_tool_line_button_release;
  widget_class->motion          = gimp_tool_line_motion;
  widget_class->hover           = gimp_tool_line_hover;
  widget_class->motion_modifier = gimp_tool_line_motion_modifier;
  widget_class->get_cursor      = gimp_tool_line_get_cursor;

  g_object_class_install_property (object_class, PROP_X1,
                                   g_param_spec_double ("x1", NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE, 0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_Y1,
                                   g_param_spec_double ("y1", NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE, 0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_X2,
                                   g_param_spec_double ("x2", NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE, 0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_Y2,
                                   g_param_spec_double ("y2", NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE, 0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

213 214 215 216 217
  g_object_class_install_property (object_class, PROP_SLIDERS,
                                   g_param_spec_boxed ("sliders", NULL, NULL,
                                                       G_TYPE_ARRAY,
                                                       GIMP_PARAM_READWRITE));

218 219 220 221 222 223 224
  g_object_class_install_property (object_class, PROP_STATUS_TITLE,
                                   g_param_spec_string ("status-title",
                                                        NULL, NULL,
                                                        _("Line: "),
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

225 226 227 228 229 230
  g_type_class_add_private (klass, sizeof (GimpToolLinePrivate));
}

static void
gimp_tool_line_init (GimpToolLine *line)
{
231
  GimpToolLinePrivate *private;
232

233 234 235 236 237 238 239 240 241 242
  private = line->private = G_TYPE_INSTANCE_GET_PRIVATE (line,
                                                         GIMP_TYPE_TOOL_LINE,
                                                         GimpToolLinePrivate);

  private->sliders = g_array_new (FALSE, FALSE, sizeof (GimpControllerSlider));

  private->slider_handle_circles = g_array_new (FALSE, TRUE,
                                                sizeof (GimpCanvasItem *));
  private->slider_handle_grips   = g_array_new (FALSE, TRUE,
                                                sizeof (GimpCanvasItem *));
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
}

static void
gimp_tool_line_constructed (GObject *object)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (object);
  GimpToolWidget      *widget  = GIMP_TOOL_WIDGET (object);
  GimpToolLinePrivate *private = line->private;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  private->line = gimp_tool_widget_add_line (widget,
                                             private->x1,
                                             private->y1,
                                             private->x2,
                                             private->y2);

  gimp_canvas_item_set_visible (private->line, SHOW_LINE);

  private->start_handle_circle =
    gimp_tool_widget_add_handle (widget,
                                 GIMP_HANDLE_CIRCLE,
                                 private->x1,
                                 private->y1,
267 268
                                 2 * ENDPOINT_GRIP_HANDLE_SIZE,
                                 2 * ENDPOINT_GRIP_HANDLE_SIZE,
269 270
                                 GIMP_HANDLE_ANCHOR_CENTER);

271
  private->start_handle_grip =
272
    gimp_tool_widget_add_handle (widget,
273
                                 ENDPOINT_GRIP_HANDLE_TYPE,
274 275
                                 private->x1,
                                 private->y1,
276 277
                                 ENDPOINT_GRIP_HANDLE_SIZE,
                                 ENDPOINT_GRIP_HANDLE_SIZE,
278 279 280 281 282 283 284
                                 GIMP_HANDLE_ANCHOR_CENTER);

  private->end_handle_circle =
    gimp_tool_widget_add_handle (widget,
                                 GIMP_HANDLE_CIRCLE,
                                 private->x2,
                                 private->y2,
285 286
                                 2 * ENDPOINT_GRIP_HANDLE_SIZE,
                                 2 * ENDPOINT_GRIP_HANDLE_SIZE,
287 288
                                 GIMP_HANDLE_ANCHOR_CENTER);

289
  private->end_handle_grip =
290
    gimp_tool_widget_add_handle (widget,
291
                                 ENDPOINT_GRIP_HANDLE_TYPE,
292 293
                                 private->x2,
                                 private->y2,
294 295
                                 ENDPOINT_GRIP_HANDLE_SIZE,
                                 ENDPOINT_GRIP_HANDLE_SIZE,
296
                                 GIMP_HANDLE_ANCHOR_CENTER);
297 298

  gimp_tool_line_changed (widget);
299 300
}

301 302 303 304 305 306
static void
gimp_tool_line_finalize (GObject *object)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (object);
  GimpToolLinePrivate *private = line->private;

307 308 309 310
  g_clear_pointer (&private->sliders, g_array_unref);
  g_clear_pointer (&private->status_title, g_free);
  g_clear_pointer (&private->slider_handle_circles, g_array_unref);
  g_clear_pointer (&private->slider_handle_grips, g_array_unref);
311

312 313 314
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
static void
gimp_tool_line_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (object);
  GimpToolLinePrivate *private = line->private;

  switch (property_id)
    {
    case PROP_X1:
      private->x1 = g_value_get_double (value);
      break;
    case PROP_Y1:
      private->y1 = g_value_get_double (value);
      break;
    case PROP_X2:
      private->x2 = g_value_get_double (value);
      break;
    case PROP_Y2:
      private->y2 = g_value_get_double (value);
      break;

339 340 341 342 343 344 345
    case PROP_SLIDERS:
      g_return_if_fail (g_value_get_boxed (value) != NULL);

      g_array_unref (private->sliders);
      private->sliders = g_value_dup_boxed (value);
      break;

346 347 348 349 350 351 352
    case PROP_STATUS_TITLE:
      g_free (private->status_title);
      private->status_title = g_value_dup_string (value);
      if (! private->status_title)
        private->status_title = g_strdup (_("Line: "));
      break;

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_tool_line_get_property (GObject    *object,
                             guint       property_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (object);
  GimpToolLinePrivate *private = line->private;

  switch (property_id)
    {
    case PROP_X1:
      g_value_set_double (value, private->x1);
      break;
    case PROP_Y1:
      g_value_set_double (value, private->y1);
      break;
    case PROP_X2:
      g_value_set_double (value, private->x2);
      break;
    case PROP_Y2:
      g_value_set_double (value, private->y2);
      break;

383 384 385 386
    case PROP_SLIDERS:
      g_value_set_boxed (value, private->sliders);
      break;

387 388 389 390
    case PROP_STATUS_TITLE:
      g_value_set_string (value, private->status_title);
      break;

391 392 393 394 395 396 397 398 399 400 401
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_tool_line_changed (GimpToolWidget *widget)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (widget);
  GimpToolLinePrivate *private = line->private;
402
  gint                 i;
403 404 405 406 407 408 409 410 411 412

  gimp_canvas_line_set (private->line,
                        private->x1,
                        private->y1,
                        private->x2,
                        private->y2);

  gimp_canvas_handle_set_position (private->start_handle_circle,
                                   private->x1,
                                   private->y1);
413
  gimp_canvas_handle_set_position (private->start_handle_grip,
414 415 416 417 418 419
                                   private->x1,
                                   private->y1);

  gimp_canvas_handle_set_position (private->end_handle_circle,
                                   private->x2,
                                   private->y2);
420
  gimp_canvas_handle_set_position (private->end_handle_grip,
421 422 423
                                   private->x2,
                                   private->y2);

424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
  /* remove excessive slider handles */
  for (i = private->sliders->len; i < private->slider_handle_circles->len; i++)
    {
      gimp_tool_widget_remove_item (widget,
                                    g_array_index (private->slider_handle_circles,
                                                   GimpCanvasItem *, i));
      gimp_tool_widget_remove_item (widget,
                                    g_array_index (private->slider_handle_grips,
                                                   GimpCanvasItem *, i));
    }

  g_array_set_size (private->slider_handle_circles, private->sliders->len);
  g_array_set_size (private->slider_handle_grips,   private->sliders->len);

  for (i = 0; i < private->sliders->len; i++)
    {
      gdouble          t;
      gdouble          x;
      gdouble          y;
      GimpCanvasItem **circle;
      GimpCanvasItem **grip;

446
      t = gimp_tool_line_get_slider (line, i)->value;
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479

      x = private->x1 + (private->x2 - private->x1) * t;
      y = private->y1 + (private->y2 - private->y1) * t;

      circle = &g_array_index (private->slider_handle_circles,
                               GimpCanvasItem *, i);
      grip   = &g_array_index (private->slider_handle_grips,
                               GimpCanvasItem *, i);

      if (*circle)
        {
          gimp_canvas_handle_set_position (*circle, x, y);
          gimp_canvas_handle_set_position (*grip,   x, y);
        }
      else
        {
          *circle = gimp_tool_widget_add_handle (widget,
                                                 GIMP_HANDLE_CIRCLE,
                                                 x,
                                                 y,
                                                 2 * SLIDER_GRIP_HANDLE_SIZE,
                                                 2 * SLIDER_GRIP_HANDLE_SIZE,
                                                 GIMP_HANDLE_ANCHOR_CENTER);
          *grip   = gimp_tool_widget_add_handle (widget,
                                                 SLIDER_GRIP_HANDLE_TYPE,
                                                 x,
                                                 y,
                                                 SLIDER_GRIP_HANDLE_SIZE,
                                                 SLIDER_GRIP_HANDLE_SIZE,
                                                 GIMP_HANDLE_ANCHOR_CENTER);
        }
    }

480
  gimp_tool_line_update_handles (line);
481 482 483 484 485 486 487 488 489 490 491 492 493
  gimp_tool_line_update_hilight (line);
}

gboolean
gimp_tool_line_button_press (GimpToolWidget      *widget,
                             const GimpCoords    *coords,
                             guint32              time,
                             GdkModifierType      state,
                             GimpButtonPressType  press_type)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (widget);
  GimpToolLinePrivate *private = line->private;

494
  if (private->point != POINT_NONE)
495 496 497 498 499 500
    {
      private->saved_x1 = private->x1;
      private->saved_y1 = private->y1;
      private->saved_x2 = private->x2;
      private->saved_y2 = private->y2;

501 502 503
      if (private->point == POINT_SLIDER)
        {
          private->saved_slider_value =
504
            gimp_tool_line_get_slider (line, private->slider_index)->value;
505 506
        }

507 508
      private->point_grabbed = TRUE;

509 510 511
      gimp_tool_line_point_motion (line,
                                   state & gimp_get_constrain_behavior_mask ());

512
      return private->point;
513 514
    }

515 516
  gimp_tool_line_update_status (line, state, TRUE);

517
  return 0;
518 519 520 521 522 523 524 525 526 527 528 529 530 531
}

void
gimp_tool_line_button_release (GimpToolWidget        *widget,
                               const GimpCoords      *coords,
                               guint32                time,
                               GdkModifierType        state,
                               GimpButtonReleaseType  release_type)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (widget);
  GimpToolLinePrivate *private = line->private;

  if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
    {
532 533
      if (private->point == POINT_SLIDER)
        {
534
          gimp_tool_line_get_slider (line, private->slider_index)->value =
535 536 537
            private->saved_slider_value;
        }

538 539 540 541 542 543 544 545
      g_object_set (line,
                    "x1", private->saved_x1,
                    "y1", private->saved_y1,
                    "x2", private->saved_x2,
                    "y2", private->saved_y2,
                    NULL);
    }

546
  private->point_grabbed = FALSE;
547 548 549 550 551 552 553 554 555 556
}

void
gimp_tool_line_motion (GimpToolWidget   *widget,
                       const GimpCoords *coords,
                       guint32           time,
                       GdkModifierType   state)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (widget);
  GimpToolLinePrivate *private = line->private;
557 558
  gdouble              diff_x  = coords->x - private->mouse_x;
  gdouble              diff_y  = coords->y - private->mouse_y;
559 560 561 562

  private->mouse_x = coords->x;
  private->mouse_y = coords->y;

563
  if (private->point == POINT_BOTH)
564 565
    {
      g_object_set (line,
566 567 568 569
                    "x1", private->x1 + diff_x,
                    "y1", private->y1 + diff_y,
                    "x2", private->x2 + diff_x,
                    "y2", private->y2 + diff_y,
570 571 572 573 574 575 576 577
                    NULL);
    }
  else
    {
      gboolean constrain = (state & gimp_get_constrain_behavior_mask ()) != 0;

      gimp_tool_line_point_motion (line, constrain);
    }
578 579

  gimp_tool_line_update_status (line, state, TRUE);
580 581 582 583 584 585 586 587 588 589
}

void
gimp_tool_line_hover (GimpToolWidget   *widget,
                      const GimpCoords *coords,
                      GdkModifierType   state,
                      gboolean          proximity)
{
  GimpToolLine        *line    = GIMP_TOOL_LINE (widget);
  GimpToolLinePrivate *private = line->private;
590
  gint                 i;
591 592 593 594

  private->mouse_x = coords->x;
  private->mouse_y = coords->y;

595 596
  gimp_tool_line_update_handles (line);

597 598
  private->point = POINT_NONE;

599 600 601 602 603 604
  if (state & GDK_MOD1_MASK)
    {
      private->point = POINT_BOTH;
    }
  else
    {
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
      /* give sliders precedence over the endpoints, since they're smaller */
      for (i = private->sliders->len - 1; i >= 0; i--)
        {
          GimpCanvasItem *circle;

          circle = g_array_index (private->slider_handle_circles,
                                  GimpCanvasItem *, i);

          if (gimp_canvas_item_hit (circle, private->mouse_x, private->mouse_y))
            {
              private->point        = POINT_SLIDER;
              private->slider_index = i;

              break;
            }
        }

      if (private->point == POINT_NONE)
        {
          if (gimp_canvas_item_hit (private->end_handle_circle,
                                    private->mouse_x,
                                    private->mouse_y))
            {
              private->point = POINT_END;
            }
          else if (gimp_canvas_item_hit (private->start_handle_circle,
                                         private->mouse_x,
                                         private->mouse_y))
            {
              private->point = POINT_START;
            }
        }
637 638
    }

639
  gimp_tool_line_update_hilight (line);
640
  gimp_tool_line_update_status (line, state, proximity);
641 642 643 644 645 646 647 648 649 650 651 652 653
}

static void
gimp_tool_line_motion_modifier (GimpToolWidget  *widget,
                                GdkModifierType  key,
                                gboolean         press,
                                GdkModifierType  state)
{
  GimpToolLine *line = GIMP_TOOL_LINE (widget);

  if (key == gimp_get_constrain_behavior_mask ())
    {
      gimp_tool_line_point_motion (line, press);
654 655

      gimp_tool_line_update_status (line, state, TRUE);
656 657 658 659 660 661 662 663 664
    }
}

static gboolean
gimp_tool_line_get_cursor (GimpToolWidget     *widget,
                           const GimpCoords   *coords,
                           GdkModifierType     state,
                           GimpCursorType     *cursor,
                           GimpToolCursorType *tool_cursor,
665
                           GimpCursorModifier *modifier)
666
{
667 668
  GimpToolLine        *line    = GIMP_TOOL_LINE (widget);
  GimpToolLinePrivate *private = line->private;
669

670
  if (private->point == POINT_BOTH)
671
    {
672
      *modifier = GIMP_CURSOR_MODIFIER_MOVE;
673 674 675 676 677 678 679

      return TRUE;
    }

  return FALSE;
}

680 681 682 683 684 685 686 687 688 689 690
static GimpControllerSlider *
gimp_tool_line_get_slider (GimpToolLine *line,
                           gint          slider)
{
  GimpToolLinePrivate *private = line->private;

  g_assert (slider >= 0 && slider < private->sliders->len);

  return &g_array_index (private->sliders, GimpControllerSlider, slider);
}

691 692
static gboolean
gimp_tool_line_point_motion (GimpToolLine *line,
693
                             gboolean      constrain)
694 695 696 697 698
{
  GimpToolLinePrivate *private = line->private;
  gdouble              x       = private->mouse_x;
  gdouble              y       = private->mouse_y;

699
  switch (private->point)
700 701
    {
    case POINT_START:
702
      if (constrain)
703 704 705 706 707 708 709 710 711 712 713
        gimp_constrain_line (private->x2, private->y2,
                             &x, &y,
                             GIMP_CONSTRAIN_LINE_15_DEGREES);

      g_object_set (line,
                    "x1", x,
                    "y1", y,
                    NULL);
      return TRUE;

    case POINT_END:
714
      if (constrain)
715 716 717 718 719 720 721 722 723 724
        gimp_constrain_line (private->x1, private->y1,
                             &x, &y,
                             GIMP_CONSTRAIN_LINE_15_DEGREES);

      g_object_set (line,
                    "x2", x,
                    "y2", y,
                    NULL);
      return TRUE;

725 726
    case POINT_SLIDER:
      {
727
        gdouble length_sqr;
728

729 730
        length_sqr = SQR (private->x2 - private->x1) +
                     SQR (private->y2 - private->y1);
731

732 733 734 735 736 737 738
        /* don't change slider values of 0-length lines, since we'll just get
         * NaN.
         */
        if (length_sqr > 0.0)
          {
            GimpControllerSlider *slider;
            gdouble               t;
739

740
            slider = gimp_tool_line_get_slider (line, private->slider_index);
741

742 743 744 745
            /* project the cursor position onto the line */
            t  = (private->x2 - private->x1) * (x - private->x1) +
                 (private->y2 - private->y1) * (y - private->y1);
            t /= length_sqr;
746

747 748 749
            if (constrain)
              t = RINT (12.0 * t) / 12.0;

750 751
            t = CLAMP (t, slider->min, slider->max);
            t = CLAMP (t, 0.0, 1.0);
752

753
            t = fabs (t); /* avoid negative zero */
754 755 756 757 758 759 760

            slider->value = t;

            g_object_set (line,
                          "sliders", private->sliders,
                          NULL);
          }
761 762 763 764

        return TRUE;
      }

765 766 767 768 769 770 771 772
    default:
      break;
    }

  return FALSE;
}

static void
773
gimp_tool_line_update_handles (GimpToolLine *line)
774 775 776 777
{
  GimpToolLinePrivate *private = line->private;
  gboolean             start_visible,  end_visible;
  gint                 start_diameter, end_diameter;
778
  gint                 i;
779 780

  /* Calculate handle visibility */
781
  if (private->point_grabbed)
782 783 784 785 786 787
    {
      start_visible = FALSE;
      end_visible   = FALSE;
    }
  else
    {
788 789 790 791
      start_diameter = gimp_canvas_handle_calc_size (private->start_handle_circle,
                                                     private->mouse_x,
                                                     private->mouse_y,
                                                     0,
792
                                                     2 * ENDPOINT_GRIP_HANDLE_SIZE);
793
      start_visible = start_diameter > 2;
794

795 796 797 798
      end_diameter = gimp_canvas_handle_calc_size (private->end_handle_circle,
                                                   private->mouse_x,
                                                   private->mouse_y,
                                                   0,
799
                                                   2 * ENDPOINT_GRIP_HANDLE_SIZE);
800
      end_visible = end_diameter > 2;
801 802 803 804 805 806
    }

  gimp_canvas_item_set_visible (private->start_handle_circle, start_visible);
  gimp_canvas_item_set_visible (private->end_handle_circle,   end_visible);

  if (start_visible)
807 808
    gimp_canvas_handle_set_size (private->start_handle_circle,
                                 start_diameter, start_diameter);
809 810

  if (end_visible)
811 812
    gimp_canvas_handle_set_size (private->end_handle_circle,
                                 end_diameter, end_diameter);
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839

  for (i = 0; i < private->sliders->len; i++)
    {
      GimpCanvasItem *circle;
      gboolean        visible;
      gint            diameter;

      circle = g_array_index (private->slider_handle_circles,
                              GimpCanvasItem *, i);

      if (private->point_grabbed)
        visible = FALSE;
      else
        {
          diameter = gimp_canvas_handle_calc_size (circle,
                                                   private->mouse_x,
                                                   private->mouse_y,
                                                   0,
                                                   2 * SLIDER_GRIP_HANDLE_SIZE);
          visible = diameter > 2;
        }

      gimp_canvas_item_set_visible (circle, visible);

      if (visible)
        gimp_canvas_handle_set_size (circle, diameter, diameter);
    }
840
}
841

842 843 844 845
static void
gimp_tool_line_update_hilight (GimpToolLine *line)
{
  GimpToolLinePrivate *private = line->private;
846
  gint                 i;
847

848
  gimp_canvas_item_set_highlight (private->start_handle_circle,
849
                                  private->point == POINT_START);
850
  gimp_canvas_item_set_highlight (private->start_handle_grip,
851
                                  private->point == POINT_START);
852 853

  gimp_canvas_item_set_highlight (private->end_handle_circle,
854
                                  private->point == POINT_END);
855
  gimp_canvas_item_set_highlight (private->end_handle_grip,
856
                                  private->point == POINT_END);
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874

  for (i = 0; i < private->sliders->len; i++)
    {
      GimpCanvasItem *circle;
      GimpCanvasItem *grip;
      gboolean        highlight;

      circle = g_array_index (private->slider_handle_circles,
                              GimpCanvasItem *, i);
      grip   = g_array_index (private->slider_handle_grips,
                              GimpCanvasItem *, i);

      highlight =
        (private->point == POINT_SLIDER && private->slider_index == i);

      gimp_canvas_item_set_highlight (circle, highlight);
      gimp_canvas_item_set_highlight (grip,   highlight);
    }
875 876
}

877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
static void
gimp_tool_line_update_status (GimpToolLine    *line,
                              GdkModifierType  state,
                              gboolean         proximity)
{
  GimpToolLinePrivate *private = line->private;

  if (proximity)
    {
      gchar *status_help =
        gimp_suggest_modifiers ("",
                                (gimp_get_constrain_behavior_mask () |
                                 GDK_MOD1_MASK) &
                                ~state,
                                NULL,
                                _("%s for constrained angles"),
                                _("%s to move the whole line"));

      gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (line),
896
                                          private->status_title,
897 898 899 900 901 902 903 904 905 906 907 908 909
                                          private->x2 - private->x1,
                                          ", ",
                                          private->y2 - private->y1,
                                          status_help);

      g_free (status_help);
    }
  else
    {
      gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (line), NULL);
    }
}

910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929

/*  public functions  */

GimpToolWidget *
gimp_tool_line_new (GimpDisplayShell *shell,
                    gdouble           x1,
                    gdouble           y1,
                    gdouble           x2,
                    gdouble           y2)
{
  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);

  return g_object_new (GIMP_TYPE_TOOL_LINE,
                       "shell", shell,
                       "x1",    x1,
                       "y1",    y1,
                       "x2",    x2,
                       "y2",    y2,
                       NULL);
}
930 931 932 933

void
gimp_tool_line_set_sliders (GimpToolLine               *line,
                            const GimpControllerSlider *sliders,
934
                            gint                        n_sliders)
935 936 937 938
{
  GimpToolLinePrivate *private;

  g_return_if_fail (GIMP_IS_TOOL_LINE (line));
939
  g_return_if_fail (n_sliders == 0 || (n_sliders > 0 && sliders != NULL));
940 941 942

  private = line->private;

943
  g_array_set_size (private->sliders, n_sliders);
944 945

  memcpy (private->sliders->data, sliders,
946
          n_sliders * sizeof (GimpControllerSlider));
947 948 949 950 951 952 953 954

  g_object_set (line,
                "sliders", private->sliders,
                NULL);
}

const GimpControllerSlider *
gimp_tool_line_get_sliders (GimpToolLine *line,
955
                            gint         *n_sliders)
956 957 958 959 960 961 962
{
  GimpToolLinePrivate *private;

  g_return_val_if_fail (GIMP_IS_TOOL_LINE (line), NULL);

  private = line->private;

963
  if (n_sliders) *n_sliders = private->sliders->len;
964 965 966

  return (const GimpControllerSlider *) private->sliders->data;
}