gimpwidgets.c 32.4 KB
Newer Older
1
/* LIBGIMP - The GIMP Library
2
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3 4 5 6
 *
 * gimpwidgets.c
 * Copyright (C) 2000 Michael Natterer <mitch@gimp.org>
 *
7
 * This library is free software: you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * 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 17
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
18 19
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
20 21
 */

22 23
#include "config.h"

24
#include <gegl.h>
25 26
#include <gtk/gtk.h>

27
#include "libgimpcolor/gimpcolor.h"
28
#include "libgimpmath/gimpmath.h"
29
#include "libgimpbase/gimpbase.h"
30

31
#include "gimpwidgets.h"
32

33
#include "libgimp/libgimp-intl.h"
34

35

36 37 38 39
/*  hack: declare prototype here instead of #undef GIMP_DISABLE_DEPRECATED  */
void   gimp_toggle_button_sensitive_update (GtkToggleButton *toggle_button);


40 41 42 43 44 45 46 47 48 49 50
/**
 * SECTION: gimpwidgets
 * @title: GimpWidgets
 * @short_description: A collection of convenient widget constructors,
 *                     standard callbacks and helper functions.
 *
 * A collection of convenient widget constructors, standard callbacks
 * and helper functions.
 **/


51 52
/**
 * gimp_radio_group_new:
53 54 55
 * @in_frame:    %TRUE if you want a #GtkFrame around the radio button group.
 * @frame_title: The title of the Frame or %NULL if you don't want a title.
 * @...:         A %NULL-terminated @va_list describing the radio buttons.
56
 *
57
 * Convenience function to create a group of radio buttons embedded into
58
 * a #GtkFrame or #GtkVBox.
59
 *
60
 * Returns: A #GtkFrame or #GtkVBox (depending on @in_frame).
61
 **/
62 63
GtkWidget *
gimp_radio_group_new (gboolean            in_frame,
Sven Neumann's avatar
Sven Neumann committed
64 65 66 67 68 69 70 71 72 73 74 75
                      const gchar        *frame_title,

                      /* specify radio buttons as va_list:
                       *  const gchar    *label,
                       *  GCallback       callback,
                       *  gpointer        callback_data,
                       *  gpointer        item_data,
                       *  GtkWidget     **widget_ptr,
                       *  gboolean        active,
                       */

                      ...)
76 77 78 79 80 81
{
  GtkWidget *vbox;
  GtkWidget *button;
  GSList    *group;

  /*  radio button variables  */
82 83 84
  const gchar  *label;
  GCallback     callback;
  gpointer      callback_data;
85
  gpointer      item_data;
86 87
  GtkWidget   **widget_ptr;
  gboolean      active;
88 89 90

  va_list args;

91
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
92 93 94 95 96

  group = NULL;

  /*  create the radio buttons  */
  va_start (args, frame_title);
97
  label = va_arg (args, const gchar *);
98 99
  while (label)
    {
100 101
      callback      = va_arg (args, GCallback);
      callback_data = va_arg (args, gpointer);
102
      item_data     = va_arg (args, gpointer);
103 104
      widget_ptr    = va_arg (args, GtkWidget **);
      active        = va_arg (args, gboolean);
105

106
      if (label != (gpointer) 1)
Sven Neumann's avatar
Sven Neumann committed
107
        button = gtk_radio_button_new_with_mnemonic (group, label);
108
      else
Sven Neumann's avatar
Sven Neumann committed
109
        button = gtk_radio_button_new (group);
110

111
      group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
112 113
      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);

114 115 116 117 118 119 120
      if (item_data)
        {
          g_object_set_data (G_OBJECT (button), "gimp-item-data", item_data);

          /*  backward compatibility  */
          g_object_set_data (G_OBJECT (button), "user_data", item_data);
        }
121 122

      if (widget_ptr)
Sven Neumann's avatar
Sven Neumann committed
123
        *widget_ptr = button;
124 125

      if (active)
Sven Neumann's avatar
Sven Neumann committed
126
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
127

128
      g_signal_connect (button, "toggled",
Sven Neumann's avatar
Sven Neumann committed
129 130
                        callback,
                        callback_data);
131

132 133
      gtk_widget_show (button);

134
      label = va_arg (args, const gchar *);
135 136 137 138
    }
  va_end (args);

  if (in_frame)
139 140 141
    {
      GtkWidget *frame;

142
      frame = gimp_frame_new (frame_title);
143 144 145 146 147
      gtk_container_add (GTK_CONTAINER (frame), vbox);
      gtk_widget_show (vbox);

      return frame;
    }
148 149 150 151

  return vbox;
}

152 153
/**
 * gimp_radio_group_new2:
154
 * @in_frame:              %TRUE if you want a #GtkFrame around the
155
 *                         radio button group.
156
 * @frame_title:           The title of the Frame or %NULL if you don't want
157
 *                         a title.
158 159
 * @radio_button_callback: The callback each button's "toggled" signal will
 *                         be connected with.
160 161
 * @radio_button_callback_data:
 *                         The data which will be passed to g_signal_connect().
162
 * @initial:               The @item_data of the initially pressed radio button.
163
 * @...:                   A %NULL-terminated @va_list describing
164
 *                         the radio buttons.
165
 *
166
 * Convenience function to create a group of radio buttons embedded into
167
 * a #GtkFrame or #GtkVBox.
168
 *
169
 * Returns: A #GtkFrame or #GtkVBox (depending on @in_frame).
170
 **/
171
GtkWidget *
172
gimp_radio_group_new2 (gboolean         in_frame,
Sven Neumann's avatar
Sven Neumann committed
173 174 175 176 177 178 179 180 181 182 183 184
                       const gchar     *frame_title,
                       GCallback        radio_button_callback,
                       gpointer         callback_data,
                       gpointer         initial, /* item_data */

                       /* specify radio buttons as va_list:
                        *  const gchar *label,
                        *  gpointer     item_data,
                        *  GtkWidget  **widget_ptr,
                        */

                       ...)
185 186 187 188 189 190
{
  GtkWidget *vbox;
  GtkWidget *button;
  GSList    *group;

  /*  radio button variables  */
191
  const gchar *label;
192
  gpointer     item_data;
193
  GtkWidget  **widget_ptr;
194 195 196

  va_list args;

197
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
198 199 200 201 202

  group = NULL;

  /*  create the radio buttons  */
  va_start (args, initial);
203 204
  label = va_arg (args, const gchar *);

205 206
  while (label)
    {
207
      item_data  = va_arg (args, gpointer);
208
      widget_ptr = va_arg (args, GtkWidget **);
209

210
      if (label != (gpointer) 1)
Sven Neumann's avatar
Sven Neumann committed
211
        button = gtk_radio_button_new_with_mnemonic (group, label);
212
      else
Sven Neumann's avatar
Sven Neumann committed
213
        button = gtk_radio_button_new (group);
214

215
      group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
216 217
      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);

218 219 220 221 222 223 224
      if (item_data)
        {
          g_object_set_data (G_OBJECT (button), "gimp-item-data", item_data);

          /*  backward compatibility  */
          g_object_set_data (G_OBJECT (button), "user_data", item_data);
        }
225 226

      if (widget_ptr)
Sven Neumann's avatar
Sven Neumann committed
227
        *widget_ptr = button;
228

229
      if (initial == item_data)
Sven Neumann's avatar
Sven Neumann committed
230
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
231

232
      g_signal_connect (button, "toggled",
Sven Neumann's avatar
Sven Neumann committed
233 234
                        radio_button_callback,
                        callback_data);
235

236 237
      gtk_widget_show (button);

238
      label = va_arg (args, const gchar *);
239 240 241 242
    }
  va_end (args);

  if (in_frame)
243 244 245
    {
      GtkWidget *frame;

246
      frame = gimp_frame_new (frame_title);
247 248 249 250 251
      gtk_container_add (GTK_CONTAINER (frame), vbox);
      gtk_widget_show (vbox);

      return frame;
    }
252 253 254 255

  return vbox;
}

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
/**
 * gimp_int_radio_group_new:
 * @in_frame:              %TRUE if you want a #GtkFrame around the
 *                         radio button group.
 * @frame_title:           The title of the Frame or %NULL if you don't want
 *                         a title.
 * @radio_button_callback: The callback each button's "toggled" signal will
 *                         be connected with.
 * @radio_button_callback_data:
 *                         The data which will be passed to g_signal_connect().
 * @initial:               The @item_data of the initially pressed radio button.
 * @...:                   A %NULL-terminated @va_list describing
 *                         the radio buttons.
 *
 * Convenience function to create a group of radio buttons embedded into
271
 * a #GtkFrame or #GtkVBox. This function does the same thing as
272 273 274 275
 * gimp_radio_group_new2(), but it takes integers as @item_data instead of
 * pointers, since that is a very common case (mapping an enum to a radio
 * group).
 *
276
 * Returns: A #GtkFrame or #GtkVBox (depending on @in_frame).
277 278 279
 **/
GtkWidget *
gimp_int_radio_group_new (gboolean         in_frame,
Sven Neumann's avatar
Sven Neumann committed
280 281 282 283 284 285 286 287 288 289 290 291
                          const gchar     *frame_title,
                          GCallback        radio_button_callback,
                          gpointer         callback_data,
                          gint             initial, /* item_data */

                          /* specify radio buttons as va_list:
                           *  const gchar *label,
                           *  gint         item_data,
                           *  GtkWidget  **widget_ptr,
                           */

                          ...)
292 293 294 295 296 297 298 299 300 301 302 303 304
{
  GtkWidget *vbox;
  GtkWidget *button;
  GSList    *group;

  /*  radio button variables  */
  const gchar *label;
  gint         item_data;
  gpointer     item_ptr;
  GtkWidget  **widget_ptr;

  va_list args;

305
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

  group = NULL;

  /*  create the radio buttons  */
  va_start (args, initial);
  label = va_arg (args, const gchar *);

  while (label)
    {
      item_data  = va_arg (args, gint);
      widget_ptr = va_arg (args, GtkWidget **);

      item_ptr = GINT_TO_POINTER (item_data);

      if (label != GINT_TO_POINTER (1))
Sven Neumann's avatar
Sven Neumann committed
321
        button = gtk_radio_button_new_with_mnemonic (group, label);
322
      else
Sven Neumann's avatar
Sven Neumann committed
323
        button = gtk_radio_button_new (group);
324 325 326 327 328 329 330 331 332 333 334 335 336

      group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);

      if (item_data)
        {
          g_object_set_data (G_OBJECT (button), "gimp-item-data", item_ptr);

          /*  backward compatibility  */
          g_object_set_data (G_OBJECT (button), "user_data", item_ptr);
        }

      if (widget_ptr)
Sven Neumann's avatar
Sven Neumann committed
337
        *widget_ptr = button;
338 339

      if (initial == item_data)
Sven Neumann's avatar
Sven Neumann committed
340
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
341 342

      g_signal_connect (button, "toggled",
Sven Neumann's avatar
Sven Neumann committed
343 344
                        radio_button_callback,
                        callback_data);
345 346 347 348 349 350 351 352 353 354 355

      gtk_widget_show (button);

      label = va_arg (args, const gchar *);
    }
  va_end (args);

  if (in_frame)
    {
      GtkWidget *frame;

356
      frame = gimp_frame_new (frame_title);
357 358 359 360 361 362 363 364 365
      gtk_container_add (GTK_CONTAINER (frame), vbox);
      gtk_widget_show (vbox);

      return frame;
    }

  return vbox;
}

366 367 368 369 370
/**
 * gimp_radio_group_set_active:
 * @radio_button: Pointer to a #GtkRadioButton.
 * @item_data: The @item_data of the radio button you want to select.
 *
371 372
 * Calls gtk_toggle_button_set_active() with the radio button that was
 * created with a matching @item_data.
373
 **/
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
void
gimp_radio_group_set_active (GtkRadioButton *radio_button,
                             gpointer        item_data)
{
  GtkWidget *button;
  GSList    *group;

  g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button));

  for (group = gtk_radio_button_get_group (radio_button);
       group;
       group = g_slist_next (group))
    {
      button = GTK_WIDGET (group->data);

      if (g_object_get_data (G_OBJECT (button), "gimp-item-data") == item_data)
        {
          gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
          return;
        }
    }
}

397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
/**
 * gimp_int_radio_group_set_active:
 * @radio_button: Pointer to a #GtkRadioButton.
 * @item_data: The @item_data of the radio button you want to select.
 *
 * Calls gtk_toggle_button_set_active() with the radio button that was created
 * with a matching @item_data. This function does the same thing as
 * gimp_radio_group_set_active(), but takes integers as @item_data instead
 * of pointers.
 **/
void
gimp_int_radio_group_set_active (GtkRadioButton *radio_button,
                                 gint            item_data)
{
  g_return_if_fail (GTK_IS_RADIO_BUTTON (radio_button));

  gimp_radio_group_set_active (radio_button, GINT_TO_POINTER (item_data));
}

416 417
/**
 * gimp_spin_button_new:
418 419 420
 * @adjustment:     Returns the spinbutton's #GtkAdjustment.
 * @value:          The initial value of the spinbutton.
 * @lower:          The lower boundary.
421
 * @upper:          The upper boundary.
422 423
 * @step_increment: The spinbutton's step increment.
 * @page_increment: The spinbutton's page increment (mouse button 2).
424
 * @page_size:      Ignored, spin buttons must always have a zero page size.
425 426
 * @climb_rate:     The spinbutton's climb rate.
 * @digits:         The spinbutton's number of decimal digits.
427
 *
428 429 430 431
 * This function is a shortcut for gtk_adjustment_new() and a
 * subsequent gtk_spin_button_new(). It also calls
 * gtk_spin_button_set_numeric() so that non-numeric text cannot be
 * entered.
432
 *
433 434
 * Deprecated: 2.10: Use gtk_spin_button_new() instead.
 *
435
 * Returns: A #GtkSpinButton and its #GtkAdjustment.
436
 **/
437 438
GtkWidget *
gimp_spin_button_new (GtkObject **adjustment,  /* return value */
Sven Neumann's avatar
Sven Neumann committed
439 440 441 442 443 444 445 446
                      gdouble     value,
                      gdouble     lower,
                      gdouble     upper,
                      gdouble     step_increment,
                      gdouble     page_increment,
                      gdouble     page_size,
                      gdouble     climb_rate,
                      guint       digits)
447 448 449 450
{
  GtkWidget *spinbutton;

  *adjustment = gtk_adjustment_new (value, lower, upper,
451
                                    step_increment, page_increment, 0);
452 453

  spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (*adjustment),
Sven Neumann's avatar
Sven Neumann committed
454
                                    climb_rate, digits);
455

456 457 458 459 460
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);

  return spinbutton;
}

461
static void
462
gimp_random_seed_update (GtkWidget *widget,
Sven Neumann's avatar
Sven Neumann committed
463
                         gpointer   data)
464
{
465
  GtkWidget *spinbutton = data;
466

467 468 469 470 471 472 473 474 475
  /* Generate a new seed if the "New Seed" button was clicked or
   * of the "Randomize" toggle is activated
   */
  if (! GTK_IS_TOGGLE_BUTTON (widget) ||
      gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    {
      gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinbutton),
                                 (guint) g_random_int ());
    }
476 477
}

478 479
/**
 * gimp_random_seed_new:
480
 * @seed:        A pointer to the variable which stores the random seed.
481
 * @random_seed: A pointer to a boolean indicating whether seed should be
482
 *               initialised randomly or not.
483
 *
484 485
 * Creates a widget that allows the user to control how the random number
 * generator is initialized.
486
 *
487 488
 * Returns: A #GtkHBox containing a #GtkSpinButton for the seed and
 *          a #GtkButton for setting a random seed.
489
 **/
490
GtkWidget *
491 492
gimp_random_seed_new (guint    *seed,
                      gboolean *random_seed)
493
{
494 495 496 497 498
  GtkWidget     *hbox;
  GtkWidget     *toggle;
  GtkWidget     *spinbutton;
  GtkAdjustment *adj;
  GtkWidget     *button;
499

500 501 502
  g_return_val_if_fail (seed != NULL, NULL);
  g_return_val_if_fail (random_seed != NULL, NULL);

503
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
504

505
  /* If we're being asked to generate a random seed, generate one. */
506
  if (*random_seed)
507
    *seed = g_random_int ();
508

509 510 511 512
  adj = (GtkAdjustment *)
    gtk_adjustment_new (*seed, 0, (guint32) -1, 1, 10, 0);
  spinbutton = gtk_spin_button_new (adj, 1.0, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
513
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
514 515
  gtk_widget_show (spinbutton);

516
  g_signal_connect (adj, "value-changed",
517
                    G_CALLBACK (gimp_uint_adjustment_update),
518
                    seed);
519 520

  gimp_help_set_help_data (spinbutton,
521
                           _("Use this value for random number generator "
522 523 524
                             "seed - this allows you to repeat a "
                             "given \"random\" operation"), NULL);

525
  button = gtk_button_new_with_mnemonic (_("_New Seed"));
526
  gtk_misc_set_padding (GTK_MISC (gtk_bin_get_child (GTK_BIN (button))), 2, 0);
527 528 529
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

530
  /* Send spinbutton as data so that we can change the value in
531 532
   * gimp_random_seed_update()
   */
533
  g_signal_connect (button, "clicked",
534 535
                    G_CALLBACK (gimp_random_seed_update),
                    spinbutton);
536 537

  gimp_help_set_help_data (button,
538 539
                           _("Seed random number generator with a generated "
                             "random number"),
540
                           NULL);
541

542
  toggle = gtk_check_button_new_with_mnemonic (_("_Randomize"));
543 544 545
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), *random_seed);
  gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);
546

547 548 549 550
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    random_seed);

551 552 553 554 555
  /* Need to create a new seed when the "Randomize" toggle is activated  */
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (gimp_random_seed_update),
                    spinbutton);

556
  g_object_set_data (G_OBJECT (hbox), "spinbutton", spinbutton);
557
  g_object_set_data (G_OBJECT (hbox), "button", button);
558 559
  g_object_set_data (G_OBJECT (hbox), "toggle", toggle);

560 561 562 563 564 565
  g_object_bind_property (toggle,     "active",
                          spinbutton, "sensitive",
                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
  g_object_bind_property (toggle, "active",
                          button, "sensitive",
                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
566

567 568 569
  return hbox;
}

570 571 572
typedef struct
{
  GimpChainButton *chainbutton;
573 574 575
  gboolean         chain_constrains_ratio;
  gdouble          orig_x;
  gdouble          orig_y;
576 577 578 579 580
  gdouble          last_x;
  gdouble          last_y;
} GimpCoordinatesData;

static void
581
gimp_coordinates_callback (GtkWidget           *widget,
582
                           GimpCoordinatesData *data)
583 584 585 586 587 588 589
{
  gdouble new_x;
  gdouble new_y;

  new_x = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0);
  new_y = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1);

590
  if (gimp_chain_button_get_active (data->chainbutton))
591
    {
592
      if (data->chain_constrains_ratio)
Sven Neumann's avatar
Sven Neumann committed
593
        {
594
          if ((data->orig_x != 0) && (data->orig_y != 0))
Sven Neumann's avatar
Sven Neumann committed
595
            {
596 597 598 599
              g_signal_handlers_block_by_func (widget,
                                               gimp_coordinates_callback,
                                               data);

600
              if (ROUND (new_x) != ROUND (data->last_x))
Sven Neumann's avatar
Sven Neumann committed
601
                {
602 603
                  data->last_x = new_x;
                  new_y = (new_x * data->orig_y) / data->orig_x;
604

Sven Neumann's avatar
Sven Neumann committed
605
                  gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 1,
606
                                              new_y);
607
                  data->last_y
608
                    = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1);
Sven Neumann's avatar
Sven Neumann committed
609
                }
610
              else if (ROUND (new_y) != ROUND (data->last_y))
Sven Neumann's avatar
Sven Neumann committed
611
                {
612 613
                  data->last_y = new_y;
                  new_x = (new_y * data->orig_x) / data->orig_y;
614

Sven Neumann's avatar
Sven Neumann committed
615
                  gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 0,
616
                                              new_x);
617
                  data->last_x
618
                    = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0);
Sven Neumann's avatar
Sven Neumann committed
619
                }
620 621 622 623

              g_signal_handlers_unblock_by_func (widget,
                                                 gimp_coordinates_callback,
                                                 data);
Sven Neumann's avatar
Sven Neumann committed
624 625
            }
        }
626
      else
Sven Neumann's avatar
Sven Neumann committed
627
        {
628
          if (new_x != data->last_x)
Sven Neumann's avatar
Sven Neumann committed
629 630
            {
              gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 1, new_x);
631
              data->last_y = data->last_x
632
                = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1);
Sven Neumann's avatar
Sven Neumann committed
633
            }
634
          else if (new_y != data->last_y)
Sven Neumann's avatar
Sven Neumann committed
635 636
            {
              gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (widget), 0, new_y);
637
              data->last_x = data->last_y
638
                = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0);
Sven Neumann's avatar
Sven Neumann committed
639 640
            }
        }
641 642 643
    }
  else
    {
644 645 646 647
      if (new_x != data->last_x)
        data->last_x = new_x;
      if (new_y != data->last_y)
        data->last_y = new_y;
648
    }
649 650
}

651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
static void
gimp_coordinates_data_free (GimpCoordinatesData *data)
{
  g_slice_free (GimpCoordinatesData, data);
}

static void
gimp_coordinates_chainbutton_toggled (GimpChainButton *button,
                                      GimpSizeEntry   *entry)
{
  if (gimp_chain_button_get_active (button))
    {
      GimpCoordinatesData *data;

      data = g_object_get_data (G_OBJECT (entry), "coordinates-data");

      data->orig_x = gimp_size_entry_get_refval (entry, 0);
      data->orig_y = gimp_size_entry_get_refval (entry, 1);
    }
}

672 673
/**
 * gimp_coordinates_new:
674
 * @unit:                   The initial unit of the #GimpUnitMenu.
675 676 677 678
 * @unit_format:            A printf-like unit-format string as is used with
 *                          gimp_unit_menu_new().
 * @menu_show_pixels:       %TRUE if the #GimpUnitMenu should contain an item
 *                          for GIMP_UNIT_PIXEL.
679
 * @menu_show_percent:      %TRUE if the #GimpUnitMenu should contain an item
680
 *                          for GIMP_UNIT_PERCENT.
681
 * @spinbutton_width:       The horizontal size of the #GimpSizeEntry's
682 683
 *                           #GtkSpinButton's.
 * @update_policy:          The update policy for the #GimpSizeEntry.
684
 * @chainbutton_active:     %TRUE if the attached #GimpChainButton should be
685
 *                          active.
686 687
 * @chain_constrains_ratio: %TRUE if the chainbutton should constrain the
 *                          fields' aspect ratio. If %FALSE, the values will
688
 *                          be constrained.
689 690 691 692 693 694 695 696 697 698 699 700 701 702
 * @xlabel:                 The label for the X coordinate.
 * @x:                      The initial value of the X coordinate.
 * @xres:                   The horizontal resolution in DPI.
 * @lower_boundary_x:       The lower boundary of the X coordinate.
 * @upper_boundary_x:       The upper boundary of the X coordinate.
 * @xsize_0:                The X value which will be treated as 0%.
 * @xsize_100:              The X value which will be treated as 100%.
 * @ylabel:                 The label for the Y coordinate.
 * @y:                      The initial value of the Y coordinate.
 * @yres:                   The vertical resolution in DPI.
 * @lower_boundary_y:       The lower boundary of the Y coordinate.
 * @upper_boundary_y:       The upper boundary of the Y coordinate.
 * @ysize_0:                The Y value which will be treated as 0%.
 * @ysize_100:              The Y value which will be treated as 100%.
703
 *
704 705 706 707 708
 * Convenience function that creates a #GimpSizeEntry with two fields for x/y
 * coordinates/sizes with a #GimpChainButton attached to constrain either the
 * two fields' values or the ratio between them.
 *
 * Returns: The new #GimpSizeEntry.
709
 **/
710
GtkWidget *
711
gimp_coordinates_new (GimpUnit         unit,
Sven Neumann's avatar
Sven Neumann committed
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
                      const gchar     *unit_format,
                      gboolean         menu_show_pixels,
                      gboolean         menu_show_percent,
                      gint             spinbutton_width,
                      GimpSizeEntryUpdatePolicy  update_policy,

                      gboolean         chainbutton_active,
                      gboolean         chain_constrains_ratio,

                      const gchar     *xlabel,
                      gdouble          x,
                      gdouble          xres,
                      gdouble          lower_boundary_x,
                      gdouble          upper_boundary_x,
                      gdouble          xsize_0,   /* % */
                      gdouble          xsize_100, /* % */

                      const gchar     *ylabel,
                      gdouble          y,
                      gdouble          yres,
                      gdouble          lower_boundary_y,
                      gdouble          upper_boundary_y,
                      gdouble          ysize_0,   /* % */
                      gdouble          ysize_100  /* % */)
736
{
737
  GimpCoordinatesData *data;
738
  GtkAdjustment       *adjustment;
739 740 741
  GtkWidget           *spinbutton;
  GtkWidget           *sizeentry;
  GtkWidget           *chainbutton;
742

743 744 745
  adjustment = (GtkAdjustment *) gtk_adjustment_new (1, 0, 1, 1, 10, 0);
  spinbutton = gtk_spin_button_new (adjustment, 1.0, 2);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
746 747 748 749 750 751 752 753 754

  if (spinbutton_width > 0)
    {
      if (spinbutton_width < 17)
        gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), spinbutton_width);
      else
        gtk_widget_set_size_request (spinbutton, spinbutton_width, -1);
    }

755
  sizeentry = gimp_size_entry_new (1, unit, unit_format,
Sven Neumann's avatar
Sven Neumann committed
756 757 758 759 760
                                   menu_show_pixels,
                                   menu_show_percent,
                                   FALSE,
                                   spinbutton_width,
                                   update_policy);
761 762
  gtk_table_set_col_spacing (GTK_TABLE (sizeentry), 0, 4);
  gtk_table_set_col_spacing (GTK_TABLE (sizeentry), 2, 4);
763 764 765 766 767
  gimp_size_entry_add_field (GIMP_SIZE_ENTRY (sizeentry),
                             GTK_SPIN_BUTTON (spinbutton), NULL);
  gtk_table_attach_defaults (GTK_TABLE (sizeentry), spinbutton, 1, 2, 0, 1);
  gtk_widget_show (spinbutton);

768
  gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (sizeentry),
Sven Neumann's avatar
Sven Neumann committed
769
                            (update_policy == GIMP_SIZE_ENTRY_UPDATE_RESOLUTION) ||
770
                            (menu_show_pixels == FALSE) ?
Sven Neumann's avatar
Sven Neumann committed
771
                            GIMP_UNIT_INCH : GIMP_UNIT_PIXEL);
772 773 774 775

  gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (sizeentry), 0, xres, TRUE);
  gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (sizeentry), 1, yres, TRUE);
  gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (sizeentry), 0,
Sven Neumann's avatar
Sven Neumann committed
776
                                         lower_boundary_x, upper_boundary_x);
777
  gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (sizeentry), 1,
Sven Neumann's avatar
Sven Neumann committed
778
                                         lower_boundary_y, upper_boundary_y);
779

780 781 782
  if (menu_show_percent)
    {
      gimp_size_entry_set_size (GIMP_SIZE_ENTRY (sizeentry), 0,
Sven Neumann's avatar
Sven Neumann committed
783
                                xsize_0, xsize_100);
784
      gimp_size_entry_set_size (GIMP_SIZE_ENTRY (sizeentry), 1,
Sven Neumann's avatar
Sven Neumann committed
785
                                ysize_0, ysize_100);
786 787
    }

788 789 790
  gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (sizeentry), 0, x);
  gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (sizeentry), 1, y);

791 792 793 794
  gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry),
                                xlabel, 0, 0, 0.0);
  gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (sizeentry),
                                ylabel, 1, 0, 0.0);
795

796
  chainbutton = gimp_chain_button_new (GIMP_CHAIN_RIGHT);
797

798
  if (chainbutton_active)
799
    gimp_chain_button_set_active (GIMP_CHAIN_BUTTON (chainbutton), TRUE);
800

801
  gtk_table_attach (GTK_TABLE (sizeentry), chainbutton, 2, 3, 0, 2,
Sven Neumann's avatar
Sven Neumann committed
802
                    GTK_SHRINK | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
803
  gtk_widget_show (chainbutton);
804

805 806 807 808 809 810 811 812
  data = g_slice_new (GimpCoordinatesData);

  data->chainbutton            = GIMP_CHAIN_BUTTON (chainbutton);
  data->chain_constrains_ratio = chain_constrains_ratio;
  data->orig_x                 = x;
  data->orig_y                 = y;
  data->last_x                 = x;
  data->last_y                 = y;
813

814 815 816
  g_object_set_data_full (G_OBJECT (sizeentry), "coordinates-data",
                          data,
                          (GDestroyNotify) gimp_coordinates_data_free);
817

818
  g_signal_connect (sizeentry, "value-changed",
Sven Neumann's avatar
Sven Neumann committed
819
                    G_CALLBACK (gimp_coordinates_callback),
820
                    data);
821

822
  g_object_set_data (G_OBJECT (sizeentry), "chainbutton", chainbutton);
823

824 825 826 827
  g_signal_connect (chainbutton, "toggled",
                    G_CALLBACK (gimp_coordinates_chainbutton_toggled),
                    sizeentry);

828 829 830
  return sizeentry;
}

831

832
/*
833
 *  Standard Callbacks
834 835
 */

836 837 838 839 840
/**
 * gimp_toggle_button_sensitive_update:
 * @toggle_button: The #GtkToggleButton the "set_sensitive" and
 *                 "inverse_sensitive" lists are attached to.
 *
841
 * If you attached a pointer to a #GtkWidget with g_object_set_data() and
842 843 844 845 846 847 848 849 850 851
 * the "set_sensitive" key to the #GtkToggleButton, the sensitive state of
 * the attached widget will be set according to the toggle button's
 * "active" state.
 *
 * You can attach an arbitrary list of widgets by attaching another
 * "set_sensitive" data pointer to the first widget (and so on...).
 *
 * This function can also set the sensitive state according to the toggle
 * button's inverse "active" state by attaching widgets with the
 * "inverse_sensitive" key.
852 853 854
 *
 * Deprecated: use g_object_bind_property() instead of using the
 *             "set_sensitive" and "inverse_sensitive" data pointers.
855
 **/
856
void
857
gimp_toggle_button_sensitive_update (GtkToggleButton *toggle_button)
858 859
{
  GtkWidget *set_sensitive;
860
  gboolean   active;
861

862
  active = gtk_toggle_button_get_active (toggle_button);
863

864
  set_sensitive =
865
    g_object_get_data (G_OBJECT (toggle_button), "set_sensitive");
866 867
  while (set_sensitive)
    {
868
      gtk_widget_set_sensitive (set_sensitive, active);
869
      set_sensitive =
870
        g_object_get_data (G_OBJECT (set_sensitive), "set_sensitive");
871 872 873
    }

  set_sensitive =
874
    g_object_get_data (G_OBJECT (toggle_button), "inverse_sensitive");
875 876
  while (set_sensitive)
    {
877
      gtk_widget_set_sensitive (set_sensitive, ! active);
878
      set_sensitive =
879
        g_object_get_data (G_OBJECT (set_sensitive), "inverse_sensitive");
880 881 882
    }
}

883 884
/**
 * gimp_toggle_button_update:
885
 * @widget: A #GtkToggleButton.
886 887
 * @data:   A pointer to a #gint variable which will store the value of
 *          gtk_toggle_button_get_active().
888
 *
889 890 891
 * Note that this function calls gimp_toggle_button_sensitive_update()
 * which is a deprecated hack you shouldn't use. See that function's
 * documentation for a proper replacement of its functionality.
892
 **/
893 894
void
gimp_toggle_button_update (GtkWidget *widget,
Sven Neumann's avatar
Sven Neumann committed
895
                           gpointer   data)
896
{
897
  gint *toggle_val = (gint *) data;
898 899 900 901 902 903 904 905 906

  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    *toggle_val = TRUE;
  else
    *toggle_val = FALSE;

  gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget));
}

907 908
/**
 * gimp_radio_button_update:
909
 * @widget: A #GtkRadioButton.
910
 * @data:   A pointer to a #gint variable which will store the value of
911
 *          GPOINTER_TO_INT (g_object_get_data (@widget, "gimp-item-data")).
912
 *
913 914 915
 * Note that this function calls gimp_toggle_button_sensitive_update()
 * which is a deprecated hack you shouldn't use. See that function's
 * documentation for a proper replacement of its functionality.
916
 **/
917 918
void
gimp_radio_button_update (GtkWidget *widget,
Sven Neumann's avatar
Sven Neumann committed
919
                          gpointer   data)
920
{
921
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
922
    {
923
      gint *toggle_val = (gint *) data;
924

925
      *toggle_val = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
Sven Neumann's avatar
Sven Neumann committed
926
                                                        "gimp-item-data"));
927
    }
928 929

  gimp_toggle_button_sensitive_update (GTK_TOGGLE_BUTTON (widget));
930 931
}

932 933
/**
 * gimp_int_adjustment_update:
934
 * @adjustment: A #GtkAdjustment.
935 936
 * @data:       A pointer to a #gint variable which will store the
 *              @adjustment's value.
937
 *
938 939
 * Note that the #GtkAdjustment's value (which is a #gdouble) will be
 * rounded with RINT().
940
 **/
941 942
void
gimp_int_adjustment_update (GtkAdjustment *adjustment,
Sven Neumann's avatar
Sven Neumann committed
943
                            gpointer       data)
944
{
945
  gint *val = (gint *) data;
946

947
  *val = RINT (gtk_adjustment_get_value (adjustment));
948 949
}

950 951 952
/**
 * gimp_uint_adjustment_update:
 * @adjustment: A #GtkAdjustment.
953 954
 * @data:       A pointer to a #guint variable which will store the
 *              @adjustment's value.
955
 *
956
 * Note that the #GtkAdjustment's value (which is a #gdouble) will be rounded
957
 * with (#guint) (value + 0.5).
958
 **/
959 960
void
gimp_uint_adjustment_update (GtkAdjustment *adjustment,
Sven Neumann's avatar
Sven Neumann committed
961
                             gpointer       data)
962
{
963
  guint *val = (guint *) data;
964

965
  *val = (guint) (gtk_adjustment_get_value (adjustment) + 0.5);
966 967
}

968 969
/**
 * gimp_float_adjustment_update:
970
 * @adjustment: A #GtkAdjustment.
971
 * @data:       A pointer to a #gfloat variable which will store the
972
 *              @adjustment's value.
973
 **/
974 975
void
gimp_float_adjustment_update (GtkAdjustment *adjustment,
Sven Neumann's avatar
Sven Neumann committed
976
                              gpointer       data)
977
{
978
  gfloat *val = (gfloat *) data;
979 980

  *val = gtk_adjustment_get_value (adjustment);
981

982 983
}

984 985
/**
 * gimp_double_adjustment_update:
986
 * @adjustment: A #GtkAdjustment.
987 988
 * @data:       A pointer to a #gdouble variable which will store the
 *              @adjustment's value.
989
 **/
990 991
void
gimp_double_adjustment_update (GtkAdjustment *adjustment,
Sven Neumann's avatar
Sven Neumann committed
992
                               gpointer       data)
993
{
994
  gdouble *val = (gdouble *) data;
995

996
  *val = gtk_adjustment_get_value (adjustment);
997
}