/*
* Copyright 2010 Canonical, Ltd.
* Copyright 2021-2024 Robert Tari
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of either or both of the following licenses:
*
* 1) the GNU Lesser General Public License version 3, as published by the
* Free Software Foundation; and/or
* 2) the GNU Lesser General Public License version 2.1, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the applicable version of the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of both the GNU Lesser General Public
* License version 3 and version 2.1 along with this program. If not, see
*
*
* Authors:
* Cody Russell
* Robert Tari
*/
#include
#include
#include "idorange.h"
#include "idoscalemenuitem.h"
#include "idotypebuiltins.h"
#include "idoactionhelper.h"
static void ido_scale_menu_item_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void ido_scale_menu_item_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static gboolean ido_scale_menu_item_parent_key_press_event (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data);
static void ido_scale_menu_item_select (GtkMenuItem *item);
static void ido_scale_menu_item_deselect (GtkMenuItem *item);
static gboolean ido_scale_menu_item_button_press_event (GtkWidget *menuitem,
GdkEventButton *event);
static gboolean ido_scale_menu_item_button_release_event (GtkWidget *menuitem,
GdkEventButton *event);
static gboolean ido_scale_menu_item_motion_notify_event (GtkWidget *menuitem,
GdkEventMotion *event);
static void ido_scale_menu_item_primary_image_notify (GtkImage *image,
GParamSpec *pspec,
IdoScaleMenuItem *item);
static void ido_scale_menu_item_secondary_image_notify (GtkImage *image,
GParamSpec *pspec,
IdoScaleMenuItem *item);
static void ido_scale_menu_item_parent_set (GtkWidget *item,
GtkWidget *previous_parent);
static void update_packing (IdoScaleMenuItem *self,
IdoScaleMenuItemStyle style);
static void default_primary_clicked_handler (IdoScaleMenuItem *self);
static void default_secondary_clicked_handler (IdoScaleMenuItem *self);
typedef struct {
GtkWidget *scale;
GtkAdjustment *adjustment;
GtkWidget *primary_image;
GtkWidget *secondary_image;
GtkWidget *primary_label;
GtkWidget *secondary_label;
GtkWidget *hbox;
gboolean reverse_scroll;
gboolean grabbed;
IdoScaleMenuItemStyle style;
IdoRangeStyle range_style;
gboolean ignore_value_changed;
gboolean has_focus;
gboolean bCloseOnChange;
} IdoScaleMenuItemPrivate;
enum {
SLIDER_GRABBED,
SLIDER_RELEASED,
PRIMARY_CLICKED,
SECONDARY_CLICKED,
VALUE_CHANGED,
LAST_SIGNAL
};
enum {
PROP_0,
PROP_ADJUSTMENT,
PROP_REVERSE_SCROLL_EVENTS,
PROP_STYLE,
PROP_RANGE_STYLE
};
static guint signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (IdoScaleMenuItem, ido_scale_menu_item, GTK_TYPE_MENU_ITEM)
static gboolean
ido_scale_menu_item_scroll_event (GtkWidget *menuitem,
GdkEventScroll *event)
{
IdoScaleMenuItem *item = IDO_SCALE_MENU_ITEM (menuitem);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (item);
GtkWidget *scale = priv->scale;
if (priv->reverse_scroll)
{
switch (event->direction)
{
case GDK_SCROLL_UP:
event->direction = GDK_SCROLL_DOWN;
break;
case GDK_SCROLL_DOWN:
event->direction = GDK_SCROLL_UP;
break;
default:
break;
}
}
gtk_widget_event (scale,
((GdkEvent *)(void*)(event)));
return TRUE;
}
static void
ido_scale_menu_item_scale_value_changed (GtkRange *range,
gpointer user_data)
{
IdoScaleMenuItem *self = user_data;
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (self);
/* The signal is not sent when it was set through
* ido_scale_menu_item_set_value(). */
if (!priv->ignore_value_changed)
{
g_signal_emit (self, signals[VALUE_CHANGED], 0, gtk_range_get_value (range));
if (priv->bCloseOnChange)
{
GtkWidget *pParent = gtk_widget_get_parent (GTK_WIDGET (self));
gtk_menu_shell_deactivate (GTK_MENU_SHELL (pParent));
}
}
}
static void
ido_scale_menu_item_constructed (GObject *object)
{
IdoScaleMenuItem *self = IDO_SCALE_MENU_ITEM (object);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (self);
GObject *adj = G_OBJECT (gtk_adjustment_new (0.0, 0.0, 100.0, 1.0, 10.0, 0.0));
IdoRangeStyle range_style;
GtkWidget *hbox;
priv->adjustment = NULL;
g_object_get (self,
"range-style", &range_style,
NULL);
priv->scale = ido_range_new (adj, range_style);
g_signal_connect (priv->scale, "value-changed", G_CALLBACK (ido_scale_menu_item_scale_value_changed), self);
g_object_ref (priv->scale);
gtk_scale_set_draw_value (GTK_SCALE (priv->scale), FALSE);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
priv->primary_image = gtk_image_new ();
g_signal_connect (priv->primary_image, "notify",
G_CALLBACK (ido_scale_menu_item_primary_image_notify),
self);
priv->secondary_image = gtk_image_new ();
g_signal_connect (priv->secondary_image, "notify",
G_CALLBACK (ido_scale_menu_item_secondary_image_notify),
self);
priv->primary_label = gtk_label_new ("");
priv->secondary_label = gtk_label_new ("");
priv->hbox = hbox;
update_packing (self, priv->style);
gtk_container_add (GTK_CONTAINER (self), hbox);
gtk_widget_add_events (GTK_WIDGET(self), GDK_SCROLL_MASK);
}
static void
ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (item_class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (item_class);
GtkMenuItemClass *menuitem_class = GTK_MENU_ITEM_CLASS (item_class);
item_class->primary_clicked = default_primary_clicked_handler;
item_class->secondary_clicked = default_secondary_clicked_handler;
menuitem_class->select = ido_scale_menu_item_select;
menuitem_class->deselect = ido_scale_menu_item_deselect;
widget_class->button_press_event = ido_scale_menu_item_button_press_event;
widget_class->button_release_event = ido_scale_menu_item_button_release_event;
widget_class->motion_notify_event = ido_scale_menu_item_motion_notify_event;
widget_class->scroll_event = ido_scale_menu_item_scroll_event;
widget_class->parent_set = ido_scale_menu_item_parent_set;
gobject_class->constructed = ido_scale_menu_item_constructed;
gobject_class->set_property = ido_scale_menu_item_set_property;
gobject_class->get_property = ido_scale_menu_item_get_property;
g_object_class_install_property (gobject_class,
PROP_STYLE,
g_param_spec_enum ("accessory-style",
"Style of primary/secondary widgets",
"The style of the primary/secondary widgets",
IDO_TYPE_SCALE_MENU_ITEM_STYLE,
IDO_SCALE_MENU_ITEM_STYLE_NONE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_RANGE_STYLE,
g_param_spec_enum ("range-style",
"Range style",
"Style of the range",
IDO_TYPE_RANGE_STYLE,
IDO_RANGE_STYLE_DEFAULT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (gobject_class,
PROP_ADJUSTMENT,
g_param_spec_object ("adjustment",
"Adjustment",
"The adjustment containing the scale value",
GTK_TYPE_ADJUSTMENT,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_REVERSE_SCROLL_EVENTS,
g_param_spec_boolean ("reverse-scroll-events",
"Reverse scroll events",
"Reverses how up/down scroll events are interpreted",
TRUE,
G_PARAM_READWRITE));
/**
* IdoScaleMenuItem::slider-grabbed:
* @menuitem: The #IdoScaleMenuItem emitting the signal.
*
* The ::slider-grabbed signal is emitted when the pointer selects the slider.
*/
signals[SLIDER_GRABBED] = g_signal_new ("slider-grabbed",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* IdoScaleMenuItem::slider-released:
* @menuitem: The #IdoScaleMenuItem emitting the signal.
*
* The ::slider-released signal is emitted when the pointer releases the slider.
*/
signals[SLIDER_RELEASED] = g_signal_new ("slider-released",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* IdoScaleMenuItem::primary-clicked:
* @menuitem: The #IdoScaleMenuItem emitting the signal.
*
* The ::primary-clicked signal is emitted when the pointer clicks the primary label.
*/
signals[PRIMARY_CLICKED] = g_signal_new ("primary-clicked",
G_TYPE_FROM_CLASS (item_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
G_STRUCT_OFFSET (IdoScaleMenuItemClass, primary_clicked),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, /* return type */
0 /* n_params */);
/**
* IdoScaleMenuItem::secondary-clicked:
* @menuitem: The #IdoScaleMenuItem emitting the signal.
*
* The ::secondary-clicked signal is emitted when the pointer clicks the secondary label.
*/
signals[SECONDARY_CLICKED] = g_signal_new ("secondary-clicked",
G_TYPE_FROM_CLASS (item_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
G_STRUCT_OFFSET (IdoScaleMenuItemClass, secondary_clicked),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, /* return type */
0 /* n_params */);
/**
* IdoScaleMenuItem::value-changed:
* @menuitem: the #IdoScaleMenuItem for which the value changed
* @value: the new value
*
* Emitted whenever the value of the contained scale changes because
* of user input.
*/
signals[VALUE_CHANGED] = g_signal_new ("value-changed",
IDO_TYPE_SCALE_MENU_ITEM,
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
g_cclosure_marshal_VOID__DOUBLE,
G_TYPE_NONE,
1, G_TYPE_DOUBLE);
}
static void
update_packing (IdoScaleMenuItem *self, IdoScaleMenuItemStyle style)
{
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (self);
GtkBox * box = GTK_BOX (priv->hbox);
GtkContainer *container = GTK_CONTAINER (priv->hbox);
/* remove the old layout */
GList * children = gtk_container_get_children (container);
GList * l;
for (l=children; l!=NULL; l=l->next)
gtk_container_remove (container, l->data);
g_list_free (children);
/* add the new layout */
switch (style)
{
case IDO_SCALE_MENU_ITEM_STYLE_IMAGE:
gtk_box_pack_start (box, priv->primary_image, FALSE, FALSE, 0);
gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0);
gtk_box_pack_start (box, priv->secondary_image, FALSE, FALSE, 0);
break;
case IDO_SCALE_MENU_ITEM_STYLE_LABEL:
gtk_box_pack_start (box, priv->primary_label, FALSE, FALSE, 0);
gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0);
gtk_box_pack_start (box, priv->secondary_label, FALSE, FALSE, 0);
break;
default:
gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0);
break;
}
gtk_widget_show_all (priv->hbox);
}
static void
ido_scale_menu_item_init (IdoScaleMenuItem *self)
{
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (self);
priv->bCloseOnChange = FALSE;
priv->reverse_scroll = TRUE;
gtk_widget_set_size_request (GTK_WIDGET (self), 200, -1);
}
static void
ido_scale_menu_item_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdoScaleMenuItem *menu_item = IDO_SCALE_MENU_ITEM (object);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (menu_item);
switch (prop_id)
{
case PROP_ADJUSTMENT:
gtk_range_set_adjustment (GTK_RANGE (priv->scale), g_value_get_object (value));
break;
case PROP_REVERSE_SCROLL_EVENTS:
priv->reverse_scroll = g_value_get_boolean (value);
break;
case PROP_STYLE:
ido_scale_menu_item_set_style (menu_item, g_value_get_enum (value));
break;
case PROP_RANGE_STYLE:
priv->range_style = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ido_scale_menu_item_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdoScaleMenuItem *menu_item = IDO_SCALE_MENU_ITEM (object);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (menu_item);
GtkAdjustment *adjustment;
switch (prop_id)
{
case PROP_ADJUSTMENT:
adjustment = gtk_range_get_adjustment (GTK_RANGE (priv->scale));
g_value_set_object (value, adjustment);
break;
case PROP_REVERSE_SCROLL_EVENTS:
g_value_set_boolean (value, priv->reverse_scroll);
break;
case PROP_RANGE_STYLE:
g_value_set_enum (value, priv->range_style);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
ido_scale_menu_item_parent_key_press_event (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data)
{
IdoScaleMenuItem *menu_item = IDO_SCALE_MENU_ITEM (user_data);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (menu_item);
/* only listen to events when the playback menu item is selected */
if (!priv->has_focus)
return FALSE;
switch (event->keyval)
{
case GDK_KEY_Left:
case GDK_KEY_minus:
case GDK_KEY_KP_Subtract:
GTK_RANGE_GET_CLASS (priv->scale)->move_slider (GTK_RANGE (priv->scale), GTK_SCROLL_STEP_LEFT);
return TRUE;
case GDK_KEY_Right:
case GDK_KEY_plus:
case GDK_KEY_KP_Add:
GTK_RANGE_GET_CLASS (priv->scale)->move_slider (GTK_RANGE (priv->scale), GTK_SCROLL_STEP_RIGHT);
return TRUE;
}
return FALSE;
}
static void
ido_scale_menu_item_select (GtkMenuItem *item)
{
IdoScaleMenuItem *menu_item = IDO_SCALE_MENU_ITEM (item);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (menu_item);
priv->has_focus = TRUE;
gtk_widget_set_state_flags (priv->scale, GTK_STATE_FLAG_FOCUSED, FALSE);
}
static void
ido_scale_menu_item_deselect (GtkMenuItem *item)
{
IdoScaleMenuItem *menu_item = IDO_SCALE_MENU_ITEM (item);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (menu_item);
priv->has_focus = FALSE;
gtk_widget_unset_state_flags (priv->scale, GTK_STATE_FLAG_FOCUSED);
}
static gboolean
ido_scale_menu_item_button_press_event (GtkWidget *menuitem,
GdkEventButton *event)
{
IdoScaleMenuItem *item = IDO_SCALE_MENU_ITEM (menuitem);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (item);
GtkAllocation alloc;
gint x, y;
gtk_widget_get_allocation (priv->scale, &alloc);
gtk_widget_translate_coordinates (menuitem, priv->scale, event->x, event->y, &x, &y);
if (x > 0 && x < alloc.width && y > 0 && y < alloc.height)
gtk_widget_event (priv->scale, (GdkEvent *) event);
if (!priv->grabbed)
{
priv->grabbed = TRUE;
g_signal_emit (menuitem, signals[SLIDER_GRABBED], 0);
}
return TRUE;
}
static gboolean
ido_scale_menu_item_button_release_event (GtkWidget *menuitem,
GdkEventButton *event)
{
IdoScaleMenuItem *item = IDO_SCALE_MENU_ITEM (menuitem);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (item);
GtkWidget *scale = priv->scale;
GtkAllocation alloc;
gint x, y;
gtk_widget_get_allocation (priv->scale, &alloc);
gtk_widget_translate_coordinates (menuitem, priv->scale, event->x, event->y, &x, &y);
/* if user clicked to the left of the scale... */
if (x < 0)
{
if (gtk_widget_get_direction (menuitem) == GTK_TEXT_DIR_LTR)
{
ido_scale_menu_item_primary_clicked (item);
}
else
{
ido_scale_menu_item_secondary_clicked (item);
}
}
/* if user clicked to the right of the scale... */
else if (x > alloc.width)
{
if (gtk_widget_get_direction (menuitem) == GTK_TEXT_DIR_LTR)
{
ido_scale_menu_item_secondary_clicked (item);
}
else
{
ido_scale_menu_item_primary_clicked (item);
}
}
/* user clicked on the scale... */
else if (x > 0 && x < alloc.width && y > 0 && y < alloc.height)
{
gtk_widget_event (scale, (GdkEvent*) event);
}
if (priv->grabbed)
{
priv->grabbed = FALSE;
g_signal_emit (menuitem, signals[SLIDER_RELEASED], 0);
}
return TRUE;
}
static gboolean
ido_scale_menu_item_motion_notify_event (GtkWidget *menuitem,
GdkEventMotion *event)
{
IdoScaleMenuItem *menu_item = IDO_SCALE_MENU_ITEM (menuitem);
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (menu_item);
GtkAllocation alloc;
gint x, y;
gtk_widget_get_allocation (priv->scale, &alloc);
gtk_widget_translate_coordinates (menuitem, priv->scale, event->x, event->y, &x, &y);
/* don't translate coordinates when the scale has the "grab" -
* GtkRange expects coords relative to its event window in that case
*/
if (!priv->grabbed)
{
event->x = x;
event->y = y;
}
if (priv->grabbed ||
(x > 0 && x < alloc.width && y > 0 && y < alloc.height))
gtk_widget_event (priv->scale, (GdkEvent *) event);
return TRUE;
}
static void
menu_hidden (GtkWidget *menu,
IdoScaleMenuItem *scale)
{
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (scale);
if (priv->grabbed)
{
priv->grabbed = FALSE;
g_signal_emit (scale, signals[SLIDER_RELEASED], 0);
}
}
static void
ido_scale_menu_item_parent_set (GtkWidget *item,
GtkWidget *previous_parent)
{
GtkWidget *parent;
if (previous_parent)
{
g_signal_handlers_disconnect_by_func (previous_parent, menu_hidden, item);
g_signal_handlers_disconnect_by_func (previous_parent, ido_scale_menu_item_parent_key_press_event, item);
}
parent = gtk_widget_get_parent (item);
if (parent)
{
g_signal_connect (parent, "hide", G_CALLBACK (menu_hidden), item);
/* Menus don't pass key events to their children. This works around
* that by listening to key events on the parent widget. */
g_signal_connect (parent, "key-press-event",
G_CALLBACK (ido_scale_menu_item_parent_key_press_event), item);
}
}
static void
ido_scale_menu_item_primary_image_notify (GtkImage *image,
GParamSpec *pspec,
IdoScaleMenuItem *item)
{
if (gtk_image_get_storage_type (image) == GTK_IMAGE_EMPTY)
gtk_widget_hide (GTK_WIDGET (image));
else
gtk_widget_show (GTK_WIDGET (image));
}
static void
ido_scale_menu_item_secondary_image_notify (GtkImage *image,
GParamSpec *pspec,
IdoScaleMenuItem *item)
{
if (gtk_image_get_storage_type (image) == GTK_IMAGE_EMPTY)
gtk_widget_hide (GTK_WIDGET (image));
else
gtk_widget_show (GTK_WIDGET (image));
}
/**
* ido_scale_menu_item_new:
* @label: the text of the new menu item.
* @size: The size style of the range.
* @adjustment: A #GtkAdjustment describing the slider value.
*
* Creates a new #IdoScaleMenuItem with an empty label.
*
* Return Value: a new #IdoScaleMenuItem.
**/
GtkWidget*
ido_scale_menu_item_new (const gchar *label,
IdoRangeStyle range_style,
GtkAdjustment *adjustment)
{
return g_object_new (IDO_TYPE_SCALE_MENU_ITEM,
"adjustment", adjustment,
"range-style", range_style,
NULL);
}
/**
* ido_scale_menu_item_new_with_label:
* @label: the text of the menu item.
* @size: The size style of the range.
* @min: The minimum value of the slider.
* @max: The maximum value of the slider.
* @step: The step increment of the slider.
*
* Creates a new #IdoScaleMenuItem containing a label.
*
* Return Value: a new #IdoScaleMenuItem.
**/
GtkWidget*
ido_scale_menu_item_new_with_range (const gchar *label,
IdoRangeStyle range_style,
gdouble value,
gdouble min,
gdouble max,
gdouble step)
{
GObject *adjustment = G_OBJECT (gtk_adjustment_new (value, min, max, step, step, 0));
return GTK_WIDGET (g_object_new (IDO_TYPE_SCALE_MENU_ITEM,
"label", label,
"range-style", range_style,
"adjustment", adjustment,
NULL));
}
/**
* ido_scale_menu_item_get_scale:
* @menuitem: The #IdoScaleMenuItem
*
* Retrieves the scale widget.
*
* Return Value: (transfer none): The #IdoRange in this item
**/
GtkWidget*
ido_scale_menu_item_get_scale (IdoScaleMenuItem *menuitem)
{
IdoScaleMenuItemPrivate *priv;
g_return_val_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem), NULL);
priv = ido_scale_menu_item_get_instance_private (menuitem);
return priv->scale;
}
/**
* ido_scale_menu_item_get_style:
* @menuitem: The #IdoScaleMenuItem
*
* Retrieves the type of widgets being used for the primary and
* secondary widget slots. This could be images, labels, or nothing.
*
* Return Value: A #IdoScaleMenuItemStyle enum describing the style.
**/
IdoScaleMenuItemStyle
ido_scale_menu_item_get_style (IdoScaleMenuItem *menuitem)
{
IdoScaleMenuItemPrivate *priv;
g_return_val_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem), IDO_SCALE_MENU_ITEM_STYLE_NONE);
priv = ido_scale_menu_item_get_instance_private (menuitem);
return priv->style;
}
/**
* ido_scale_menu_item_set_style:
* @menuitem: The #IdoScaleMenuItem
* @style: Set the style use for the primary and secondary widget slots.
*
* Sets the type of widgets being used for the primary and
* secondary widget slots. This could be images, labels, or nothing.
**/
void
ido_scale_menu_item_set_style (IdoScaleMenuItem *menuitem,
IdoScaleMenuItemStyle style)
{
IdoScaleMenuItemPrivate *priv;
g_return_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem));
priv = ido_scale_menu_item_get_instance_private (menuitem);
priv->style = style;
update_packing (menuitem, style);
}
/**
* ido_scale_menu_item_get_primary_image:
* @menuitem: The #IdoScaleMenuItem
*
* Retrieves a pointer to the image widget used in the primary slot.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
*
* Return Value: (transfer none): A #GtkWidget pointer for the primary image.
**/
GtkWidget *
ido_scale_menu_item_get_primary_image (IdoScaleMenuItem *menuitem)
{
IdoScaleMenuItemPrivate *priv;
g_return_val_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem), NULL);
priv = ido_scale_menu_item_get_instance_private (menuitem);
return priv->primary_image;
}
/**
* ido_scale_menu_item_get_secondary_image:
* @menuitem: The #IdoScaleMenuItem
*
* Retrieves a pointer to the image widget used in the secondary slot.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
*
* Return Value: (transfer none): A #GtkWidget pointer for the secondary image.
**/
GtkWidget *
ido_scale_menu_item_get_secondary_image (IdoScaleMenuItem *menuitem)
{
IdoScaleMenuItemPrivate *priv;
g_return_val_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem), NULL);
priv = ido_scale_menu_item_get_instance_private (menuitem);
return priv->secondary_image;
}
/**
* ido_scale_menu_item_set_icons:
* @item: a #IdoScaleMenuItem
* @primary-icon: (allow-none): the primary icon, or %NULL
* @secondary-icon: (allow-none): the secondary icon, %NULL
*
* Sets the icons of @item to @primary_icon and @secondary_icon.
* Pass %NULL for either of them to unset the icon.
*/
static void
ido_scale_menu_item_set_icons (IdoScaleMenuItem *item,
GIcon *primary_icon,
GIcon *secondary_icon)
{
GtkWidget *primary;
GtkWidget *secondary;
primary = ido_scale_menu_item_get_primary_image (item);
secondary = ido_scale_menu_item_get_secondary_image (item);
if (primary_icon)
gtk_image_set_from_gicon (GTK_IMAGE (primary), primary_icon, GTK_ICON_SIZE_MENU);
else
gtk_image_clear (GTK_IMAGE (primary));
if (secondary_icon)
gtk_image_set_from_gicon (GTK_IMAGE (secondary), secondary_icon, GTK_ICON_SIZE_MENU);
else
gtk_image_clear (GTK_IMAGE (secondary));
}
/**
* ido_scale_menu_item_get_primary_label:
* @menuitem: The #IdoScaleMenuItem
*
* Retrieves a string of the text for the primary label widget.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
*
* Return Value: The label text.
**/
const gchar*
ido_scale_menu_item_get_primary_label (IdoScaleMenuItem *menuitem)
{
IdoScaleMenuItemPrivate *priv;
g_return_val_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem), NULL);
priv = ido_scale_menu_item_get_instance_private (menuitem);
return gtk_label_get_text (GTK_LABEL (priv->primary_label));
}
/**
* ido_scale_menu_item_get_secondary_label:
* @menuitem: The #IdoScaleMenuItem
*
* Retrieves a string of the text for the secondary label widget.
* Whether this is visible depends upon the return value from
* ido_scale_menu_item_get_style().
*
* Return Value: The label text.
**/
const gchar*
ido_scale_menu_item_get_secondary_label (IdoScaleMenuItem *menuitem)
{
IdoScaleMenuItemPrivate *priv;
g_return_val_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem), NULL);
priv = ido_scale_menu_item_get_instance_private (menuitem);
return gtk_label_get_text (GTK_LABEL (priv->secondary_label));
}
/**
* ido_scale_menu_item_set_primary_label:
* @menuitem: The #IdoScaleMenuItem
* @label: The label text
*
* Sets the text for the label widget in the primary slot. This
* widget will only be visibile if the return value of
* ido_scale_menu_item_get_style() is set to %IDO_SCALE_MENU_ITEM_STYLE_LABEL.
**/
void
ido_scale_menu_item_set_primary_label (IdoScaleMenuItem *menuitem,
const gchar *label)
{
IdoScaleMenuItemPrivate *priv;
g_return_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem));
priv = ido_scale_menu_item_get_instance_private (menuitem);
if (priv->primary_label)
{
gtk_label_set_text (GTK_LABEL (priv->primary_label), label);
}
}
/**
* ido_scale_menu_item_set_secondary_label:
* @menuitem: The #IdoScaleMenuItem
* @label: The label text
*
* Sets the text for the label widget in the secondary slot. This
* widget will only be visibile if the return value of
* ido_scale_menu_item_get_style() is set to %IDO_SCALE_MENU_ITEM_STYLE_LABEL.
**/
void
ido_scale_menu_item_set_secondary_label (IdoScaleMenuItem *menuitem,
const gchar *label)
{
IdoScaleMenuItemPrivate *priv;
g_return_if_fail (IDO_IS_SCALE_MENU_ITEM (menuitem));
priv = ido_scale_menu_item_get_instance_private (menuitem);
if (priv->secondary_label)
{
gtk_label_set_text (GTK_LABEL (priv->secondary_label), label);
}
}
/**
* ido_scale_menu_item_primary_clicked:
* @menuitem: the #IdoScaleMenuItem
*
* Emits the "primary-clicked" signal.
*
* The default handler for this signal lowers the scale's
* adjustment to its lower bound.
*/
void
ido_scale_menu_item_primary_clicked (IdoScaleMenuItem * menuitem)
{
g_signal_emit (menuitem, signals[PRIMARY_CLICKED], 0);
}
static void
default_primary_clicked_handler (IdoScaleMenuItem * item)
{
g_debug ("%s: setting scale to lower bound", G_STRFUNC);
IdoScaleMenuItemPrivate * priv = ido_scale_menu_item_get_instance_private (item);
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (priv->scale));
gtk_adjustment_set_value (adj, gtk_adjustment_get_lower (adj));
}
/**
* ido_scale_menu_item_secondary_clicked:
* @menuitem: the #IdoScaleMenuItem
*
* Emits the "secondary-clicked" signal.
*
* The default handler for this signal raises the scale's
* adjustment to its upper bound.
*/
void
ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem * menuitem)
{
g_signal_emit (menuitem, signals[SECONDARY_CLICKED], 0);
}
static void
default_secondary_clicked_handler (IdoScaleMenuItem * item)
{
g_debug ("%s: setting scale to upper bound", G_STRFUNC);
IdoScaleMenuItemPrivate * priv = ido_scale_menu_item_get_instance_private (item);
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (priv->scale));
gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj));
}
/* ido_scale_menu_item_set_value:
*
* Sets the value of the scale inside @item to @value, without emitting
* "value-changed".
*/
static void
ido_scale_menu_item_set_value (IdoScaleMenuItem *item,
gdouble value)
{
IdoScaleMenuItemPrivate *priv = ido_scale_menu_item_get_instance_private (item);
/* set ignore_value_changed to signify to the scale menu item that it
* should not emit its own value-changed signal, as that should only
* be emitted when the value is changed by the user. */
priv->ignore_value_changed = TRUE;
gtk_range_set_value (GTK_RANGE (priv->scale), value);
priv->ignore_value_changed = FALSE;
}
/**
* ido_scale_menu_item_state_changed:
*
* Updates a IdoScaleMenuItem from @state. State must be a double which
* contains the current value of the slider.
*/
static void
ido_scale_menu_item_state_changed (IdoActionHelper *helper,
GVariant *state,
gpointer user_data)
{
GtkWidget *menuitem;
menuitem = ido_action_helper_get_widget (helper);
ido_scale_menu_item_set_value (IDO_SCALE_MENU_ITEM (menuitem), g_variant_get_double (state));
}
static void
ido_scale_menu_item_value_changed (GtkScale *scale,
gdouble value,
gpointer user_data)
{
IdoActionHelper *helper = user_data;
ido_action_helper_change_action_state (helper, g_variant_new_double (value));
}
static GIcon *
menu_item_get_icon (GMenuItem *menuitem,
const gchar *attribute)
{
GVariant *value;
value = g_menu_item_get_attribute_value (menuitem, attribute, NULL);
return value ? g_icon_deserialize (value) : NULL;
}
static gchar* onFormatValue (GtkScale *pScale, gdouble fValue)
{
gint nValue = fValue * 100;
gchar *sValue = g_strdup_printf ("%i%%", nValue);
return sValue;
}
/**
* ido_scale_menu_item_new_from_model:
*
* Creates a new #IdoScaleMenuItem. If @menuitem contains an action, it
* will be bound to that action in @actions.
*
* Returns: (transfer full): a new #IdoScaleMenuItem
*/
GtkMenuItem *
ido_scale_menu_item_new_from_model (GMenuItem *menuitem,
GActionGroup *actions)
{
GtkWidget *item;
gchar *action;
gdouble min = 0.0;
gdouble max = 100.0;
gdouble step = 1.0;
GIcon *min_icon;
GIcon *max_icon;
g_menu_item_get_attribute (menuitem, "min-value", "d", &min);
g_menu_item_get_attribute (menuitem, "max-value", "d", &max);
g_menu_item_get_attribute (menuitem, "step", "d", &step);
item = ido_scale_menu_item_new_with_range ("Volume", IDO_RANGE_STYLE_DEFAULT, 0.0, min, max, step);
ido_scale_menu_item_set_style (IDO_SCALE_MENU_ITEM (item), IDO_SCALE_MENU_ITEM_STYLE_IMAGE);
if (g_menu_item_get_attribute (menuitem, "action", "s", &action))
{
IdoActionHelper *helper;
helper = ido_action_helper_new (item, actions, action, NULL);
g_signal_connect (helper, "action-state-changed",
G_CALLBACK (ido_scale_menu_item_state_changed), NULL);
g_signal_connect (item, "value-changed", G_CALLBACK (ido_scale_menu_item_value_changed), helper);
g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper);
g_free (action);
}
IdoScaleMenuItemPrivate *pPrivate = ido_scale_menu_item_get_instance_private (IDO_SCALE_MENU_ITEM (item));
guchar nDigits = 0;
gboolean bFound = g_menu_item_get_attribute (menuitem, "digits", "y", &nDigits);
if (bFound)
{
gtk_scale_set_digits (GTK_SCALE (pPrivate->scale), nDigits);
gtk_range_set_round_digits (GTK_RANGE (pPrivate->scale), nDigits);
}
gboolean bMarks = FALSE;
bFound = g_menu_item_get_attribute (menuitem, "marks", "b", &bMarks);
if (bFound)
{
gtk_scale_set_draw_value (GTK_SCALE (pPrivate->scale), TRUE);
for (gdouble fValue = min; fValue < (max + step); fValue += step)
{
gtk_scale_add_mark (GTK_SCALE (pPrivate->scale), round (fValue * 10) / 10, GTK_POS_BOTTOM, NULL);
}
g_signal_connect (pPrivate->scale, "format-value", G_CALLBACK (onFormatValue), NULL);
}
g_menu_item_get_attribute (menuitem, "close-on-change", "b", &pPrivate->bCloseOnChange);
min_icon = menu_item_get_icon (menuitem, "min-icon");
max_icon = menu_item_get_icon (menuitem, "max-icon");
ido_scale_menu_item_set_icons (IDO_SCALE_MENU_ITEM (item), min_icon, max_icon);
if (min_icon)
g_object_unref (min_icon);
if (max_icon)
g_object_unref (max_icon);
return GTK_MENU_ITEM (item);
}
#define __IDO_SCALE_MENU_ITEM_C__