gimpdial.c 17.4 KB
Newer Older
1 2 3 4 5 6
/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimpdial.c
 * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
 *
7 8 9 10
 * Based on code from the color-rotate plug-in
 * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de)
 *                         Based on code from Pavel Grinfeld (pavel@ml.com)
 *
11 12 13 14 15 16 17 18 19 20 21
 * 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
22
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
23 24 25 26 27 28 29 30 31 32 33 34 35
 */

#include "config.h"

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

#include "libgimpmath/gimpmath.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "widgets-types.h"

36 37
#include "core/gimp-cairo.h"

38 39 40
#include "gimpdial.h"


41
#define SEGMENT_FRACTION 0.3
42

43

44 45 46
enum
{
  PROP_0,
47
  PROP_DRAW_BETA,
48
  PROP_ALPHA,
49
  PROP_BETA,
50 51
  PROP_CLOCKWISE_ANGLES,
  PROP_CLOCKWISE_DELTA
52 53
};

54 55
typedef enum
{
56 57 58 59
  DIAL_TARGET_NONE  = 0,
  DIAL_TARGET_ALPHA = 1 << 0,
  DIAL_TARGET_BETA  = 1 << 1,
  DIAL_TARGET_BOTH  = DIAL_TARGET_ALPHA | DIAL_TARGET_BETA
60 61
} DialTarget;

62

63 64
struct _GimpDialPrivate
{
65 66
  gdouble     alpha;
  gdouble     beta;
67 68
  gboolean    clockwise_angles;
  gboolean    clockwise_delta;
69 70 71 72
  gboolean    draw_beta;

  DialTarget  target;
  gdouble     last_angle;
73 74
};

75

76 77 78 79 80 81 82 83 84
static void        gimp_dial_set_property         (GObject            *object,
                                                   guint               property_id,
                                                   const GValue       *value,
                                                   GParamSpec         *pspec);
static void        gimp_dial_get_property         (GObject            *object,
                                                   guint               property_id,
                                                   GValue             *value,
                                                   GParamSpec         *pspec);

85 86
static gboolean    gimp_dial_draw                 (GtkWidget          *widget,
                                                   cairo_t            *cr);
87 88 89 90
static gboolean    gimp_dial_button_press_event   (GtkWidget          *widget,
                                                   GdkEventButton     *bevent);
static gboolean    gimp_dial_motion_notify_event  (GtkWidget          *widget,
                                                   GdkEventMotion     *mevent);
91 92

static void        gimp_dial_reset_target         (GimpCircle         *circle);
93

94 95 96
static void        gimp_dial_set_target           (GimpDial           *dial,
                                                   DialTarget          target);

97 98 99 100
static void        gimp_dial_draw_arrows          (cairo_t            *cr,
                                                   gint                size,
                                                   gdouble             alpha,
                                                   gdouble             beta,
101
                                                   gboolean            clockwise_delta,
102
                                                   DialTarget          highlight,
103
                                                   gboolean            draw_beta);
104

105 106 107 108
static gdouble     gimp_dial_normalize_angle      (gdouble             angle);
static gdouble     gimp_dial_get_angle_distance   (gdouble             alpha,
                                                   gdouble             beta);

109

110
G_DEFINE_TYPE_WITH_PRIVATE (GimpDial, gimp_dial, GIMP_TYPE_CIRCLE)
111 112 113 114 115 116 117

#define parent_class gimp_dial_parent_class


static void
gimp_dial_class_init (GimpDialClass *klass)
{
118 119 120
  GObjectClass    *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass  *widget_class = GTK_WIDGET_CLASS (klass);
  GimpCircleClass *circle_class = GIMP_CIRCLE_CLASS (klass);
121 122 123 124

  object_class->get_property         = gimp_dial_get_property;
  object_class->set_property         = gimp_dial_set_property;

125
  widget_class->draw                 = gimp_dial_draw;
126 127
  widget_class->button_press_event   = gimp_dial_button_press_event;
  widget_class->motion_notify_event  = gimp_dial_motion_notify_event;
128 129

  circle_class->reset_target         = gimp_dial_reset_target;
130 131 132 133 134 135 136 137 138 139 140 141 142 143

  g_object_class_install_property (object_class, PROP_ALPHA,
                                   g_param_spec_double ("alpha",
                                                        NULL, NULL,
                                                        0.0, 2 * G_PI, 0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_BETA,
                                   g_param_spec_double ("beta",
                                                        NULL, NULL,
                                                        0.0, 2 * G_PI, G_PI,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));
144

145 146 147 148 149 150 151 152 153
  g_object_class_install_property (object_class, PROP_CLOCKWISE_ANGLES,
                                   g_param_spec_boolean ("clockwise-angles",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_CLOCKWISE_DELTA,
                                   g_param_spec_boolean ("clockwise-delta",
154 155 156 157
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));
158

159 160 161 162 163 164
  g_object_class_install_property (object_class, PROP_DRAW_BETA,
                                   g_param_spec_boolean ("draw-beta",
                                                         NULL, NULL,
                                                         TRUE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));
165 166 167 168 169
}

static void
gimp_dial_init (GimpDial *dial)
{
170
  dial->priv = gimp_dial_get_instance_private (dial);
171 172 173 174 175 176 177 178 179 180 181 182 183
}

static void
gimp_dial_set_property (GObject      *object,
                        guint         property_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  GimpDial *dial = GIMP_DIAL (object);

  switch (property_id)
    {
    case PROP_ALPHA:
184
      dial->priv->alpha = g_value_get_double (value);
185 186 187 188
      gtk_widget_queue_draw (GTK_WIDGET (dial));
      break;

    case PROP_BETA:
189
      dial->priv->beta = g_value_get_double (value);
190 191 192
      gtk_widget_queue_draw (GTK_WIDGET (dial));
      break;

193 194 195 196 197 198 199
    case PROP_CLOCKWISE_ANGLES:
      dial->priv->clockwise_angles = g_value_get_boolean (value);
      gtk_widget_queue_draw (GTK_WIDGET (dial));
      break;

    case PROP_CLOCKWISE_DELTA:
      dial->priv->clockwise_delta = g_value_get_boolean (value);
200 201 202
      gtk_widget_queue_draw (GTK_WIDGET (dial));
      break;

203 204 205 206 207
    case PROP_DRAW_BETA:
      dial->priv->draw_beta = g_value_get_boolean (value);
      gtk_widget_queue_draw (GTK_WIDGET (dial));
      break;

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_dial_get_property (GObject    *object,
                        guint       property_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  GimpDial *dial = GIMP_DIAL (object);

  switch (property_id)
    {
    case PROP_ALPHA:
225
      g_value_set_double (value, dial->priv->alpha);
226 227 228
      break;

    case PROP_BETA:
229
      g_value_set_double (value, dial->priv->beta);
230 231
      break;

232 233 234 235 236 237
    case PROP_CLOCKWISE_ANGLES:
      g_value_set_boolean (value, dial->priv->clockwise_angles);
      break;

    case PROP_CLOCKWISE_DELTA:
      g_value_set_boolean (value, dial->priv->clockwise_delta);
238 239
      break;

240 241 242 243
    case PROP_DRAW_BETA:
      g_value_set_boolean (value, dial->priv->draw_beta);
      break;

244 245 246 247 248 249 250
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gboolean
251 252
gimp_dial_draw (GtkWidget *widget,
                cairo_t   *cr)
253
{
254
  GimpDial      *dial  = GIMP_DIAL (widget);
255 256
  GtkAllocation  allocation;
  gint           size;
257 258
  gdouble        alpha = dial->priv->alpha;
  gdouble        beta  = dial->priv->beta;
259

260
  GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
261

262 263 264
  g_object_get (widget,
                "size", &size,
                NULL);
265

266
  gtk_widget_get_allocation (widget, &allocation);
267

268 269 270 271 272 273
  if (dial->priv->clockwise_angles)
    {
      alpha = -alpha;
      beta  = -beta;
    }

274
  cairo_save (cr);
275

276 277 278
  cairo_translate (cr,
                   (allocation.width  - size) / 2.0,
                   (allocation.height - size) / 2.0);
279

280
  gimp_dial_draw_arrows (cr, size,
281 282
                         alpha, beta,
                         dial->priv->clockwise_delta,
283 284
                         dial->priv->target,
                         dial->priv->draw_beta);
285

286
  cairo_restore (cr);
287 288 289 290 291 292 293 294 295 296

  return FALSE;
}

static gboolean
gimp_dial_button_press_event (GtkWidget      *widget,
                              GdkEventButton *bevent)
{
  GimpDial *dial = GIMP_DIAL (widget);

297
  if (bevent->type == GDK_BUTTON_PRESS &&
298 299
      bevent->button == 1              &&
      dial->priv->target != DIAL_TARGET_NONE)
300
    {
301
      gdouble angle;
302

303
      GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent);
304

305 306 307
      angle = _gimp_circle_get_angle_and_distance (GIMP_CIRCLE (dial),
                                                   bevent->x, bevent->y,
                                                   NULL);
308 309 310 311

      if (dial->priv->clockwise_angles && angle)
        angle = 2.0 * G_PI - angle;

312
      dial->priv->last_angle = angle;
313

314
      switch (dial->priv->target)
315
        {
316 317 318 319 320 321 322 323 324 325
        case DIAL_TARGET_ALPHA:
          g_object_set (dial, "alpha", angle, NULL);
          break;

        case DIAL_TARGET_BETA:
          g_object_set (dial, "beta", angle, NULL);
          break;

        default:
          break;
326
        }
327 328 329 330 331 332 333 334 335
    }

  return FALSE;
}

static gboolean
gimp_dial_motion_notify_event (GtkWidget      *widget,
                               GdkEventMotion *mevent)
{
336 337 338
  GimpDial *dial = GIMP_DIAL (widget);
  gdouble   angle;
  gdouble   distance;
339

340 341 342
  angle = _gimp_circle_get_angle_and_distance (GIMP_CIRCLE (dial),
                                               mevent->x, mevent->y,
                                               &distance);
343

344 345 346
  if (dial->priv->clockwise_angles && angle)
    angle = 2.0 * G_PI - angle;

347
  if (_gimp_circle_has_grab (GIMP_CIRCLE (dial)))
348 349
    {
      gdouble delta;
350

351 352 353 354
      delta = angle - dial->priv->last_angle;
      dial->priv->last_angle = angle;

      if (delta != 0.0)
355
        {
356 357 358 359 360 361 362 363 364 365 366 367
          switch (dial->priv->target)
            {
            case DIAL_TARGET_ALPHA:
              g_object_set (dial, "alpha", angle, NULL);
              break;

            case DIAL_TARGET_BETA:
              g_object_set (dial, "beta", angle, NULL);
              break;

            case DIAL_TARGET_BOTH:
              g_object_set (dial,
368 369
                            "alpha", gimp_dial_normalize_angle (dial->priv->alpha + delta),
                            "beta",  gimp_dial_normalize_angle (dial->priv->beta  + delta),
370 371 372 373 374 375
                            NULL);
              break;

            default:
              break;
            }
376 377
        }
    }
378 379 380
  else
    {
      DialTarget target;
381 382 383 384 385
      gdouble    dist_alpha;
      gdouble    dist_beta;

      dist_alpha = gimp_dial_get_angle_distance (dial->priv->alpha, angle);
      dist_beta  = gimp_dial_get_angle_distance (dial->priv->beta, angle);
386 387 388

      if (dial->priv->draw_beta       &&
          distance > SEGMENT_FRACTION &&
389
          MIN (dist_alpha, dist_beta) < G_PI / 12)
390
        {
391
          if (dist_alpha < dist_beta)
392 393 394 395 396 397 398 399 400 401 402 403 404
            {
              target = DIAL_TARGET_ALPHA;
            }
          else
            {
              target = DIAL_TARGET_BETA;
            }
        }
      else
        {
          target = DIAL_TARGET_BOTH;
        }

405
      gimp_dial_set_target (dial, target);
406 407 408 409 410 411 412
    }

  gdk_event_request_motions (mevent);

  return FALSE;
}

413 414
static void
gimp_dial_reset_target (GimpCircle *circle)
415
{
416
  gimp_dial_set_target (GIMP_DIAL (circle), DIAL_TARGET_NONE);
417 418 419 420 421 422 423 424 425 426 427 428 429 430
}


/*  public functions  */

GtkWidget *
gimp_dial_new (void)
{
  return g_object_new (GIMP_TYPE_DIAL, NULL);
}


/*  private functions  */

431 432 433 434 435 436 437 438 439 440 441
static void
gimp_dial_set_target (GimpDial   *dial,
                      DialTarget  target)
{
  if (target != dial->priv->target)
    {
      dial->priv->target = target;
      gtk_widget_queue_draw (GTK_WIDGET (dial));
    }
}

442
static void
443
gimp_dial_draw_arrow (cairo_t *cr,
444
                      gdouble  radius,
445
                      gdouble  angle)
446
{
447 448
#define REL 0.8
#define DEL 0.1
449

450 451
  cairo_move_to (cr, radius, radius);
  cairo_line_to (cr,
452 453
                 radius + radius * cos (angle),
                 radius - radius * sin (angle));
454 455 456 457 458

  cairo_move_to (cr,
                 radius + radius * cos (angle),
                 radius - radius * sin (angle));
  cairo_line_to (cr,
459 460
                 radius + radius * REL * cos (angle - DEL),
                 radius - radius * REL * sin (angle - DEL));
461 462

  cairo_move_to (cr,
463 464
                 radius + radius * cos (angle),
                 radius - radius * sin (angle));
465
  cairo_line_to (cr,
466 467
                 radius + radius * REL * cos (angle + DEL),
                 radius - radius * REL * sin (angle + DEL));
468 469 470 471
}

static void
gimp_dial_draw_segment (cairo_t  *cr,
472
                        gdouble   radius,
473
                        gdouble   alpha,
474
                        gdouble   beta,
475
                        gboolean  clockwise_delta)
476
{
477
  gint    direction = clockwise_delta ? -1 : 1;
478 479 480 481 482 483
  gint    segment_dist;
  gint    tick;
  gdouble slice;

  segment_dist = radius * SEGMENT_FRACTION;
  tick         = MIN (10, segment_dist);
484 485

  cairo_move_to (cr,
486 487
                 radius + segment_dist * cos (beta),
                 radius - segment_dist * sin (beta));
488
  cairo_line_to (cr,
489 490 491 492
                 radius + segment_dist * cos (beta) +
                 direction * tick * sin (beta),
                 radius - segment_dist * sin (beta) +
                 direction * tick * cos (beta));
493 494 495

  cairo_new_sub_path (cr);

496
  if (clockwise_delta)
497
    slice = -gimp_dial_normalize_angle (alpha - beta);
498
  else
499
    slice = gimp_dial_normalize_angle (beta - alpha);
500

501 502
  gimp_cairo_arc (cr, radius, radius, segment_dist,
                  alpha, slice);
503 504 505 506 507 508 509
}

static void
gimp_dial_draw_arrows (cairo_t    *cr,
                       gint        size,
                       gdouble     alpha,
                       gdouble     beta,
510
                       gboolean    clockwise_delta,
511 512 513
                       DialTarget  highlight,
                       gboolean    draw_beta)
{
514
  gdouble radius = size / 2.0 - 2.0; /* half the broad line with and half a px */
515 516 517

  cairo_save (cr);

518
  cairo_translate (cr, 2.0, 2.0); /* half the broad line width and half a px*/
519 520 521 522 523

  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
  cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);

  if (highlight != DIAL_TARGET_BOTH)
524
    {
525 526 527 528 529 530 531 532 533
      if (! (highlight & DIAL_TARGET_ALPHA))
        gimp_dial_draw_arrow (cr, radius, alpha);

      if (draw_beta)
        {
          if (! (highlight & DIAL_TARGET_BETA))
            gimp_dial_draw_arrow (cr, radius, beta);

          if ((highlight & DIAL_TARGET_BOTH) != DIAL_TARGET_BOTH)
534
            gimp_dial_draw_segment (cr, radius, alpha, beta, clockwise_delta);
535 536 537 538 539
        }

      cairo_set_line_width (cr, 3.0);
      cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6);
      cairo_stroke_preserve (cr);
540

541 542 543
      cairo_set_line_width (cr, 1.0);
      cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8);
      cairo_stroke (cr);
544
    }
545

546 547 548 549 550 551 552 553 554
  if (highlight != DIAL_TARGET_NONE)
    {
      if (highlight & DIAL_TARGET_ALPHA)
        gimp_dial_draw_arrow (cr, radius, alpha);

      if (draw_beta)
        {
          if (highlight & DIAL_TARGET_BETA)
            gimp_dial_draw_arrow (cr, radius, beta);
555

556
          if ((highlight & DIAL_TARGET_BOTH) == DIAL_TARGET_BOTH)
557
            gimp_dial_draw_segment (cr, radius, alpha, beta, clockwise_delta);
558 559 560 561 562 563 564 565 566 567
        }

      cairo_set_line_width (cr, 3.0);
      cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.6);
      cairo_stroke_preserve (cr);

      cairo_set_line_width (cr, 1.0);
      cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.8);
      cairo_stroke (cr);
    }
568 569

  cairo_restore (cr);
570
}
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589

static gdouble
gimp_dial_normalize_angle (gdouble angle)
{
  if (angle < 0)
    return angle + 2 * G_PI;
  else if (angle > 2 * G_PI)
    return angle - 2 * G_PI;
  else
    return angle;
}

static gdouble
gimp_dial_get_angle_distance (gdouble alpha,
                              gdouble beta)
{
  return ABS (MIN (gimp_dial_normalize_angle (alpha - beta),
                   2 * G_PI - gimp_dial_normalize_angle (alpha - beta)));
}