/* GTK - The GIMP Toolkit
* 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 .
* Copyright ( C ) 2014 Red Hat , Inc .
*
* 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
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*/
/*
* 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
* GTK + at ftp : //ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gtkscalebutton.h"
#include "gtkaccessiblerange.h"
#include "gtkadjustment.h"
#include "gtkbox.h"
#include "gtkbuttonprivate.h"
#include "gtktogglebutton.h"
#include "gtkeventcontrollerscroll.h"
#include "gtkframe.h"
#include "gtkgesture.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkorientable.h"
#include "gtkpopover.h"
#include "gtkprivate.h"
#include "gtkrangeprivate.h"
#include "gtkscale.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkwindowprivate.h"
#include "gtknative.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
/**
* 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 [ class @ Gtk . VolumeButton ] subclass that
* is tailored for this use case .
*
* # Shortcuts and Gestures
*
* The following signals have default keybindings :
*
* - [ signal @ Gtk . ScaleButton : : popup ]
*
* # CSS nodes
*
* ` ` `
* scalebutton . scale
* ╰ ─ ─ button . toggle
* ╰ ─ ─ < icon >
* ` ` `
*
* ` GtkScaleButton ` has a single CSS node with name scalebutton and ` . scale `
* style class , and contains a ` button ` node with a ` . toggle ` style class .
*/
#define SCALE_SIZE 100
enum
{
VALUE_CHANGED,
POPUP,
POPDOWN,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_VALUE,
PROP_ADJUSTMENT,
PROP_ICONS,
PROP_ACTIVE,
PROP_HAS_FRAME,
/* GtkOrientable */
PROP_ORIENTATION,
N_PROPS
};
static GParamSpec *props[N_PROPS] = { NULL, };
typedef struct
{
GtkWidget *button;
GtkWidget *plus_button;
GtkWidget *minus_button;
GtkWidget *dock;
GtkWidget *box;
GtkWidget *scale;
GtkWidget *active_button;
GtkOrientation orientation;
GtkOrientation applied_orientation;
guint autoscroll_timeout;
GtkScrollType autoscroll_step;
gboolean autoscrolling;
char **icon_list;
GtkAdjustment *adjustment; /* needed because it must be settable in init() */
} GtkScaleButtonPrivate;
static void gtk_scale_button_constructed (GObject *object);
static void gtk_scale_button_dispose (GObject *object);
static void gtk_scale_button_finalize (GObject *object);
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);
static void gtk_scale_button_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline);
static void gtk_scale_button_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
static void gtk_scale_button_set_orientation_private (GtkScaleButton *button,
GtkOrientation orientation);
static void gtk_scale_button_popup (GtkWidget *widget);
static void gtk_scale_button_popdown (GtkWidget *widget);
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);
static gboolean gtk_scale_button_scroll_controller_scroll (GtkEventControllerScroll *scroll,
double dx,
double dy,
GtkScaleButton *button);
static void gtk_scale_button_accessible_range_init (GtkAccessibleRangeInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkScaleButton, gtk_scale_button, GTK_TYPE_WIDGET,
G_ADD_PRIVATE (GtkScaleButton)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE_RANGE,
gtk_scale_button_accessible_range_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
static guint signals[LAST_SIGNAL] = { 0 , };
static gboolean
accessible_range_set_current_value (GtkAccessibleRange *accessible_range,
double value)
{
gtk_scale_button_set_value (GTK_SCALE_BUTTON (accessible_range), value);
return TRUE ;
}
static void
gtk_scale_button_accessible_range_init (GtkAccessibleRangeInterface *iface)
{
iface->set_current_value = accessible_range_set_current_value;
}
static void
gtk_scale_button_class_init (GtkScaleButtonClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gobject_class->constructed = gtk_scale_button_constructed;
gobject_class->finalize = gtk_scale_button_finalize;
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;
widget_class->measure = gtk_scale_button_measure;
widget_class->size_allocate = gtk_scale_button_size_allocate;
widget_class->focus = gtk_widget_focus_child;
widget_class->grab_focus = gtk_widget_grab_focus_child;
props[PROP_ORIENTATION] = g_param_spec_override ("orientation" ,
g_object_interface_find_property (g_type_default_interface_ref (GTK_TYPE_ORIENTABLE), "orientation" ));
/**
* GtkScaleButton : value :
*
* The value of the scale .
*/
props[PROP_VALUE] = g_param_spec_double ("value" , NULL, NULL,
-G_MAXDOUBLE,
G_MAXDOUBLE,
0 ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkScaleButton : adjustment :
*
* The ` GtkAdjustment ` that is used as the model .
*/
props[PROP_ADJUSTMENT] = g_param_spec_object ("adjustment" , NULL, NULL,
GTK_TYPE_ADJUSTMENT,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkScaleButton : icons :
*
* 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
* range of values .
*
* 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
* 50 % of the scale , and the second one for the top 50 % .
*
* It is recommended to use at least 3 icons so that the
* ` GtkScaleButton ` reflects the current value of the scale
* better for the users .
*/
props[PROP_ICONS] = g_param_spec_boxed ("icons" , NULL, NULL,
G_TYPE_STRV,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
/**
* GtkScaleButton : active :
*
* If the scale button should be pressed in .
*
* Since : 4 . 10
*/
props[PROP_ACTIVE] = g_param_spec_boolean ("active" , NULL, NULL,
FALSE ,
G_PARAM_READABLE | G_PARAM_STATIC_NAME);
/**
* GtkScaleButton : has - frame :
*
* If the scale button has a frame .
*
* Since : 4 . 14
*/
props[PROP_HAS_FRAME] = g_param_spec_boolean ("has-frame" , NULL, NULL,
FALSE ,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPS, props);
/**
* GtkScaleButton : : value - changed :
* @ button : the object which received the signal
* @ value : the new value
*
* Emitted when the value field has changed .
*/
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,
NULL,
G_TYPE_NONE, 1 , G_TYPE_DOUBLE);
/**
* GtkScaleButton : : popup :
* @ button : the object which received the signal
*
* Emitted to popup the scale widget .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
*
* The default bindings for this signal are < kbd > Space < / kbd > ,
* < kbd > Enter < / kbd > and < kbd > Return < / kbd > .
*/
signals[POPUP] =
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,
NULL,
G_TYPE_NONE, 0 );
/**
* GtkScaleButton : : popdown :
* @ button : the object which received the signal
*
* Emitted to dismiss the popup .
*
* This is a [ keybinding signal ] ( class . SignalAction . html ) .
*
* The default binding for this signal is < kbd > Escape < / kbd > .
*/
signals[POPDOWN] =
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,
NULL,
G_TYPE_NONE, 0 );
/* Key bindings */
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_space, GDK_NO_MODIFIER_MASK,
"popup" ,
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_KP_Space, GDK_NO_MODIFIER_MASK,
"popup" ,
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_Return, GDK_NO_MODIFIER_MASK,
"popup" ,
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_ISO_Enter, GDK_NO_MODIFIER_MASK,
"popup" ,
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_KP_Enter, GDK_NO_MODIFIER_MASK,
"popup" ,
NULL);
gtk_widget_class_add_binding_signal (widget_class,
GDK_KEY_Escape, GDK_NO_MODIFIER_MASK,
"popdown" ,
NULL);
/* Bind class to template
*/
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gtk/libgtk/ui/gtkscalebutton.ui" );
gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, button);
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_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);
gtk_widget_class_set_css_name (widget_class, I_("scalebutton" ));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP);
}
static gboolean
start_autoscroll (gpointer data)
{
GtkScaleButton *button = data;
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gtk_range_start_autoscroll (GTK_RANGE (priv->scale), priv->autoscroll_step);
priv->autoscrolling = TRUE ;
priv->autoscroll_timeout = 0 ;
return G_SOURCE_REMOVE;
}
static void
button_pressed_cb (GtkGesture *gesture,
int n_press,
double x,
double y,
GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
GtkWidget *widget;
widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
if (widget == priv->plus_button)
priv->autoscroll_step = GTK_SCROLL_PAGE_FORWARD;
else
priv->autoscroll_step = GTK_SCROLL_PAGE_BACKWARD;
priv->autoscroll_timeout = g_timeout_add (200 , start_autoscroll, button);
}
static void
gtk_scale_button_toggled (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gboolean active;
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ACTIVE]);
active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button));
if (active)
gtk_popover_popup (GTK_POPOVER (priv->dock));
else
gtk_popover_popdown (GTK_POPOVER (priv->dock));
}
static void
gtk_scale_button_closed (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE );
}
static void
gtk_scale_button_init (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
GtkEventController *controller;
priv->orientation = GTK_ORIENTATION_VERTICAL;
priv->applied_orientation = GTK_ORIENTATION_VERTICAL;
gtk_widget_init_template (GTK_WIDGET (button));
gtk_widget_set_parent (priv->dock, GTK_WIDGET (button));
/* Need a local reference to the adjustment */
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);
gtk_accessible_update_property (GTK_ACCESSIBLE (button),
GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (priv->adjustment) -
gtk_adjustment_get_page_size (priv->adjustment),
GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (priv->adjustment),
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (priv->adjustment),
-1 );
gtk_widget_add_css_class (GTK_WIDGET (button), "scale" );
controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
g_signal_connect (controller, "scroll" ,
G_CALLBACK (gtk_scale_button_scroll_controller_scroll),
button);
gtk_widget_add_controller (GTK_WIDGET (button), controller);
g_signal_connect_swapped (priv->dock, "closed" ,
G_CALLBACK (gtk_scale_button_closed), button);
g_signal_connect_swapped (priv->button, "toggled" ,
G_CALLBACK (gtk_scale_button_toggled), button);
g_signal_connect (gtk_button_get_gesture (GTK_BUTTON (priv->plus_button)),
"pressed" , G_CALLBACK (button_pressed_cb), button);
g_signal_connect (gtk_button_get_gesture (GTK_BUTTON (priv->minus_button)),
"pressed" , G_CALLBACK (button_pressed_cb), button);
}
static void
gtk_scale_button_constructed (GObject *object)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (object);
G_OBJECT_CLASS (gtk_scale_button_parent_class)->constructed (object);
/* set button text and size */
gtk_scale_button_update_icon (button);
}
static void
gtk_scale_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (object);
switch (prop_id)
{
case PROP_ORIENTATION:
gtk_scale_button_set_orientation_private (button, g_value_get_enum (value));
break ;
case PROP_VALUE:
gtk_scale_button_set_value (button, g_value_get_double (value));
break ;
case PROP_ADJUSTMENT:
gtk_scale_button_set_adjustment (button, g_value_get_object (value));
break ;
case PROP_ICONS:
gtk_scale_button_set_icons (button,
(const char **)g_value_get_boxed (value));
break ;
case PROP_HAS_FRAME:
gtk_scale_button_set_has_frame (button, g_value_get_boolean (value));
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break ;
}
}
static void
gtk_scale_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (object);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
switch (prop_id)
{
case PROP_ORIENTATION:
g_value_set_enum (value, priv->orientation);
break ;
case PROP_VALUE:
g_value_set_double (value, gtk_scale_button_get_value (button));
break ;
case PROP_ADJUSTMENT:
g_value_set_object (value, gtk_scale_button_get_adjustment (button));
break ;
case PROP_ICONS:
g_value_set_boxed (value, priv->icon_list);
break ;
case PROP_ACTIVE:
g_value_set_boolean (value, gtk_scale_button_get_active (button));
break ;
case PROP_HAS_FRAME:
g_value_set_boolean (value, gtk_scale_button_get_has_frame (button));
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break ;
}
}
static void
gtk_scale_button_finalize (GObject *object)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (object);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_clear_pointer (&priv->icon_list, g_strfreev);
g_clear_object (&priv->adjustment);
g_clear_handle_id (&priv->autoscroll_timeout, g_source_remove);
G_OBJECT_CLASS (gtk_scale_button_parent_class)->finalize (object);
}
static void
gtk_scale_button_dispose (GObject *object)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (object);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_clear_pointer (&priv->dock, gtk_widget_unparent);
g_clear_pointer (&priv->button, gtk_widget_unparent);
G_OBJECT_CLASS (gtk_scale_button_parent_class)->dispose (object);
}
/**
* gtk_scale_button_new :
* @ 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 )
* @ icons : ( nullable ) ( 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 ( )
*
* Creates a ` GtkScaleButton ` .
*
* The new scale button has a range between @ min and @ max ,
* with a stepping of @ step .
*
* Returns : a new ` GtkScaleButton `
*/
GtkWidget *
gtk_scale_button_new (double min,
double max,
double step,
const char **icons)
{
GtkScaleButton *button;
GtkAdjustment *adjustment;
adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0 );
button = g_object_new (GTK_TYPE_SCALE_BUTTON,
"adjustment" , adjustment,
"icons" , icons,
NULL);
return GTK_WIDGET (button);
}
/**
* gtk_scale_button_get_value :
* @ button : a ` GtkScaleButton `
*
* Gets the current value of the scale button .
*
* Returns : current value of the scale button
*/
double
gtk_scale_button_get_value (GtkScaleButton * button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), 0 );
return gtk_adjustment_get_value (priv->adjustment);
}
/**
* gtk_scale_button_set_value :
* @ button : a ` GtkScaleButton `
* @ value : new value of the scale button
*
* 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 [ signal @ Gtk . ScaleButton : : value - changed ]
* signal if the value changes .
*/
void
gtk_scale_button_set_value (GtkScaleButton *button,
double value)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
gtk_range_set_value (GTK_RANGE (priv->scale), value);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_VALUE]);
}
/**
* gtk_scale_button_set_icons :
* @ button : a ` GtkScaleButton `
* @ icons : ( array zero - terminated = 1 ) : a % NULL - terminated array of icon names
*
* Sets the icons to be used by the scale button .
*/
void
gtk_scale_button_set_icons (GtkScaleButton *button,
const char **icons)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
char **tmp;
g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
tmp = priv->icon_list;
priv->icon_list = g_strdupv ((char **) icons);
g_strfreev (tmp);
gtk_scale_button_update_icon (button);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ICONS]);
}
/**
* gtk_scale_button_get_adjustment :
* @ button : a ` GtkScaleButton `
*
* Gets the ` GtkAdjustment ` associated with the ` GtkScaleButton ` ’ s scale .
*
* See [ method @ Gtk . Range . get_adjustment ] for details .
*
* Returns : ( transfer none ) : the adjustment associated with the scale
*/
GtkAdjustment*
gtk_scale_button_get_adjustment (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
return priv->adjustment;
}
/**
* gtk_scale_button_set_adjustment :
* @ button : a ` GtkScaleButton `
* @ adjustment : a ` GtkAdjustment `
*
* Sets the ` GtkAdjustment ` to be used as a model
* for the ` GtkScaleButton ` ’ s scale .
*
* See [ method @ Gtk . Range . set_adjustment ] for details .
*/
void
gtk_scale_button_set_adjustment (GtkScaleButton *button,
GtkAdjustment *adjustment)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
if (!adjustment)
adjustment = gtk_adjustment_new (0 .0 , 0 .0 , 0 .0 , 0 .0 , 0 .0 , 0 .0 );
else
g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
if (priv->adjustment != adjustment)
{
if (priv->adjustment)
g_object_unref (priv->adjustment);
priv->adjustment = g_object_ref_sink (adjustment);
if (priv->scale)
gtk_range_set_adjustment (GTK_RANGE (priv->scale), adjustment);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ADJUSTMENT]);
gtk_accessible_update_property (GTK_ACCESSIBLE (button),
GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment) -
gtk_adjustment_get_page_size (adjustment),
GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment),
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
-1 );
}
}
/**
* gtk_scale_button_get_plus_button :
* @ button : a ` GtkScaleButton `
*
* Retrieves the plus button of the ` GtkScaleButton . `
*
* Returns : ( transfer none ) ( type Gtk . Button ) : the plus button
* of the ` GtkScaleButton `
*/
GtkWidget *
gtk_scale_button_get_plus_button (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
return priv->plus_button;
}
/**
* gtk_scale_button_get_minus_button :
* @ button : a ` GtkScaleButton `
*
* Retrieves the minus button of the ` GtkScaleButton ` .
*
* Returns : ( transfer none ) ( type Gtk . Button ) : the minus button
* of the ` GtkScaleButton `
*/
GtkWidget *
gtk_scale_button_get_minus_button (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
return priv->minus_button;
}
/**
* gtk_scale_button_get_popup :
* @ button : a ` GtkScaleButton `
*
* Retrieves the popup of the ` GtkScaleButton ` .
*
* Returns : ( transfer none ) : the popup of the ` GtkScaleButton `
*/
GtkWidget *
gtk_scale_button_get_popup (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
return priv->dock;
}
/**
* gtk_scale_button_get_active :
* @ button : a ` GtkScaleButton `
*
* Queries a ` GtkScaleButton ` and returns its current state .
*
* Returns % TRUE if the scale button is pressed in and % FALSE
* if it is raised .
*
* Returns : whether the button is pressed
*
* Since : 4 . 10
*/
gboolean
gtk_scale_button_get_active (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), FALSE );
return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button));
}
/**
* gtk_scale_button_get_has_frame :
* @ button : a ` GtkScaleButton `
*
* Returns whether the button has a frame .
*
* Returns : % TRUE if the button has a frame
*
* Since : 4 . 14
*/
gboolean
gtk_scale_button_get_has_frame (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), TRUE );
return gtk_button_get_has_frame (GTK_BUTTON (priv->button));
}
/**
* gtk_scale_button_set_has_frame :
* @ button : a ` GtkScaleButton `
* @ has_frame : whether the button should have a visible frame
*
* Sets the style of the button .
*
* Since : 4 . 14
*/
void
gtk_scale_button_set_has_frame (GtkScaleButton *button,
gboolean has_frame)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
if (gtk_button_get_has_frame (GTK_BUTTON (priv->button)) == has_frame)
return ;
gtk_button_set_has_frame (GTK_BUTTON (priv->button), has_frame);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_HAS_FRAME]);
}
static void
apply_orientation (GtkScaleButton *button,
GtkOrientation orientation)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
if (priv->applied_orientation != orientation)
{
priv->applied_orientation = orientation;
gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box), orientation);
gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->scale), orientation);
if (orientation == GTK_ORIENTATION_VERTICAL)
{
gtk_box_reorder_child_after (GTK_BOX (priv->box), priv->scale,
priv->plus_button);
gtk_box_reorder_child_after (GTK_BOX (priv->box), priv->minus_button,
priv->scale);
gtk_widget_set_size_request (GTK_WIDGET (priv->scale), -1 , SCALE_SIZE);
gtk_range_set_inverted (GTK_RANGE (priv->scale), TRUE );
}
else
{
gtk_box_reorder_child_after (GTK_BOX (priv->box), priv->scale,
priv->minus_button);
gtk_box_reorder_child_after (GTK_BOX (priv->box), priv->plus_button,
priv->scale);
gtk_widget_set_size_request (GTK_WIDGET (priv->scale), SCALE_SIZE, -1 );
gtk_range_set_inverted (GTK_RANGE (priv->scale), FALSE );
}
}
}
static void
gtk_scale_button_set_orientation_private (GtkScaleButton *button,
GtkOrientation orientation)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
if (priv->orientation != orientation)
{
priv->orientation = orientation;
apply_orientation (button, priv->orientation);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_ORIENTATION]);
}
}
static gboolean
gtk_scale_button_scroll_controller_scroll (GtkEventControllerScroll *scroll,
double dx,
double dy,
GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
GtkAdjustment *adjustment;
double d;
adjustment = priv->adjustment;
d = CLAMP (gtk_scale_button_get_value (button) -
(dy * gtk_adjustment_get_step_increment (adjustment)),
gtk_adjustment_get_lower (adjustment),
gtk_adjustment_get_upper (adjustment));
gtk_scale_button_set_value (button, d);
return GDK_EVENT_STOP;
}
/*
* button callbacks .
*/
static void
gtk_scale_popup (GtkWidget *widget)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
apply_orientation (button, priv->orientation);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), TRUE );
}
static void
gtk_scale_button_popdown (GtkWidget *widget)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE );
}
static void
gtk_scale_button_popup (GtkWidget *widget)
{
gtk_scale_popup (widget);
}
/*
* + / - button callbacks .
*/
static gboolean
button_click (GtkScaleButton *button,
GtkWidget *active)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
GtkAdjustment *adjustment = priv->adjustment;
gboolean can_continue = TRUE ;
double val;
val = gtk_scale_button_get_value (button);
if (active == priv->plus_button)
val += gtk_adjustment_get_page_increment (adjustment);
else
val -= gtk_adjustment_get_page_increment (adjustment);
if (val <= gtk_adjustment_get_lower (adjustment))
{
can_continue = FALSE ;
val = gtk_adjustment_get_lower (adjustment);
}
else if (val > gtk_adjustment_get_upper (adjustment))
{
can_continue = FALSE ;
val = gtk_adjustment_get_upper (adjustment);
}
gtk_scale_button_set_value (button, val);
return can_continue;
}
static void
cb_button_clicked (GtkWidget *widget,
gpointer user_data)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
g_clear_handle_id (&priv->autoscroll_timeout, g_source_remove);
if (priv->autoscrolling)
{
gtk_range_stop_autoscroll (GTK_RANGE (priv->scale));
priv->autoscrolling = FALSE ;
return ;
}
button_click (button, widget);
}
static void
gtk_scale_button_update_icon (GtkScaleButton *button)
{
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
GtkAdjustment *adjustment;
double value;
const char *name;
guint num_icons;
if (!priv->icon_list || !priv->icon_list[0 ] || priv->icon_list[0 ][0 ] == '\0' )
{
gtk_button_set_icon_name (GTK_BUTTON (priv->button), "image-missing" );
return ;
}
num_icons = g_strv_length (priv->icon_list);
/* The 1-icon special case */
if (num_icons == 1 )
{
gtk_button_set_icon_name (GTK_BUTTON (priv->button), priv->icon_list[0 ]);
return ;
}
adjustment = priv->adjustment;
value = gtk_scale_button_get_value (button);
/* The 2-icons special case */
if (num_icons == 2 )
{
double limit;
limit = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / 2 + gtk_adjustment_get_lower (adjustment);
if (value < limit)
name = priv->icon_list[0 ];
else
name = priv->icon_list[1 ];
gtk_button_set_icon_name (GTK_BUTTON (priv->button), name);
return ;
}
/* With 3 or more icons */
if (value == gtk_adjustment_get_lower (adjustment))
{
name = priv->icon_list[0 ];
}
else if (value == gtk_adjustment_get_upper (adjustment))
{
name = priv->icon_list[1 ];
}
else
{
double step;
guint i;
step = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / (num_icons - 2 ); i = (guint) ((value - gtk_adjustment_get_lower (adjustment)) / step) + 2 ;
g_assert (i < num_icons);
name = priv->icon_list[i];
}
gtk_button_set_icon_name (GTK_BUTTON (priv->button), name);
}
static void
cb_scale_value_changed (GtkRange *range,
gpointer user_data)
{
GtkScaleButton *button = user_data;
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
double value;
double upper, lower;
value = gtk_range_get_value (range);
upper = gtk_adjustment_get_upper (priv->adjustment);
lower = gtk_adjustment_get_lower (priv->adjustment);
gtk_scale_button_update_icon (button);
gtk_widget_set_sensitive (priv->plus_button, value < upper);
gtk_widget_set_sensitive (priv->minus_button, lower < value);
g_signal_emit (button, signals[VALUE_CHANGED], 0 , value);
g_object_notify_by_pspec (G_OBJECT (button), props[PROP_VALUE]);
gtk_accessible_update_property (GTK_ACCESSIBLE (button),
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value,
-1 );
}
static void
cb_popup_mapped (GtkWidget *popup,
gpointer user_data)
{
GtkScaleButton *button = user_data;
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gtk_widget_grab_focus (priv->scale);
}
static void
gtk_scale_button_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gtk_widget_measure (priv->button,
orientation,
for_size,
minimum, natural,
minimum_baseline, natural_baseline);
}
static void
gtk_scale_button_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (button);
gtk_widget_size_allocate (priv->button,
&(GtkAllocation) { 0 , 0 , width, height },
baseline);
gtk_popover_present (GTK_POPOVER (priv->dock));
}
Messung V0.5 in Prozent C=98 H=94 G=95
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland