gtkscalebutton.c 31.1 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
2 3 4 5
 * Copyright (C) 2005 Ronald S. Bultje
 * Copyright (C) 2006, 2007 Christian Persch
 * Copyright (C) 2006 Jan Arne Petersen
 * Copyright (C) 2005-2007 Red Hat, Inc.
6
 * Copyright (C) 2014 Red Hat, Inc.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * Authors:
 * - Ronald S. Bultje <rbultje@ronald.bitfreak.net>
 * - Bastien Nocera <bnocera@redhat.com>
 * - Jan Arne Petersen <jpetersen@jpetersen.org>
 * - Christian Persch <chpe@svn.gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
25
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
26 27 28 29 30 31
 */

/*
 * Modified by the GTK+ Team and others 2007.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
32
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
33 34
 */

35
#include "config.h"
36

37 38
#include "gtkscalebutton.h"

39 40 41 42
#include <math.h>
#include <stdlib.h>
#include <string.h>

43
#include "gtkadjustment.h"
44 45
#include "gtkbindings.h"
#include "gtkframe.h"
46
#include "gtkmain.h"
47
#include "gtkmarshalers.h"
48
#include "gtkorientable.h"
49
#include "gtkpopover.h"
50
#include "gtkprivate.h"
51
#include "gtkscale.h"
Matthias Clasen's avatar
Matthias Clasen committed
52
#include "gtkbox.h"
53
#include "gtkwindow.h"
54
#include "gtkwindowprivate.h"
55
#include "gtktypebuiltins.h"
56
#include "gtkintl.h"
57
#include "a11y/gtkscalebuttonaccessible.h"
58 59 60 61 62 63 64 65 66 67

/**
 * SECTION:gtkscalebutton
 * @Short_description: A button which pops up a scale
 * @Title: GtkScaleButton
 *
 * #GtkScaleButton provides a button which pops up a scale widget.
 * This kind of widget is commonly used for volume controls in multimedia
 * applications, and GTK+ provides a #GtkVolumeButton subclass that
 * is tailored for this use case.
68 69 70 71 72
 *
 * # CSS nodes
 *
 * GtkScaleButton has a single CSS node with name button. To differentiate
 * it from a plain #GtkButton, it gets the .scale style class.
73 74 75
 */


76 77 78
#define SCALE_SIZE 100
#define CLICK_TIMEOUT 250

79 80
enum
{
81 82 83
  VALUE_CHANGED,
  POPUP,
  POPDOWN,
84

85 86 87
  LAST_SIGNAL
};

88 89
enum
{
90
  PROP_0,
91

92
  PROP_ORIENTATION,
93 94
  PROP_VALUE,
  PROP_SIZE,
95 96
  PROP_ADJUSTMENT,
  PROP_ICONS
97 98
};

99 100
struct _GtkScaleButtonPrivate
{
101 102
  GtkWidget *plus_button;
  GtkWidget *minus_button;
103
  GtkWidget *dock;
104
  GtkWidget *box;
105 106
  GtkWidget *scale;
  GtkWidget *image;
107
  GtkWidget *active_button;
108

109
  GtkIconSize size;
110
  GtkOrientation orientation;
111
  GtkOrientation applied_orientation;
112

113
  guint click_id;
114

115
  gchar **icon_list;
116 117

  GtkAdjustment *adjustment; /* needed because it must be settable in init() */
118
};
119

120
static void     gtk_scale_button_constructed    (GObject             *object);
121
static void	gtk_scale_button_dispose	(GObject             *object);
122
static void     gtk_scale_button_finalize       (GObject             *object);
123 124 125 126 127 128 129 130
static void	gtk_scale_button_set_property	(GObject             *object,
						 guint                prop_id,
						 const GValue        *value,
						 GParamSpec          *pspec);
static void	gtk_scale_button_get_property	(GObject             *object,
						 guint                prop_id,
						 GValue              *value,
						 GParamSpec          *pspec);
131 132
static void gtk_scale_button_set_orientation_private (GtkScaleButton *button,
                                                      GtkOrientation  orientation);
133 134
static gboolean	gtk_scale_button_scroll		(GtkWidget           *widget,
						 GdkEventScroll      *event);
135
static void     gtk_scale_button_clicked        (GtkButton           *button);
136 137
static void     gtk_scale_button_popup          (GtkWidget           *widget);
static void     gtk_scale_button_popdown        (GtkWidget           *widget);
138 139 140 141 142 143
static gboolean cb_button_press			(GtkWidget           *widget,
						 GdkEventButton      *event,
						 gpointer             user_data);
static gboolean cb_button_release		(GtkWidget           *widget,
						 GdkEventButton      *event,
						 gpointer             user_data);
144 145 146 147 148 149 150
static void     cb_button_clicked               (GtkWidget           *button,
                                                 gpointer             user_data);
static void     gtk_scale_button_update_icon    (GtkScaleButton      *button);
static void     cb_scale_value_changed          (GtkRange            *range,
                                                 gpointer             user_data);
static void     cb_popup_mapped                 (GtkWidget           *popup,
                                                 gpointer             user_data);
151

152
G_DEFINE_TYPE_WITH_CODE (GtkScaleButton, gtk_scale_button, GTK_TYPE_BUTTON,
153
                         G_ADD_PRIVATE (GtkScaleButton)
154 155
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
                                                NULL))
156

157
static guint signals[LAST_SIGNAL] = { 0, };
158 159 160 161 162 163

static void
gtk_scale_button_class_init (GtkScaleButtonClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
164
  GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
165
  GtkBindingSet *binding_set;
166

167
  gobject_class->constructed = gtk_scale_button_constructed;
168
  gobject_class->finalize = gtk_scale_button_finalize;
169 170 171
  gobject_class->dispose = gtk_scale_button_dispose;
  gobject_class->set_property = gtk_scale_button_set_property;
  gobject_class->get_property = gtk_scale_button_get_property;
172

173
  widget_class->scroll_event = gtk_scale_button_scroll;
174 175

  button_class->clicked = gtk_scale_button_clicked;
176

177 178 179 180 181
  /**
   * GtkScaleButton:orientation:
   *
   * The orientation of the #GtkScaleButton's popup window.
   *
182 183 184 185 186
   * Note that since GTK+ 2.16, #GtkScaleButton implements the
   * #GtkOrientable interface which has its own @orientation
   * property. However we redefine the property here in order to
   * override its default horizontal orientation.
   *
187 188
   * Since: 2.14
   **/
189 190 191
  g_object_class_override_property (gobject_class,
				    PROP_ORIENTATION,
				    "orientation");
192

193 194 195 196 197 198 199 200
  g_object_class_install_property (gobject_class,
				   PROP_VALUE,
				   g_param_spec_double ("value",
							P_("Value"),
							P_("The value of the scale"),
							-G_MAXDOUBLE,
							G_MAXDOUBLE,
							0,
201
							GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
202 203 204 205 206 207 208 209

  g_object_class_install_property (gobject_class,
				   PROP_SIZE,
				   g_param_spec_enum ("size",
						      P_("Icon size"),
						      P_("The icon size"),
						      GTK_TYPE_ICON_SIZE,
						      GTK_ICON_SIZE_SMALL_TOOLBAR,
210
						      GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
211 212 213 214 215 216 217

  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
							P_("Adjustment"),
							P_("The GtkAdjustment that contains the current value of this scale button object"),
                                                        GTK_TYPE_ADJUSTMENT,
218
                                                        GTK_PARAM_READWRITE));
219

220 221 222
  /**
   * GtkScaleButton:icons:
   *
223 224 225 226 227
   * The names of the icons to be used by the scale button.
   * The first item in the array will be used in the button
   * when the current value is the lowest value, the second
   * item for the highest value. All the subsequent icons will
   * be used for all the other values, spread evenly over the
228 229
   * range of values.
   *
230 231 232
   * If there's only one icon name in the @icons array, it will
   * be used for all the values. If only two icon names are in
   * the @icons array, the first one will be used for the bottom
233 234
   * 50% of the scale, and the second one for the top 50%.
   *
235 236
   * It is recommended to use at least 3 icons so that the
   * #GtkScaleButton reflects the current value of the scale
237 238 239 240 241 242 243 244 245 246 247
   * better for the users.
   *
   * Since: 2.12
   */
  g_object_class_install_property (gobject_class,
                                   PROP_ICONS,
                                   g_param_spec_boxed ("icons",
                                                       P_("Icons"),
                                                       P_("List of icon names"),
                                                       G_TYPE_STRV,
                                                       GTK_PARAM_READWRITE));
248

249 250
  /**
   * GtkScaleButton::value-changed:
251 252
   * @button: the object which received the signal
   * @value: the new value
253
   *
254 255
   * The ::value-changed signal is emitted when the value field has
   * changed.
256 257 258 259 260 261 262 263 264
   *
   * Since: 2.12
   */
  signals[VALUE_CHANGED] =
    g_signal_new (I_("value-changed"),
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkScaleButtonClass, value_changed),
		  NULL, NULL,
265
		  NULL,
266
		  G_TYPE_NONE, 1, G_TYPE_DOUBLE);
267

268 269 270 271
  /**
   * GtkScaleButton::popup:
   * @button: the object which received the signal
   *
272
   * The ::popup signal is a
273
   * [keybinding signal][GtkBindingSignal]
274 275 276 277 278 279
   * which gets emitted to popup the scale widget.
   *
   * The default bindings for this signal are Space, Enter and Return.
   *
   * Since: 2.12
   */
280
  signals[POPUP] =
281 282 283 284 285 286 287
    g_signal_new_class_handler (I_("popup"),
                                G_OBJECT_CLASS_TYPE (klass),
                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                G_CALLBACK (gtk_scale_button_popup),
                                NULL, NULL,
                                g_cclosure_marshal_VOID__VOID,
                                G_TYPE_NONE, 0);
288 289 290 291 292

  /**
   * GtkScaleButton::popdown:
   * @button: the object which received the signal
   *
293
   * The ::popdown signal is a
294
   * [keybinding signal][GtkBindingSignal]
295 296 297 298 299 300
   * which gets emitted to popdown the scale widget.
   *
   * The default binding for this signal is Escape.
   *
   * Since: 2.12
   */
301
  signals[POPDOWN] =
302 303 304 305 306 307 308
    g_signal_new_class_handler (I_("popdown"),
                                G_OBJECT_CLASS_TYPE (klass),
                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                G_CALLBACK (gtk_scale_button_popdown),
                                NULL, NULL,
                                g_cclosure_marshal_VOID__VOID,
                                G_TYPE_NONE, 0);
309 310 311 312

  /* Key bindings */
  binding_set = gtk_binding_set_by_class (widget_class);

313
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
314
				"popup", 0);
315
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, 0,
316
				"popup", 0);
317
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
318
				"popup", 0);
319
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
320
				"popup", 0);
321
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
322
				"popup", 0);
323
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
324
				"popdown", 0);
325

326 327 328
  /* Bind class to template
   */
  gtk_widget_class_set_template_from_resource (widget_class,
329
					       "/org/gtk/libgtk/ui/gtkscalebutton.ui");
330

331 332 333 334 335 336
  gtk_widget_class_bind_template_child_internal_private (widget_class, GtkScaleButton, plus_button);
  gtk_widget_class_bind_template_child_internal_private (widget_class, GtkScaleButton, minus_button);
  gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, dock);
  gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, box);
  gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, scale);
  gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, image);
337 338 339

  gtk_widget_class_bind_template_callback (widget_class, cb_button_press);
  gtk_widget_class_bind_template_callback (widget_class, cb_button_release);
340 341 342
  gtk_widget_class_bind_template_callback (widget_class, cb_button_clicked);
  gtk_widget_class_bind_template_callback (widget_class, cb_scale_value_changed);
  gtk_widget_class_bind_template_callback (widget_class, cb_popup_mapped);
343

344
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_BUTTON_ACCESSIBLE);
345
  gtk_widget_class_set_css_name (widget_class, "button");
346 347 348 349 350 351
}

static void
gtk_scale_button_init (GtkScaleButton *button)
{
  GtkScaleButtonPrivate *priv;
352
  GtkStyleContext *context;
353

354
  button->priv = priv = gtk_scale_button_get_instance_private (button);
355 356

  priv->click_id = 0;
357
  priv->orientation = GTK_ORIENTATION_VERTICAL;
358
  priv->applied_orientation = GTK_ORIENTATION_VERTICAL;
359

360
  gtk_widget_init_template (GTK_WIDGET (button));
361
  gtk_popover_set_relative_to (GTK_POPOVER (priv->dock), GTK_WIDGET (button));
362 363

  /* Need a local reference to the adjustment */
364 365 366
  priv->adjustment = gtk_adjustment_new (0, 0, 100, 2, 20, 0);
  g_object_ref_sink (priv->adjustment);
  gtk_range_set_adjustment (GTK_RANGE (priv->scale), priv->adjustment);
367

368
  gtk_widget_add_events (GTK_WIDGET (button), GDK_SMOOTH_SCROLL_MASK);
369 370 371

  context = gtk_widget_get_style_context (GTK_WIDGET (button));
  gtk_style_context_add_class (context, "scale");
372 373
}

374 375
static void
gtk_scale_button_constructed (GObject *object)
376
{
377 378
  GtkScaleButton *button = GTK_SCALE_BUTTON (object);
  GtkScaleButtonPrivate *priv = button->priv;
379

380
  G_OBJECT_CLASS (gtk_scale_button_parent_class)->constructed (object);
381

382 383 384 385 386 387 388 389 390 391 392
  /* set button text and size */
  priv->size = GTK_ICON_SIZE_SMALL_TOOLBAR;
  gtk_scale_button_update_icon (button);
}

static void
gtk_scale_button_set_property (GObject       *object,
			       guint          prop_id,
			       const GValue  *value,
			       GParamSpec    *pspec)
{
393
  GtkScaleButton *button = GTK_SCALE_BUTTON (object);
394 395 396

  switch (prop_id)
    {
397
    case PROP_ORIENTATION:
398
      gtk_scale_button_set_orientation_private (button, g_value_get_enum (value));
399
      break;
400 401 402 403
    case PROP_VALUE:
      gtk_scale_button_set_value (button, g_value_get_double (value));
      break;
    case PROP_SIZE:
404 405 406 407 408 409
      if (button->priv->size != g_value_get_enum (value))
        {
          button->priv->size = g_value_get_enum (value);
          gtk_scale_button_update_icon (button);
          g_object_notify_by_pspec (object, pspec);
        }
410 411 412 413
      break;
    case PROP_ADJUSTMENT:
      gtk_scale_button_set_adjustment (button, g_value_get_object (value));
      break;
414
    case PROP_ICONS:
415
      gtk_scale_button_set_icons (button,
416 417
                                  (const gchar **)g_value_get_boxed (value));
      break;
418 419 420 421 422 423
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

424
static void
425 426 427 428 429
gtk_scale_button_get_property (GObject     *object,
			       guint        prop_id,
			       GValue      *value,
			       GParamSpec  *pspec)
{
430 431
  GtkScaleButton *button = GTK_SCALE_BUTTON (object);
  GtkScaleButtonPrivate *priv = button->priv;
432 433 434

  switch (prop_id)
    {
435 436 437
    case PROP_ORIENTATION:
      g_value_set_enum (value, priv->orientation);
      break;
438
    case PROP_VALUE:
Matthias Clasen's avatar
Matthias Clasen committed
439
      g_value_set_double (value, gtk_scale_button_get_value (button));
440 441 442 443 444 445 446
      break;
    case PROP_SIZE:
      g_value_set_enum (value, priv->size);
      break;
    case PROP_ADJUSTMENT:
      g_value_set_object (value, gtk_scale_button_get_adjustment (button));
      break;
447 448 449
    case PROP_ICONS:
      g_value_set_boxed (value, priv->icon_list);
      break;
450 451 452 453 454 455 456
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
457
gtk_scale_button_finalize (GObject *object)
458
{
459 460
  GtkScaleButton *button = GTK_SCALE_BUTTON (object);
  GtkScaleButtonPrivate *priv = button->priv;
461

462 463 464 465 466
  if (priv->icon_list)
    {
      g_strfreev (priv->icon_list);
      priv->icon_list = NULL;
    }
467

468 469 470 471 472 473
  if (priv->adjustment)
    {
      g_object_unref (priv->adjustment);
      priv->adjustment = NULL;
    }

474 475
  G_OBJECT_CLASS (gtk_scale_button_parent_class)->finalize (object);
}
476

477 478 479 480 481
static void
gtk_scale_button_dispose (GObject *object)
{
  GtkScaleButton *button = GTK_SCALE_BUTTON (object);
  GtkScaleButtonPrivate *priv = button->priv;
482

483
  if (priv->dock)
484 485 486 487 488
    {
      gtk_widget_destroy (priv->dock);
      priv->dock = NULL;
    }

489
  if (priv->click_id != 0)
490 491 492 493 494
    {
      g_source_remove (priv->click_id);
      priv->click_id = 0;
    }

495
  G_OBJECT_CLASS (gtk_scale_button_parent_class)->dispose (object);
496 497 498
}

/**
499
 * gtk_scale_button_new:
500
 * @size: (type int): a stock icon size (#GtkIconSize)
Matthias Clasen's avatar
Matthias Clasen committed
501 502 503 504
 * @min: the minimum value of the scale (usually 0)
 * @max: the maximum value of the scale (usually 100)
 * @step: the stepping of value when a scroll-wheel event,
 *        or up/down arrow event occurs (usually 2)
505 506 507
 * @icons: (allow-none) (array zero-terminated=1): a %NULL-terminated
 *         array of icon names, or %NULL if you want to set the list
 *         later with gtk_scale_button_set_icons()
508
 *
Matthias Clasen's avatar
Matthias Clasen committed
509 510
 * Creates a #GtkScaleButton, with a range between @min and @max, with
 * a stepping of @step.
511
 *
512
 * Returns: a new #GtkScaleButton
513 514 515 516 517
 *
 * Since: 2.12
 */
GtkWidget *
gtk_scale_button_new (GtkIconSize   size,
Matthias Clasen's avatar
Matthias Clasen committed
518 519 520
		      gdouble       min,
		      gdouble       max,
		      gdouble       step,
521 522 523
		      const gchar **icons)
{
  GtkScaleButton *button;
524
  GtkAdjustment *adjustment;
525

526
  adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
527 528

  button = g_object_new (GTK_TYPE_SCALE_BUTTON,
529
                         "adjustment", adjustment,
530 531 532
                         "icons", icons,
                         "size", size,
                         NULL);
533 534 535 536 537

  return GTK_WIDGET (button);
}

/**
538
 * gtk_scale_button_get_value:
539 540 541 542
 * @button: a #GtkScaleButton
 *
 * Gets the current value of the scale button.
 *
543
 * Returns: current value of the scale button
544 545 546 547 548 549 550 551 552 553 554 555
 *
 * Since: 2.12
 */
gdouble
gtk_scale_button_get_value (GtkScaleButton * button)
{
  GtkScaleButtonPrivate *priv;

  g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), 0);

  priv = button->priv;

556
  return gtk_adjustment_get_value (priv->adjustment);
557 558 559
}

/**
560
 * gtk_scale_button_set_value:
561 562 563
 * @button: a #GtkScaleButton
 * @value: new value of the scale button
 *
564 565 566
 * Sets the current value of the scale; if the value is outside
 * the minimum or maximum range values, it will be clamped to fit
 * inside them. The scale button emits the #GtkScaleButton::value-changed
567
 * signal if the value changes.
568 569 570 571 572 573 574 575 576 577 578 579 580 581
 *
 * Since: 2.12
 */
void
gtk_scale_button_set_value (GtkScaleButton *button,
			    gdouble         value)
{
  GtkScaleButtonPrivate *priv;

  g_return_if_fail (GTK_IS_SCALE_BUTTON (button));

  priv = button->priv;

  gtk_range_set_value (GTK_RANGE (priv->scale), value);
582
  g_object_notify (G_OBJECT (button), "value");
583 584 585
}

/**
586
 * gtk_scale_button_set_icons:
587
 * @button: a #GtkScaleButton
588
 * @icons: (array zero-terminated=1): a %NULL-terminated array of icon names
589
 *
590
 * Sets the icons to be used by the scale button.
591
 * For details, see the #GtkScaleButton:icons property.
592
 *
593
 * Since: 2.12
594 595 596 597 598 599
 */
void
gtk_scale_button_set_icons (GtkScaleButton  *button,
			    const gchar    **icons)
{
  GtkScaleButtonPrivate *priv;
600
  gchar **tmp;
601 602 603 604 605

  g_return_if_fail (GTK_IS_SCALE_BUTTON (button));

  priv = button->priv;

606
  tmp = priv->icon_list;
607
  priv->icon_list = g_strdupv ((gchar **) icons);
608
  g_strfreev (tmp);
609
  gtk_scale_button_update_icon (button);
610 611

  g_object_notify (G_OBJECT (button), "icons");
612 613 614
}

/**
615
 * gtk_scale_button_get_adjustment:
616 617
 * @button: a #GtkScaleButton
 *
618
 * Gets the #GtkAdjustment associated with the #GtkScaleButton’s scale.
619 620
 * See gtk_range_get_adjustment() for details.
 *
621
 * Returns: (transfer none): the adjustment associated with the scale
Matthias Clasen's avatar
Matthias Clasen committed
622
 *
623 624 625 626 627 628 629
 * Since: 2.12
 */
GtkAdjustment*
gtk_scale_button_get_adjustment	(GtkScaleButton *button)
{
  g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);

630
  return button->priv->adjustment;
631 632 633
}

/**
634
 * gtk_scale_button_set_adjustment:
635 636 637
 * @button: a #GtkScaleButton
 * @adjustment: a #GtkAdjustment
 *
638
 * Sets the #GtkAdjustment to be used as a model
639
 * for the #GtkScaleButton’s scale.
640 641 642 643 644 645 646 647 648
 * See gtk_range_set_adjustment() for details.
 *
 * Since: 2.12
 */
void
gtk_scale_button_set_adjustment	(GtkScaleButton *button,
				 GtkAdjustment  *adjustment)
{
  g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
Javier Jardón's avatar
Javier Jardón committed
649

650
  if (!adjustment)
Javier Jardón's avatar
Javier Jardón committed
651
    adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
652 653
  else
    g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
654

655 656 657 658 659
  if (button->priv->adjustment != adjustment)
    {
      if (button->priv->adjustment)
        g_object_unref (button->priv->adjustment);
      button->priv->adjustment = g_object_ref_sink (adjustment);
660

661 662
      if (button->priv->scale)
        gtk_range_set_adjustment (GTK_RANGE (button->priv->scale), adjustment);
663

664 665
      g_object_notify (G_OBJECT (button), "adjustment");
    }
666 667
}

668 669 670 671 672 673
/**
 * gtk_scale_button_get_plus_button:
 * @button: a #GtkScaleButton
 *
 * Retrieves the plus button of the #GtkScaleButton.
 *
674
 * Returns: (transfer none) (type Gtk.Button): the plus button of the #GtkScaleButton as a #GtkButton
Matthias Clasen's avatar
Matthias Clasen committed
675
 *
676 677 678 679 680 681 682
 * Since: 2.14
 */
GtkWidget *
gtk_scale_button_get_plus_button (GtkScaleButton *button)
{
  g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);

683
  return button->priv->plus_button;
684 685 686 687 688 689 690 691
}

/**
 * gtk_scale_button_get_minus_button:
 * @button: a #GtkScaleButton
 *
 * Retrieves the minus button of the #GtkScaleButton.
 *
692
 * Returns: (transfer none) (type Gtk.Button): the minus button of the #GtkScaleButton as a #GtkButton
Matthias Clasen's avatar
Matthias Clasen committed
693
 *
694 695 696 697 698 699 700
 * Since: 2.14
 */
GtkWidget *
gtk_scale_button_get_minus_button (GtkScaleButton *button)
{
  g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);

701
  return button->priv->minus_button;
702 703
}

704 705 706 707 708 709
/**
 * gtk_scale_button_get_popup:
 * @button: a #GtkScaleButton
 *
 * Retrieves the popup of the #GtkScaleButton.
 *
710
 * Returns: (transfer none): the popup of the #GtkScaleButton
Matthias Clasen's avatar
Matthias Clasen committed
711
 *
712 713
 * Since: 2.14
 */
714
GtkWidget *
715 716
gtk_scale_button_get_popup (GtkScaleButton *button)
{
Matthias Clasen's avatar
Matthias Clasen committed
717
  g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
718 719 720 721

  return button->priv->dock;
}

722
static void
723 724
apply_orientation (GtkScaleButton *button,
                   GtkOrientation  orientation)
725 726 727
{
  GtkScaleButtonPrivate *priv = button->priv;

728
  if (priv->applied_orientation != orientation)
729
    {
730 731
      priv->applied_orientation = orientation;
      gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box), orientation);
732
      gtk_container_child_set (GTK_CONTAINER (priv->box),
733
                               priv->plus_button,
734 735 736 737 738
                               "pack-type",
                               orientation == GTK_ORIENTATION_VERTICAL ?
                               GTK_PACK_START : GTK_PACK_END,
                               NULL);
      gtk_container_child_set (GTK_CONTAINER (priv->box),
739
                               priv->minus_button,
740 741 742 743 744
                               "pack-type",
                               orientation == GTK_ORIENTATION_VERTICAL ?
                               GTK_PACK_END : GTK_PACK_START,
                               NULL);

745
      gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->scale), orientation);
746 747 748

      if (orientation == GTK_ORIENTATION_VERTICAL)
        {
749
          gtk_widget_set_size_request (GTK_WIDGET (priv->scale), -1, SCALE_SIZE);
750 751 752 753
          gtk_range_set_inverted (GTK_RANGE (priv->scale), TRUE);
        }
      else
        {
754
          gtk_widget_set_size_request (GTK_WIDGET (priv->scale), SCALE_SIZE, -1);
755 756
          gtk_range_set_inverted (GTK_RANGE (priv->scale), FALSE);
        }
757 758 759 760 761 762 763 764
    }
}

static void
gtk_scale_button_set_orientation_private (GtkScaleButton *button,
                                          GtkOrientation  orientation)
{
  GtkScaleButtonPrivate *priv = button->priv;
765

766 767 768
  if (priv->orientation != orientation)
    {
      priv->orientation = orientation;
769 770 771 772
      g_object_notify (G_OBJECT (button), "orientation");
    }
}

773 774 775 776 777 778 779 780 781 782
/*
 * button callbacks.
 */

static gboolean
gtk_scale_button_scroll (GtkWidget      *widget,
			 GdkEventScroll *event)
{
  GtkScaleButton *button;
  GtkScaleButtonPrivate *priv;
783
  GtkAdjustment *adjustment;
784 785 786 787
  gdouble d;

  button = GTK_SCALE_BUTTON (widget);
  priv = button->priv;
788
  adjustment = priv->adjustment;
789 790 791 792 793 794 795

  if (event->type != GDK_SCROLL)
    return FALSE;

  d = gtk_scale_button_get_value (button);
  if (event->direction == GDK_SCROLL_UP)
    {
796 797 798
      d += gtk_adjustment_get_step_increment (adjustment);
      if (d > gtk_adjustment_get_upper (adjustment))
	d = gtk_adjustment_get_upper (adjustment);
799
    }
800
  else if (event->direction == GDK_SCROLL_DOWN)
801
    {
802 803 804
      d -= gtk_adjustment_get_step_increment (adjustment);
      if (d < gtk_adjustment_get_lower (adjustment))
	d = gtk_adjustment_get_lower (adjustment);
805
    }
806 807
  else if (event->direction == GDK_SCROLL_SMOOTH)
    {
808
      d -= event->delta_y * gtk_adjustment_get_step_increment (adjustment);
809 810 811
      d = CLAMP (d, gtk_adjustment_get_lower (adjustment),
                 gtk_adjustment_get_upper (adjustment));
    }
812 813 814 815 816 817
  gtk_scale_button_set_value (button, d);

  return TRUE;
}

static gboolean
818
gtk_scale_popup (GtkWidget *widget)
819
{
820 821
  GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
  GtkScaleButtonPrivate *priv = button->priv;
822 823 824 825 826
  GtkWidget *toplevel;
  GtkBorder border;
  GtkRequisition req;
  gint w, h;
  gint size;
827

828
  gtk_popover_popup (GTK_POPOVER (priv->dock));
829

830 831 832 833 834 835 836 837 838 839 840 841 842 843
  toplevel = gtk_widget_get_toplevel (widget);
  _gtk_window_get_shadow_width (GTK_WINDOW (toplevel), &border);
  w = gtk_widget_get_allocated_width (toplevel) - border.left - border.right;
  h = gtk_widget_get_allocated_height (toplevel) - border.top - border.bottom;
  gtk_widget_get_preferred_size (priv->dock, NULL, &req);
  size = MAX (req.width, req.height);

  if (size > w)
    apply_orientation (button, GTK_ORIENTATION_VERTICAL);
  else if (size > h)
    apply_orientation (button, GTK_ORIENTATION_HORIZONTAL);
  else
    apply_orientation (button, priv->orientation);

844 845 846 847
  return TRUE;
}

static void
848
gtk_scale_button_popdown (GtkWidget *widget)
849
{
850 851
  GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
  GtkScaleButtonPrivate *priv = button->priv;
852

853
  gtk_popover_popdown (GTK_POPOVER (priv->dock));
854 855
}

856 857
static void
gtk_scale_button_clicked (GtkButton *button)
858
{
859
  gtk_scale_popup (GTK_WIDGET (button));
860 861 862
}

static void
863
gtk_scale_button_popup (GtkWidget *widget)
864
{
865
  gtk_scale_popup (widget);
866 867 868 869 870 871
}

/*
 * +/- button callbacks.
 */
static gboolean
872 873
button_click (GtkScaleButton *button,
              GtkWidget      *active)
874
{
875 876 877
  GtkScaleButtonPrivate *priv = button->priv;
  GtkAdjustment *adjustment = priv->adjustment;
  gboolean can_continue = TRUE;
878 879
  gdouble val;

880
  val = gtk_scale_button_get_value (button);
881

882 883 884 885
  if (active == priv->plus_button)
    val += gtk_adjustment_get_page_increment (adjustment);
  else
    val -= gtk_adjustment_get_page_increment (adjustment);
886

887
  if (val <= gtk_adjustment_get_lower (adjustment))
888
    {
889
      can_continue = FALSE;
890
      val = gtk_adjustment_get_lower (adjustment);
891
    }
892
  else if (val > gtk_adjustment_get_upper (adjustment))
893
    {
894
      can_continue = FALSE;
895
      val = gtk_adjustment_get_upper (adjustment);
896
    }
897

898 899
  gtk_scale_button_set_value (button, val);

900 901 902 903 904 905 906 907 908 909 910 911 912 913
  return can_continue;
}

static gboolean
cb_button_timeout (gpointer user_data)
{
  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
  GtkScaleButtonPrivate *priv = button->priv;
  gboolean res;

  if (priv->click_id == 0)
    return G_SOURCE_REMOVE;

  res = button_click (button, priv->active_button);
914 915 916 917 918 919 920 921 922 923 924 925 926 927
  if (!res)
    {
      g_source_remove (priv->click_id);
      priv->click_id = 0;
    }

  return res;
}

static gboolean
cb_button_press (GtkWidget      *widget,
		 GdkEventButton *event,
		 gpointer        user_data)
{
928 929 930
  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
  GtkScaleButtonPrivate *priv = button->priv;
  gint double_click_time;
931 932 933 934

  if (priv->click_id != 0)
    g_source_remove (priv->click_id);

935
  priv->active_button = widget;
936

937 938 939 940
  g_object_get (gtk_widget_get_settings (widget),
                "gtk-double-click-time", &double_click_time,
                NULL);
  priv->click_id = gdk_threads_add_timeout (double_click_time,
941 942
                                            cb_button_timeout,
                                            button);
943
  g_source_set_name_by_id (priv->click_id, "[gtk+] cb_button_timeout");
944 945 946 947 948 949 950 951 952 953
  cb_button_timeout (button);

  return TRUE;
}

static gboolean
cb_button_release (GtkWidget      *widget,
		   GdkEventButton *event,
		   gpointer        user_data)
{
954 955
  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
  GtkScaleButtonPrivate *priv = button->priv;
956 957 958 959 960 961 962 963 964 965 966

  if (priv->click_id != 0)
    {
      g_source_remove (priv->click_id);
      priv->click_id = 0;
    }

  return TRUE;
}

static void
967 968
cb_button_clicked (GtkWidget *widget,
                   gpointer   user_data)
969 970
{
  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
971
  GtkScaleButtonPrivate *priv = button->priv;
972

973 974
  if (priv->click_id != 0)
    return;
975

976
  button_click (button, widget);
977 978 979 980 981
}

static void
gtk_scale_button_update_icon (GtkScaleButton *button)
{
982
  GtkScaleButtonPrivate *priv = button->priv;
983
  GtkAdjustment *adjustment;
984 985 986 987
  gdouble value;
  const gchar *name;
  guint num_icons;

988
  if (!priv->icon_list || priv->icon_list[0][0] == '\0')
989
    {
990 991 992
      gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
                                    "image-missing",
                                    priv->size);
993 994
      return;
    }
995

996
  num_icons = g_strv_length (priv->icon_list);
997 998 999 1000 1001

  /* The 1-icon special case */
  if (num_icons == 1)
    {
      gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1002 1003
                                    priv->icon_list[0],
                                    priv->size);
1004 1005 1006
      return;
    }

1007
  adjustment = priv->adjustment;
1008 1009 1010 1011 1012 1013
  value = gtk_scale_button_get_value (button);

  /* The 2-icons special case */
  if (num_icons == 2)
    {
      gdouble limit;
1014

1015
      limit = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / 2 + gtk_adjustment_get_lower (adjustment);
1016
      if (value < limit)
1017
        name = priv->icon_list[0];
1018
      else
1019
        name = priv->icon_list[1];
1020 1021

      gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1022 1023
                                    name,
                                    priv->size);
1024 1025 1026 1027
      return;
    }

  /* With 3 or more icons */
1028
  if (value == gtk_adjustment_get_lower (adjustment))
1029 1030 1031
    {
      name = priv->icon_list[0];
    }
1032
  else if (value == gtk_adjustment_get_upper (adjustment))
1033 1034 1035 1036 1037 1038 1039 1040
    {
      name = priv->icon_list[1];
    }
  else
    {
      gdouble step;
      guint i;

1041
      step = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / (num_icons - 2); i = (guint) ((value - gtk_adjustment_get_lower (adjustment)) / step) + 2;
1042 1043 1044 1045 1046
      g_assert (i < num_icons);
      name = priv->icon_list[i];
    }

  gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1047 1048
                                name,
                                priv->size);
1049 1050 1051
}

static void
1052 1053
cb_scale_value_changed (GtkRange *range,
                        gpointer  user_data)
1054
{
1055
  GtkScaleButton *button = user_data;
1056
  gdouble value;
1057
  gdouble upper, lower;
1058 1059

  value = gtk_range_get_value (range);
1060 1061
  upper = gtk_adjustment_get_upper (button->priv->adjustment);
  lower = gtk_adjustment_get_lower (button->priv->adjustment);
1062 1063 1064

  gtk_scale_button_update_icon (button);

1065 1066 1067
  gtk_widget_set_sensitive (button->priv->plus_button, value < upper);
  gtk_widget_set_sensitive (button->priv->minus_button, lower < value);

1068 1069 1070
  g_signal_emit (button, signals[VALUE_CHANGED], 0, value);
  g_object_notify (G_OBJECT (button), "value");
}
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080

static void
cb_popup_mapped (GtkWidget *popup,
                 gpointer   user_data)
{
  GtkScaleButton *button = user_data;
  GtkScaleButtonPrivate *priv = button->priv;

  gtk_widget_grab_focus (priv->scale);
}