/* * 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__