/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright 2009 Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * Boston, MA 02111-1307, USA. * */ /** * SECTION:mx-gtk-light-switch * @short_description: a toggle switch between two states * * A visual representation of a toggle switch that can move between two states. */ #include #include "mx-gtk-light-switch.h" #include #if 0 /* We use the special gcc constructor attribute so we can avoid * requiring an init function to get translations to work! This * function is also in mx-utils but we also need it here * because that is a separate library */ static void __attribute__ ((constructor)) _start (void) { bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); } #endif G_DEFINE_TYPE (MxGtkLightSwitch, mx_gtk_light_switch, GTK_TYPE_DRAWING_AREA) #define MX_GTK_LIGHT_SWITCH_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), MX_GTK_TYPE_LIGHT_SWITCH, MxGtkLightSwitchPrivate)) static gboolean mx_gtk_light_switch_configure (GtkWidget *lightswitch, GdkEventConfigure *event); static gboolean mx_gtk_light_switch_expose (GtkWidget *lightswitch, GdkEventExpose *event); static gboolean mx_gtk_light_switch_button_release (GtkWidget *lightswitch, GdkEventButton *event); static gboolean mx_gtk_light_switch_button_press (GtkWidget *lightswitch, GdkEventButton *event); static gboolean mx_gtk_light_switch_motion_notify (GtkWidget *lightswitch, GdkEventMotion *event); static void mx_gtk_light_switch_size_request (GtkWidget *lightswitch, GtkRequisition *req); static void mx_gtk_light_switch_style_set (GtkWidget *lightswitch, GtkStyle *previous_style); enum { SWITCH_FLIPPED, LAST_SIGNAL }; static guint mx_gtk_light_switch_signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, PROP_ACTIVE, }; typedef struct _MxGtkLightSwitchPrivate MxGtkLightSwitchPrivate; struct _MxGtkLightSwitchPrivate { gboolean active; /* boolean state of switch */ gboolean dragging; /* true if dragging switch */ gint x; /* the x position of the switch */ gint drag_start; /* position dragging started at */ gint drag_threshold; gint switch_width; gint switch_height; gint trough_width; gint offset; /* offset of the mouse to slider when dragging */ }; #define ON_STRING _("On") #define OFF_STRING _("Off") #define UNAVAILABLE_STRING _("Unavailable") static void mx_gtk_light_switch_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MxGtkLightSwitch *ls; ls = MX_GTK_LIGHT_SWITCH (object); switch (prop_id) { case PROP_ACTIVE: mx_gtk_light_switch_set_active (ls, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void mx_gtk_light_switch_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MxGtkLightSwitchPrivate *priv; priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (object); switch (prop_id) { case PROP_ACTIVE: g_value_set_boolean (value, priv->active); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void mx_gtk_light_switch_class_init (MxGtkLightSwitchClass *klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; GParamSpec *spec; object_class = G_OBJECT_CLASS (klass); widget_class = GTK_WIDGET_CLASS (klass); object_class->set_property = mx_gtk_light_switch_set_property; object_class->get_property = mx_gtk_light_switch_get_property; widget_class->configure_event = mx_gtk_light_switch_configure; widget_class->expose_event = mx_gtk_light_switch_expose; widget_class->button_release_event = mx_gtk_light_switch_button_release; widget_class->button_press_event = mx_gtk_light_switch_button_press; widget_class->motion_notify_event = mx_gtk_light_switch_motion_notify; widget_class->size_request = mx_gtk_light_switch_size_request; widget_class->style_set = mx_gtk_light_switch_style_set; spec = g_param_spec_boolean ("active", "Active", "Is the light switch on or not", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_ACTIVE, spec); /* MxGtkLightSwitch signals */ mx_gtk_light_switch_signals[SWITCH_FLIPPED] = g_signal_new ("switch-flipped", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MxGtkLightSwitchClass, switch_flipped), NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); g_type_class_add_private (klass, sizeof (MxGtkLightSwitchPrivate)); } static void mx_gtk_light_switch_init (MxGtkLightSwitch *self) { MxGtkLightSwitchPrivate *priv; priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (self); priv->active = FALSE; priv->x = 0; /* add events, do initial draw/update, etc */ gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); } static void draw (GtkWidget *lightswitch, cairo_t *cr) { MxGtkLightSwitchPrivate *priv; gint on_label_x; gint off_label_x; gint label_width; gint label_height; GtkStyle *style; PangoLayout *layout; PangoContext *context; GtkStateType state_type; priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); style = lightswitch->style; #if GTK_CHECK_VERSION (2,19,5) state_type = gtk_widget_get_state (lightswitch); #else state_type = GTK_WIDGET_STATE (lightswitch); #endif on_label_x = (priv->trough_width / 5) * 0.75; off_label_x = (priv->trough_width / 8) * 5; /* draw the trough */ gtk_paint_box (style, lightswitch->window, (state_type != GTK_STATE_INSENSITIVE && priv->active) ? GTK_STATE_SELECTED : state_type, GTK_SHADOW_IN, NULL, NULL, "light-switch-trough", 0, 0, (priv->trough_width), priv->switch_height); #if 0 if (state_type == GTK_STATE_INSENSITIVE) return; #else if (state_type == GTK_STATE_INSENSITIVE) { context = gdk_pango_context_get (); layout = pango_layout_new (context); g_object_unref (context); pango_layout_set_font_description (layout, style->font_desc); pango_layout_set_text (layout, UNAVAILABLE_STRING, -1); pango_layout_get_size (layout, &label_width, &label_height); gtk_paint_layout (style, lightswitch->window, state_type, FALSE, NULL, lightswitch, "lighswitch-label", (priv->trough_width - (label_width / PANGO_SCALE)) / 2, (priv->switch_height - (label_height / PANGO_SCALE)) / 2, layout); g_object_unref (layout); return; } /* Draw the first label; "On" */ context = gdk_pango_context_get (); layout = pango_layout_new (context); g_object_unref (context); pango_layout_set_font_description (layout, style->font_desc); pango_layout_set_text (layout, ON_STRING, -1); pango_layout_get_size (layout, &label_width, &label_height); gtk_paint_layout (style, lightswitch->window, (priv->active) ? GTK_STATE_SELECTED : GTK_STATE_NORMAL, FALSE, NULL, (GtkWidget*) lightswitch, "lightswitch-label", on_label_x, (priv->switch_height - (label_height / PANGO_SCALE)) / 2, layout); pango_layout_set_text (layout, OFF_STRING, -1); pango_layout_get_size (layout, &label_width, &label_height); gtk_paint_layout (style, lightswitch->window, (priv->active) ? GTK_STATE_SELECTED : GTK_STATE_NORMAL, FALSE, NULL, (GtkWidget*) lightswitch, "lightswitch-label", off_label_x, (priv->switch_height - (label_height / PANGO_SCALE)) / 2, layout); g_object_unref (layout); #endif /* draw the switch itself */ gtk_paint_box (style, lightswitch->window, #if GTK_CHECK_VERSION (2,19,5) gtk_widget_get_state (lightswitch), #else GTK_WIDGET_STATE (lightswitch), #endif GTK_SHADOW_OUT, NULL, NULL, "light-switch-handle", priv->x + style->xthickness, style->ythickness, priv->switch_width - style->xthickness * 2, priv->switch_height - style->ythickness * 2); } static void mx_gtk_light_switch_size_request (GtkWidget *lightswitch, GtkRequisition *req) { MxGtkLightSwitchPrivate *priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); req->height = priv->switch_height; req->width = priv->trough_width; } static void mx_gtk_light_switch_style_set (GtkWidget *lightswitch, GtkStyle *previous_style) { MxGtkLightSwitchPrivate *priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); PangoLayout *layout; gint label_width, label_height; layout = gtk_widget_create_pango_layout (GTK_WIDGET (lightswitch), NULL); pango_layout_set_text (layout, UNAVAILABLE_STRING, -1); pango_layout_get_pixel_size (layout, &label_width, &label_height); g_object_unref (layout); /* MxToggle is 105x39, so make sure light-switch is at least this size */ priv->trough_width = MAX (103, label_width); priv->switch_width = (priv->trough_width / 2) * 1.1; //priv->switch_height = MAX (39, label_height); priv->switch_height = 24; priv->switch_width = 50; priv->trough_width = 98; } static gboolean mx_gtk_light_switch_configure (GtkWidget *lightswitch, GdkEventConfigure *event) { MxGtkLightSwitchPrivate *priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); if (priv->active) priv->x = priv->trough_width - priv->switch_width; else priv->x = 0; return FALSE; } static gboolean mx_gtk_light_switch_expose (GtkWidget *lightswitch, GdkEventExpose *event) { cairo_t *cr; cr = gdk_cairo_create (lightswitch->window); cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_clip (cr); draw (lightswitch, cr); cairo_destroy (cr); return FALSE; } static gboolean mx_gtk_light_switch_motion_notify (GtkWidget *lightswitch, GdkEventMotion *event) { MxGtkLightSwitchPrivate *priv; priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); if (ABS (event->x - priv->drag_start) < priv->drag_threshold) return TRUE; if (event->state & GDK_BUTTON1_MASK) { gint position = event->x - priv->offset; if (position > (priv->trough_width - priv->switch_width)) priv->x = (priv->trough_width - priv->switch_width); else if (position < 0) priv->x = 0; else priv->x = position; priv->dragging = TRUE; gtk_widget_queue_draw ((GtkWidget *) lightswitch); } return TRUE; } /** * mx_gtk_light_switch_get_active: * @lightswitch: A #MxGtkLightSwitch * * Get the value of the "active" property * * Returns: #TRUE if the switch is "on" */ gboolean mx_gtk_light_switch_get_active (MxGtkLightSwitch *lightswitch) { MxGtkLightSwitchPrivate *priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); return priv->active; } /** * mx_gtk_light_switch_set_active: * @lightswitch: A #MxGtkLightSwitch * @active: #TRUE to set the switch to its ON state * * Set the value of the "active" property * */ void mx_gtk_light_switch_set_active (MxGtkLightSwitch *lightswitch, gboolean active) { MxGtkLightSwitchPrivate *priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); if (priv->active == active) { return; } else { priv->active = active; if (active == TRUE) { priv->x = priv->trough_width - priv->switch_width; } else { priv->x = 0; } gtk_widget_queue_draw ((GtkWidget *) lightswitch); g_object_notify (G_OBJECT (lightswitch), "active"); g_signal_emit (lightswitch, mx_gtk_light_switch_signals[SWITCH_FLIPPED], 0, priv->active); } } static gboolean mx_gtk_light_switch_button_press (GtkWidget *lightswitch, GdkEventButton *event) { MxGtkLightSwitchPrivate *priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); if (priv->active) priv->offset = event->x - (priv->trough_width - priv->switch_width); else priv->offset = event->x; priv->drag_start = event->x; g_object_get (gtk_widget_get_settings (lightswitch), "gtk-dnd-drag-threshold", &priv->drag_threshold, NULL); return FALSE; } static gboolean mx_gtk_light_switch_button_release (GtkWidget *lightswitch, GdkEventButton *event) { MxGtkLightSwitchPrivate *priv; priv = MX_GTK_LIGHT_SWITCH_GET_PRIVATE (lightswitch); /* detect whereabouts we are and "drop" into a state */ if (priv->dragging) { priv->dragging = FALSE; if (priv->x + (priv->switch_width / 2) > priv->trough_width / 2) { mx_gtk_light_switch_set_active ((MxGtkLightSwitch *) lightswitch, TRUE); priv->x = priv->trough_width - priv->switch_width; } else { mx_gtk_light_switch_set_active ((MxGtkLightSwitch *) lightswitch, FALSE); priv->x = 0; } /* we always need to queue a redraw after dragging to put the slider back * in the correct place */ gtk_widget_queue_draw ((GtkWidget *) lightswitch); } else { mx_gtk_light_switch_set_active ((MxGtkLightSwitch *) lightswitch, !priv->active); } return FALSE; } /** * mx_gtk_light_switch_new: * * Create a #MxGtkLightSwitch * * Returns: a newly allocated #MxGtkLightSwitch */ GtkWidget* mx_gtk_light_switch_new (void) { return g_object_new (MX_GTK_TYPE_LIGHT_SWITCH, NULL); }