gimpchainbutton.c 13.9 KB
Newer Older
1 2
/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3
 *
4
 * gimpchainbutton.c
5
 * Copyright (C) 1999-2000 Sven Neumann <sven@gimp.org>
6
 *
7
 * This library is free software: you can redistribute it and/or
Marc Lehmann's avatar
Marc Lehmann committed
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 3 of the License, or (at your option) any later version.
11 12 13 14
 *
 * This library 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
15 16
 * Library General Public License for more details.
 *
Marc Lehmann's avatar
Marc Lehmann committed
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
19
 * <https://www.gnu.org/licenses/>.
20 21
 */

22 23
#include "config.h"

24 25
#include <gtk/gtk.h>

26 27
#include "gimpwidgetstypes.h"

28
#include "gimpchainbutton.h"
29
#include "gimpicons.h"
30

31

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
/**
 * SECTION: gimpchainbutton
 * @title: GimpChainButton
 * @short_description: Widget to visually connect two entry widgets.
 * @see_also: You may want to use the convenience function
 *            gimp_coordinates_new() to set up two GimpSizeEntries
 *            (see #GimpSizeEntry) linked with a #GimpChainButton.
 *
 * This widget provides a button showing either a linked or a broken
 * chain that can be used to link two entries, spinbuttons, colors or
 * other GUI elements and show that they may be locked. Use it for
 * example to connect X and Y ratios to provide the possibility of a
 * constrained aspect ratio.
 *
 * The #GimpChainButton only gives visual feedback, it does not really
 * connect widgets. You have to take care of locking the values
 * yourself by checking the state of the #GimpChainButton whenever a
 * value changes in one of the connected widgets and adjusting the
 * other value if necessary.
 **/


54 55 56 57 58 59
enum
{
  PROP_0,
  PROP_POSITION
};

60 61 62 63 64 65
enum
{
  TOGGLED,
  LAST_SIGNAL
};

66
static void      gimp_chain_button_constructed      (GObject         *object);
67 68 69 70 71 72 73 74
static void      gimp_chain_button_set_property     (GObject         *object,
                                                     guint            property_id,
                                                     const GValue    *value,
                                                     GParamSpec      *pspec);
static void      gimp_chain_button_get_property     (GObject         *object,
                                                     guint            property_id,
                                                     GValue          *value,
                                                     GParamSpec      *pspec);
75

76
static void      gimp_chain_button_clicked_callback (GtkWidget       *widget,
Sven Neumann's avatar
Sven Neumann committed
77
                                                     GimpChainButton *button);
78
static void      gimp_chain_button_update_image     (GimpChainButton *button);
79

80 81 82
static GtkWidget * gimp_chain_line_new            (GimpChainPosition  position,
                                                   gint               which);

83

84
G_DEFINE_TYPE (GimpChainButton, gimp_chain_button, GTK_TYPE_TABLE)
85 86 87 88

#define parent_class gimp_chain_button_parent_class

static guint gimp_chain_button_signals[LAST_SIGNAL] = { 0 };
89

90
static const gchar * const gimp_chain_icon_names[] =
91
{
92 93 94 95
  GIMP_ICON_CHAIN_HORIZONTAL,
  GIMP_ICON_CHAIN_HORIZONTAL_BROKEN,
  GIMP_ICON_CHAIN_VERTICAL,
  GIMP_ICON_CHAIN_VERTICAL_BROKEN
96 97
};

Sven Neumann's avatar
Sven Neumann committed
98

99
static void
100
gimp_chain_button_class_init (GimpChainButtonClass *klass)
101
{
102 103
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

104
  object_class->constructed  = gimp_chain_button_constructed;
105 106 107
  object_class->set_property = gimp_chain_button_set_property;
  object_class->get_property = gimp_chain_button_get_property;

108
  gimp_chain_button_signals[TOGGLED] =
109
    g_signal_new ("toggled",
Sven Neumann's avatar
Sven Neumann committed
110 111 112 113 114 115
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpChainButtonClass, toggled),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
Sven Neumann's avatar
Sven Neumann committed
116

117
  klass->toggled = NULL;
118 119 120 121 122 123

  /**
   * GimpChainButton:position:
   *
   * The position in which the chain button will be used.
   *
124
   * Since: 2.4
125 126
   */
  g_object_class_install_property (object_class, PROP_POSITION,
127 128 129
                                   g_param_spec_enum ("position",
                                                      "Position",
                                                      "The chain's position",
130 131 132 133
                                                      GIMP_TYPE_CHAIN_POSITION,
                                                      GIMP_CHAIN_TOP,
                                                      G_PARAM_CONSTRUCT_ONLY |
                                                      GIMP_PARAM_READWRITE));
134 135 136
}

static void
137
gimp_chain_button_init (GimpChainButton *button)
138
{
139 140 141 142
  button->position = GIMP_CHAIN_TOP;
  button->active   = FALSE;
  button->image    = gtk_image_new ();
  button->button   = gtk_button_new ();
143

144 145
  gtk_button_set_relief (GTK_BUTTON (button->button), GTK_RELIEF_NONE);
  gtk_container_add (GTK_CONTAINER (button->button), button->image);
146
  gtk_widget_show (button->image);
147

148
  g_signal_connect (button->button, "clicked",
149
                    G_CALLBACK (gimp_chain_button_clicked_callback),
150
                    button);
151 152
}

153 154
static void
gimp_chain_button_constructed (GObject *object)
155
{
156
  GimpChainButton *button = GIMP_CHAIN_BUTTON (object);
157

158
  G_OBJECT_CLASS (parent_class)->constructed (object);
159

160 161 162
  button->line1 = gimp_chain_line_new (button->position, 1);
  button->line2 = gimp_chain_line_new (button->position, -1);

163
  gimp_chain_button_update_image (button);
164

165
  if (button->position & GIMP_CHAIN_LEFT) /* are we a vertical chainbutton? */
166
    {
167 168
      gtk_table_resize (GTK_TABLE (button), 3, 1);
      gtk_table_attach (GTK_TABLE (button), button->button, 0, 1, 1, 2,
Sven Neumann's avatar
Sven Neumann committed
169
                        GTK_SHRINK, GTK_SHRINK, 0, 0);
170
      gtk_table_attach_defaults (GTK_TABLE (button),
Sven Neumann's avatar
Sven Neumann committed
171
                                 button->line1, 0, 1, 0, 1);
172
      gtk_table_attach_defaults (GTK_TABLE (button),
Sven Neumann's avatar
Sven Neumann committed
173
                                 button->line2, 0, 1, 2, 3);
174 175 176
    }
  else
    {
177 178
      gtk_table_resize (GTK_TABLE (button), 1, 3);
      gtk_table_attach (GTK_TABLE (button), button->button, 1, 2, 0, 1,
Sven Neumann's avatar
Sven Neumann committed
179
                        GTK_SHRINK, GTK_SHRINK, 0, 0);
180
      gtk_table_attach_defaults (GTK_TABLE (button),
Sven Neumann's avatar
Sven Neumann committed
181
                                 button->line1, 0, 1, 0, 1);
182
      gtk_table_attach_defaults (GTK_TABLE (button),
Sven Neumann's avatar
Sven Neumann committed
183
                                 button->line2, 2, 3, 0, 1);
184
    }
185

186 187 188
  gtk_widget_show (button->button);
  gtk_widget_show (button->line1);
  gtk_widget_show (button->line2);
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
}

static void
gimp_chain_button_set_property (GObject      *object,
                                guint         property_id,
                                const GValue *value,
                                GParamSpec   *pspec)
{
  GimpChainButton *button = GIMP_CHAIN_BUTTON (object);

  switch (property_id)
    {
    case PROP_POSITION:
      button->position = g_value_get_enum (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_chain_button_get_property (GObject    *object,
                                guint       property_id,
                                GValue     *value,
                                GParamSpec *pspec)
{
  GimpChainButton *button = GIMP_CHAIN_BUTTON (object);

  switch (property_id)
    {
    case PROP_POSITION:
      g_value_set_enum (value, button->position);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

/**
 * gimp_chain_button_new:
 * @position: The position you are going to use for the button
 *            with respect to the widgets you want to chain.
 *
 * Creates a new #GimpChainButton widget.
 *
 * This returns a button showing either a broken or a linked chain and
 * small clamps attached to both sides that visually group the two widgets
 * you want to connect. This widget looks best when attached
 * to a table taking up two columns (or rows respectively) next
 * to the widgets that it is supposed to connect. It may work
 * for more than two widgets, but the look is optimized for two.
 *
 * Returns: Pointer to the new #GimpChainButton, which is inactive
 *          by default. Use gimp_chain_button_set_active() to
 *          change its state.
 */
GtkWidget *
gimp_chain_button_new (GimpChainPosition position)
{
  return g_object_new (GIMP_TYPE_CHAIN_BUTTON,
                       "position", position,
                       NULL);
255 256
}

257
/**
258
 * gimp_chain_button_set_active:
259
 * @button: Pointer to a #GimpChainButton.
260
 * @active: The new state.
261 262
 *
 * Sets the state of the #GimpChainButton to be either locked (%TRUE) or
263
 * unlocked (%FALSE) and changes the showed pixmap to reflect the new state.
264
 */
265
void
266
gimp_chain_button_set_active (GimpChainButton  *button,
Sven Neumann's avatar
Sven Neumann committed
267
                              gboolean          active)
268
{
269
  g_return_if_fail (GIMP_IS_CHAIN_BUTTON (button));
270

271
  if (button->active != active)
272
    {
273
      button->active = active ? TRUE : FALSE;
274

275
      gimp_chain_button_update_image (button);
276 277 278 279 280
    }
}

/**
 * gimp_chain_button_get_active
281
 * @button: Pointer to a #GimpChainButton.
282 283
 *
 * Checks the state of the #GimpChainButton.
284
 *
285
 * Returns: %TRUE if the #GimpChainButton is active (locked).
286
 */
287
gboolean
288
gimp_chain_button_get_active (GimpChainButton *button)
289
{
290
  g_return_val_if_fail (GIMP_IS_CHAIN_BUTTON (button), FALSE);
291

292
  return button->active;
293 294
}

295
static void
296
gimp_chain_button_clicked_callback (GtkWidget       *widget,
Sven Neumann's avatar
Sven Neumann committed
297
                                    GimpChainButton *button)
298
{
299
  g_return_if_fail (GIMP_IS_CHAIN_BUTTON (button));
300

301
  gimp_chain_button_set_active (button, ! button->active);
Sven Neumann's avatar
Sven Neumann committed
302

303
  g_signal_emit (button, gimp_chain_button_signals[TOGGLED], 0);
304 305
}

306 307 308 309 310 311 312
static void
gimp_chain_button_update_image (GimpChainButton *button)
{
  guint i;

  i = ((button->position & GIMP_CHAIN_LEFT) << 1) + (button->active ? 0 : 1);

313 314 315
  gtk_image_set_from_icon_name (GTK_IMAGE (button->image),
                                gimp_chain_icon_names[i],
                                GTK_ICON_SIZE_BUTTON);
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
}


/* GimpChainLine is a simple no-window widget for drawing the lines.
 *
 * Originally this used to be a GtkDrawingArea but this turned out to
 * be a bad idea. We don't need an extra window to draw on and we also
 * don't need any input events.
 */

static GType     gimp_chain_line_get_type     (void) G_GNUC_CONST;
static gboolean  gimp_chain_line_expose_event (GtkWidget       *widget,
                                               GdkEventExpose  *event);

struct _GimpChainLine
{
  GtkWidget          parent_instance;
  GimpChainPosition  position;
  gint               which;
};

typedef struct _GimpChainLine  GimpChainLine;
typedef GtkWidgetClass         GimpChainLineClass;

G_DEFINE_TYPE (GimpChainLine, gimp_chain_line, GTK_TYPE_WIDGET)

static void
gimp_chain_line_class_init (GimpChainLineClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  widget_class->expose_event = gimp_chain_line_expose_event;
}

static void
gimp_chain_line_init (GimpChainLine *line)
{
353
  gtk_widget_set_has_window (GTK_WIDGET (line), FALSE);
354 355 356 357 358 359 360 361 362 363 364 365 366 367
}

static GtkWidget *
gimp_chain_line_new (GimpChainPosition  position,
                     gint               which)
{
  GimpChainLine *line = g_object_new (gimp_chain_line_get_type (), NULL);

  line->position = position;
  line->which    = which;

  return GTK_WIDGET (line);
}

368
static gboolean
369 370
gimp_chain_line_expose_event (GtkWidget      *widget,
                              GdkEventExpose *event)
371
{
372 373
  GtkStyle          *style = gtk_widget_get_style (widget);
  GimpChainLine     *line  = ((GimpChainLine *) widget);
374
  GtkAllocation      allocation;
375 376
  GdkPoint           points[3];
  GimpChainPosition  position;
377 378
  cairo_t           *cr;

379 380
  gtk_widget_get_allocation (widget, &allocation);

381 382
  cr = gdk_cairo_create (gtk_widget_get_window (widget));
  gdk_cairo_region (cr, event->region);
383
  cairo_translate (cr, allocation.x, allocation.y);
384
  cairo_clip (cr);
385 386

#define SHORT_LINE 4
387 388
  points[0].x = allocation.width  / 2;
  points[0].y = allocation.height / 2;
389

390
  position = line->position;
391 392

  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
    {
      switch (position)
        {
        case GIMP_CHAIN_TOP:
        case GIMP_CHAIN_BOTTOM:
          break;

        case GIMP_CHAIN_LEFT:
          position = GIMP_CHAIN_RIGHT;
          break;

        case GIMP_CHAIN_RIGHT:
          position = GIMP_CHAIN_LEFT;
          break;
        }
    }
409 410

  switch (position)
411 412 413 414 415 416
    {
    case GIMP_CHAIN_LEFT:
      points[0].x += SHORT_LINE;
      points[1].x = points[0].x - SHORT_LINE;
      points[1].y = points[0].y;
      points[2].x = points[1].x;
417
      points[2].y = (line->which == 1 ? allocation.height - 1 : 0);
418
      break;
419

420 421 422 423 424
    case GIMP_CHAIN_RIGHT:
      points[0].x -= SHORT_LINE;
      points[1].x = points[0].x + SHORT_LINE;
      points[1].y = points[0].y;
      points[2].x = points[1].x;
425
      points[2].y = (line->which == 1 ? allocation.height - 1 : 0);
426
      break;
427

428 429 430 431
    case GIMP_CHAIN_TOP:
      points[0].y += SHORT_LINE;
      points[1].x = points[0].x;
      points[1].y = points[0].y - SHORT_LINE;
432
      points[2].x = (line->which == 1 ? allocation.width - 1 : 0);
433 434
      points[2].y = points[1].y;
      break;
435

436 437 438 439
    case GIMP_CHAIN_BOTTOM:
      points[0].y -= SHORT_LINE;
      points[1].x = points[0].x;
      points[1].y = points[0].y + SHORT_LINE;
440
      points[2].x = (line->which == 1 ? allocation.width - 1 : 0);
441 442
      points[2].y = points[1].y;
      break;
443

444
    default:
445
      return FALSE;
446 447
    }

448 449 450 451 452 453 454 455 456
  cairo_move_to (cr, points[0].x, points[0].y);
  cairo_line_to (cr, points[1].x, points[1].y);
  cairo_line_to (cr, points[2].x, points[2].y);

  cairo_set_line_width (cr, 2.0);
  cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
  gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);

  cairo_stroke (cr);
457

458
  cairo_destroy (cr);
459 460

  return TRUE;
461
}