/* * Borrowed from Moblin-Web-Browser: The web browser for Moblin * Copyright (c) 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "e-mail-tab.h" #define E_MAIL_TAB_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_TAB, EMailTabPrivate)) #define E_MAIL_PIXBOUND(u) ((gfloat)((gint)(u))) static void mx_draggable_iface_init (MxDraggableIface *iface); G_DEFINE_TYPE_WITH_CODE ( EMailTab, e_mail_tab, MX_TYPE_WIDGET, G_IMPLEMENT_INTERFACE ( MX_TYPE_DRAGGABLE, mx_draggable_iface_init)) enum { PROP_0, PROP_ICON, PROP_TEXT, PROP_CAN_CLOSE, PROP_TAB_WIDTH, PROP_DOCKING, PROP_PREVIEW, PROP_PREVIEW_MODE, PROP_PREVIEW_DURATION, PROP_SPACING, PROP_PRIVATE, PROP_ACTIVE, PROP_DRAG_THRESHOLD, PROP_DRAG_AXIS, PROP_DRAG_CONTAINMENT_AREA, PROP_DRAG_ENABLED, PROP_DRAG_ACTOR, }; enum { CLICKED, CLOSED, TRANSITION_COMPLETE, LAST_SIGNAL }; /* Animation stage lengths */ #define TAB_S1_ANIM 0.75 #define TAB_S2_ANIM (1.0-TAB_S1_ANIM) static guint signals[LAST_SIGNAL] = { 0, }; static void e_mail_tab_close_clicked_cb (MxButton *button, EMailTab *self); struct _EMailTabPrivate { ClutterActor *icon; ClutterActor *default_icon; ClutterActor *label; ClutterActor *close_button; gboolean can_close; gint width; gboolean docking; gfloat spacing; gboolean private; guint alert_count; guint alert_source; gboolean has_text; guint active : 1; guint pressed : 1; guint hover : 1; ClutterActor *preview; gboolean preview_mode; ClutterTimeline *preview_timeline; gdouble preview_height_progress; guint anim_length; ClutterActor *old_bg; ClutterActor *drag_actor; ClutterActorBox drag_area; gboolean drag_enabled; MxDragAxis drag_axis; gint drag_threshold; gulong drag_threshold_handler; gfloat press_x; gfloat press_y; gboolean in_drag; }; static void e_mail_tab_drag_begin (MxDraggable *draggable, gfloat event_x, gfloat event_y, gint event_button, ClutterModifierType modifiers) { EMailTabPrivate *priv; ClutterActor *self = CLUTTER_ACTOR (draggable); ClutterActor *actor = mx_draggable_get_drag_actor (draggable); ClutterActor *stage = clutter_actor_get_stage (self); gfloat x, y; priv = E_MAIL_TAB_GET_PRIVATE (draggable); priv->in_drag = TRUE; clutter_actor_get_transformed_position (self, &x, &y); clutter_actor_set_position (actor, x, y); /* Start up animation */ if (CLUTTER_IS_TEXTURE (actor)) { /* TODO: Some neat deformation effect? */ } else { /* Fade in */ clutter_actor_set_opacity (actor, 0x00); clutter_actor_animate ( actor, CLUTTER_LINEAR, 150, "opacity", 0xff, NULL); } clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor); } static void e_mail_tab_drag_motion (MxDraggable *draggable, gfloat delta_x, gfloat delta_y) { ClutterActor *actor = mx_draggable_get_drag_actor (draggable); clutter_actor_move_by (actor, delta_x, delta_y); } static void e_mail_tab_drag_end_anim_cb (ClutterAnimation *animation, EMailTab *tab) { ClutterActor *actor = CLUTTER_ACTOR (clutter_animation_get_object (animation)); ClutterActor *parent = clutter_actor_get_parent (actor); if (parent) clutter_container_remove_actor ( CLUTTER_CONTAINER (parent), actor); } static void e_mail_tab_drag_end (MxDraggable *draggable, gfloat event_x, gfloat event_y) { EMailTab *self = E_MAIL_TAB (draggable); EMailTabPrivate *priv = self->priv; priv->in_drag = FALSE; if (priv->drag_actor) { ClutterActor *parent = clutter_actor_get_parent (priv->drag_actor); if (parent) { /* Animate drop */ if (CLUTTER_IS_TEXTURE (priv->drag_actor)) { /* TODO: Some neat deformation effect? */ clutter_container_remove_actor ( CLUTTER_CONTAINER (parent), priv->drag_actor); } else { clutter_actor_animate ( priv->drag_actor, CLUTTER_LINEAR, 150, "opacity", 0x00, "signal::completed", G_CALLBACK (e_mail_tab_drag_end_anim_cb), self, NULL); } } g_object_unref (priv->drag_actor); priv->drag_actor = NULL; } } static void mx_draggable_iface_init (MxDraggableIface *iface) { iface->drag_begin = e_mail_tab_drag_begin; iface->drag_motion = e_mail_tab_drag_motion; iface->drag_end = e_mail_tab_drag_end; } static void e_mail_tab_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EMailTab *tab = E_MAIL_TAB (object); EMailTabPrivate *priv = tab->priv; switch (property_id) { case PROP_ICON: g_value_set_object (value, e_mail_tab_get_icon (tab)); break; case PROP_TEXT: g_value_set_string (value, e_mail_tab_get_text (tab)); break; case PROP_CAN_CLOSE: g_value_set_boolean (value, e_mail_tab_get_can_close (tab)); break; case PROP_TAB_WIDTH: g_value_set_int (value, e_mail_tab_get_width (tab)); break; case PROP_DOCKING: g_value_set_boolean (value, e_mail_tab_get_docking (tab)); break; case PROP_PREVIEW: g_value_set_object (value, e_mail_tab_get_preview_actor (tab)); break; case PROP_PREVIEW_MODE: g_value_set_boolean (value, e_mail_tab_get_preview_mode (tab)); break; case PROP_PREVIEW_DURATION: g_value_set_uint (value, e_mail_tab_get_preview_duration (tab)); break; case PROP_SPACING: g_value_set_float (value, e_mail_tab_get_spacing (tab)); break; case PROP_PRIVATE: g_value_set_boolean (value, e_mail_tab_get_private (tab)); break; case PROP_ACTIVE: g_value_set_boolean (value, e_mail_tab_get_active (tab)); break; case PROP_DRAG_THRESHOLD: g_value_set_uint (value, (guint) priv->drag_threshold); break; case PROP_DRAG_AXIS: g_value_set_enum (value, priv->drag_axis); break; case PROP_DRAG_CONTAINMENT_AREA: g_value_set_boxed (value, &priv->drag_area); break; case PROP_DRAG_ENABLED: g_value_set_boolean (value, priv->drag_enabled); break; case PROP_DRAG_ACTOR: if (!priv->drag_actor) priv->drag_actor = g_object_ref_sink ( clutter_clone_new (CLUTTER_ACTOR (tab))); g_value_set_object (value, priv->drag_actor); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void e_mail_tab_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { EMailTab *tab = E_MAIL_TAB (object); EMailTabPrivate *priv = tab->priv; switch (property_id) { case PROP_ICON: e_mail_tab_set_icon ( tab, g_value_get_object (value)); break; case PROP_TEXT: e_mail_tab_set_text ( tab, g_value_get_string (value)); break; case PROP_CAN_CLOSE: e_mail_tab_set_can_close ( tab, g_value_get_boolean (value)); case PROP_TAB_WIDTH: e_mail_tab_set_width ( tab, g_value_get_int (value)); break; case PROP_DOCKING: e_mail_tab_set_docking ( tab, g_value_get_boolean (value)); break; case PROP_PREVIEW: e_mail_tab_set_preview_actor ( tab, g_value_get_object (value)); break; case PROP_PREVIEW_MODE: e_mail_tab_set_preview_mode ( tab, g_value_get_boolean (value)); break; case PROP_PREVIEW_DURATION: e_mail_tab_set_preview_duration ( tab, g_value_get_uint (value)); break; case PROP_SPACING: e_mail_tab_set_spacing ( tab, g_value_get_float (value)); break; case PROP_PRIVATE: e_mail_tab_set_private ( tab, g_value_get_boolean (value)); break; case PROP_ACTIVE: e_mail_tab_set_active ( tab, g_value_get_boolean (value)); break; case PROP_DRAG_THRESHOLD: break; case PROP_DRAG_AXIS: priv->drag_axis = g_value_get_enum (value); break; case PROP_DRAG_CONTAINMENT_AREA: { ClutterActorBox *box = g_value_get_boxed (value); if (box) priv->drag_area = *box; else memset ( &priv->drag_area, 0, sizeof (ClutterActorBox)); break; } case PROP_DRAG_ENABLED: priv->drag_enabled = g_value_get_boolean (value); break; case PROP_DRAG_ACTOR: { ClutterActor *new_actor = g_value_get_object (value); if (priv->drag_actor) { ClutterActor *parent; parent = clutter_actor_get_parent (priv->drag_actor); /* We know it's a container because we added it ourselves */ if (parent) clutter_container_remove_actor ( CLUTTER_CONTAINER (parent), priv->drag_actor); g_object_unref (priv->drag_actor); priv->drag_actor = NULL; } if (new_actor) priv->drag_actor = g_object_ref_sink (new_actor); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void e_mail_tab_dispose_old_bg (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; if (priv->old_bg) { ClutterActor *parent; parent = clutter_actor_get_parent (priv->old_bg); if (parent == (ClutterActor *) tab) clutter_actor_unparent (priv->old_bg); g_object_unref (priv->old_bg); priv->old_bg = NULL; } } static void e_mail_tab_dispose (GObject *object) { EMailTab *tab = E_MAIL_TAB (object); EMailTabPrivate *priv = tab->priv; e_mail_tab_dispose_old_bg (tab); if (priv->icon) { clutter_actor_unparent (priv->icon); priv->icon = NULL; } if (priv->default_icon) { g_object_unref (priv->default_icon); priv->default_icon = NULL; } if (priv->label) { clutter_actor_unparent (CLUTTER_ACTOR (priv->label)); priv->label = NULL; } if (priv->close_button) { clutter_actor_unparent (CLUTTER_ACTOR (priv->close_button)); priv->close_button = NULL; } if (priv->preview) { clutter_actor_unparent (priv->preview); priv->preview = NULL; } if (priv->alert_source) { g_source_remove (priv->alert_source); priv->alert_source = 0; } if (priv->drag_actor) { ClutterActor *parent; parent = clutter_actor_get_parent (priv->drag_actor); if (parent) clutter_container_remove_actor ( CLUTTER_CONTAINER (parent), priv->drag_actor); g_object_unref (priv->drag_actor); priv->drag_actor = NULL; } if (priv->drag_threshold_handler) g_signal_handler_disconnect ( gtk_settings_get_default (), priv->drag_threshold_handler); priv->drag_threshold_handler = 0; G_OBJECT_CLASS (e_mail_tab_parent_class)->dispose (object); } static void e_mail_tab_get_preferred_width (ClutterActor *actor, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { EMailTabPrivate *priv; MxPadding padding; priv = E_MAIL_TAB_GET_PRIVATE (actor); /* Get padding */ mx_widget_get_padding (MX_WIDGET (actor), &padding); if (min_width_p) *min_width_p = padding.left + padding.right; if (natural_width_p) *natural_width_p = padding.left + padding.right; if (priv->width >= 0) { if (natural_width_p) *natural_width_p += priv->width; } else { gfloat min_width, nat_width, acc_min_width, acc_nat_width; acc_min_width = acc_nat_width = 0; if (priv->has_text) clutter_actor_get_preferred_size ( CLUTTER_ACTOR (priv->label), &acc_min_width, NULL, &acc_nat_width, NULL); if (priv->icon) clutter_actor_get_preferred_size ( priv->icon, &min_width, NULL, &nat_width, NULL); acc_min_width += min_width; acc_nat_width += nat_width; if (priv->can_close) clutter_actor_get_preferred_size ( CLUTTER_ACTOR (priv->close_button), &min_width, NULL, &nat_width, NULL); acc_min_width += min_width; acc_nat_width += nat_width; if (priv->preview && priv->preview_mode) { clutter_actor_get_preferred_size ( priv->preview, &min_width, NULL, &nat_width, NULL); if (min_width > acc_min_width) acc_min_width = min_width; if (nat_width > acc_nat_width) acc_nat_width = nat_width; } if (min_width_p) *min_width_p += acc_min_width; if (natural_width_p) *natural_width_p += acc_nat_width; } } void e_mail_tab_get_height_no_preview (EMailTab *tab, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { MxPadding padding; gfloat min_height, nat_height, tmp_min_height, tmp_nat_height; ClutterActor *actor = CLUTTER_ACTOR (tab); EMailTabPrivate *priv = tab->priv; /* Get padding */ mx_widget_get_padding (MX_WIDGET (actor), &padding); if (min_height_p) *min_height_p = padding.top + padding.bottom; if (natural_height_p) *natural_height_p = padding.top + padding.bottom; min_height = nat_height = 0; if (priv->has_text) clutter_actor_get_preferred_height ( CLUTTER_ACTOR (priv->label), -1, &min_height, &nat_height); if (priv->icon) { clutter_actor_get_preferred_height ( priv->icon, -1, &tmp_min_height, &tmp_nat_height); if (tmp_min_height > min_height) min_height = tmp_min_height; if (tmp_nat_height > nat_height) nat_height = tmp_nat_height; } if (priv->can_close) { clutter_actor_get_preferred_height ( CLUTTER_ACTOR (priv->close_button), -1, &tmp_min_height, &tmp_nat_height); if (tmp_min_height > min_height) min_height = tmp_min_height; if (tmp_nat_height > nat_height) nat_height = tmp_nat_height; } if (min_height_p) *min_height_p += min_height; if (natural_height_p) *natural_height_p += nat_height; } static void e_mail_tab_get_preferred_height (ClutterActor *actor, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { EMailTab *tab = E_MAIL_TAB (actor); EMailTabPrivate *priv = tab->priv; e_mail_tab_get_height_no_preview ( tab, for_width, min_height_p, natural_height_p); if (priv->preview) { MxPadding padding; gfloat min_height, nat_height; gfloat label_min_height, label_nat_height; /* Get preview + padding height */ mx_widget_get_padding (MX_WIDGET (actor), &padding); clutter_actor_get_preferred_height ( priv->preview, (gfloat) priv->width, &min_height, &nat_height); /* Add label height */ clutter_actor_get_preferred_height ( CLUTTER_ACTOR (priv->label), -1, &label_min_height, &label_nat_height); min_height = (min_height * priv->preview_height_progress) + padding.top + padding.bottom + priv->spacing + label_min_height; nat_height = (nat_height * priv->preview_height_progress) + padding.top + padding.bottom + priv->spacing + label_nat_height; /* Sometimes the preview's natural height will be nan due to * keeping of the aspect ratio. This guards against that and * stops Clutter from warning that the natural height is less * than the minimum height. */ if (isnan (nat_height)) nat_height = min_height; if (min_height_p && (min_height > *min_height_p)) *min_height_p = min_height; if (natural_height_p && (nat_height > *natural_height_p)) *natural_height_p = nat_height; } } static void e_mail_tab_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { EMailTabPrivate *priv; MxPadding padding; ClutterActorBox child_box; gfloat icon_width, icon_height; gfloat label_width, label_height; gfloat close_width, close_height; gfloat preview_width, preview_height; priv = E_MAIL_TAB_GET_PRIVATE (actor); /* Chain up to store box */ CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->allocate (actor, box, flags); /* Possibly synchronise an axis if we're dragging */ if (priv->in_drag) { ClutterActor *drag_actor = mx_draggable_get_drag_actor (MX_DRAGGABLE (actor)); if (drag_actor) { gfloat x, y; clutter_actor_get_transformed_position (actor, &x, &y); switch (mx_draggable_get_axis (MX_DRAGGABLE (actor))) { case MX_DRAG_AXIS_X : /* Synchronise y axis */ clutter_actor_set_y (drag_actor, y); break; case MX_DRAG_AXIS_Y : /* Synchronise x axis */ clutter_actor_set_x (drag_actor, x); break; default: break; } } } /* Allocate old background texture */ if (priv->old_bg) { child_box.x1 = 0; child_box.y1 = 0; child_box.x2 = box->x2 - box->x1; child_box.y2 = box->y2 - box->y1; clutter_actor_allocate (priv->old_bg, &child_box, flags); } mx_widget_get_padding (MX_WIDGET (actor), &padding); /* Get the preferred width/height of the icon, * label and close-button first. */ if (priv->icon) clutter_actor_get_preferred_size ( priv->icon, NULL, NULL, &icon_width, &icon_height); clutter_actor_get_preferred_size ( CLUTTER_ACTOR (priv->label), NULL, NULL, &label_width, &label_height); if (priv->can_close) clutter_actor_get_preferred_size ( CLUTTER_ACTOR (priv->close_button), NULL, NULL, &close_width, &close_height); /* Allocate for icon */ if (priv->icon) { child_box.x1 = padding.left; child_box.x2 = child_box.x1 + icon_width; child_box.y1 = E_MAIL_PIXBOUND ((box->y2 - box->y1) / 2 - icon_height / 2); child_box.y2 = child_box.y1 + icon_height; clutter_actor_allocate (priv->icon, &child_box, flags); } /* Allocate for close button */ if (priv->can_close) { child_box.x2 = box->x2 - box->x1 - padding.right; child_box.x1 = child_box.x2 - close_width; child_box.y1 = E_MAIL_PIXBOUND ((box->y2 - box->y1) / 2 - close_height / 2); child_box.y2 = child_box.y1 + close_height; clutter_actor_allocate ( CLUTTER_ACTOR (priv->close_button), &child_box, flags); } /* Allocate for preview widget */ preview_height = 0; if (priv->preview) { preview_width = (box->x2 - box->x1 - padding.left - padding.right); preview_height = (box->y2 - box->y1 - padding.top - padding.bottom - priv->spacing - label_height); child_box.x1 = E_MAIL_PIXBOUND (padding.left); child_box.y1 = E_MAIL_PIXBOUND (padding.top); child_box.x2 = child_box.x1 + preview_width; child_box.y2 = child_box.y1 + preview_height; clutter_actor_allocate (priv->preview, &child_box, flags); } /* Allocate for label */ if ((priv->preview_height_progress <= TAB_S1_ANIM) || (!priv->preview)) { if (priv->icon) child_box.x1 = E_MAIL_PIXBOUND ( padding.left + icon_width + priv->spacing); else child_box.x1 = E_MAIL_PIXBOUND (padding.left); child_box.x2 = (box->x2 - box->x1 - padding.right); child_box.y1 = E_MAIL_PIXBOUND ( (box->y2 - box->y1) / 2 - label_height / 2); child_box.y2 = child_box.y1 + label_height; /* If close button is visible, don't overlap it */ if (priv->can_close) child_box.x2 -= close_width + priv->spacing; } else { /* Put label underneath preview */ child_box.x1 = E_MAIL_PIXBOUND (padding.left); child_box.x2 = (box->x2 - box->x1 - padding.right); child_box.y1 = E_MAIL_PIXBOUND ( padding.top + preview_height + priv->spacing); child_box.y2 = child_box.y1 + label_height; } clutter_actor_allocate (CLUTTER_ACTOR (priv->label), &child_box, flags); /* If we're in preview mode, re-allocate the background so it doesn't * encompass the label. (A bit hacky?) */ if (priv->preview && CLUTTER_ACTOR_IS_VISIBLE (priv->preview)) { gfloat max_height = padding.top + padding.bottom + preview_height; if (box->y2 - box->y1 > max_height) { MxWidget *widget = MX_WIDGET (actor); ClutterActor *background = mx_widget_get_border_image (widget); if (!background) background = mx_widget_get_background_image (widget); child_box.x1 = 0; child_box.x2 = box->x2 - box->x1; child_box.y1 = 0; child_box.y2 = max_height; if (background) clutter_actor_allocate ( background, &child_box, flags); if (priv->old_bg && (priv->old_bg != background)) clutter_actor_allocate ( priv->old_bg, &child_box, flags); } } } static void e_mail_tab_paint (ClutterActor *actor) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (actor); /* Chain up to paint background */ CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->paint (actor); if (priv->old_bg) clutter_actor_paint (priv->old_bg); if (priv->icon) clutter_actor_paint (priv->icon); clutter_actor_paint (CLUTTER_ACTOR (priv->label)); if (priv->can_close) clutter_actor_paint (CLUTTER_ACTOR (priv->close_button)); if (priv->preview) clutter_actor_paint (CLUTTER_ACTOR (priv->preview)); } static void e_mail_tab_pick (ClutterActor *actor, const ClutterColor *c) { CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->pick (actor, c); if (clutter_actor_should_pick_paint (actor)) e_mail_tab_paint (actor); } static void e_mail_tab_map (ClutterActor *actor) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (actor); CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->map (actor); clutter_actor_map (CLUTTER_ACTOR (priv->label)); clutter_actor_map (CLUTTER_ACTOR (priv->close_button)); if (priv->icon) clutter_actor_map (priv->icon); if (priv->preview) clutter_actor_map (priv->preview); if (priv->old_bg) clutter_actor_map (priv->old_bg); } static void e_mail_tab_unmap (ClutterActor *actor) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (actor); CLUTTER_ACTOR_CLASS (e_mail_tab_parent_class)->unmap (actor); clutter_actor_unmap (CLUTTER_ACTOR (priv->label)); clutter_actor_unmap (CLUTTER_ACTOR (priv->close_button)); if (priv->icon) clutter_actor_unmap (priv->icon); if (priv->preview) clutter_actor_unmap (priv->preview); if (priv->old_bg) clutter_actor_unmap (priv->old_bg); } static gboolean e_mail_tab_button_press_event (ClutterActor *actor, ClutterButtonEvent *event) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (actor); if (event->button == 1) { mx_stylable_set_style_pseudo_class ( MX_STYLABLE (actor), "active"); clutter_grab_pointer (actor); priv->pressed = TRUE; priv->press_x = event->x; priv->press_y = event->y; } /* We have to always return false, or dragging won't work */ return FALSE; } static gboolean e_mail_tab_button_release_event (ClutterActor *actor, ClutterButtonEvent *event) { EMailTab *tab = E_MAIL_TAB (actor); EMailTabPrivate *priv = tab->priv; if (priv->pressed) { clutter_ungrab_pointer (); priv->pressed = FALSE; /* Note, no need to set the pseudo class here as clicking * always results in being set active. */ if (priv->hover) { if (!priv->active) e_mail_tab_set_active (tab, TRUE); g_signal_emit (actor, signals[CLICKED], 0); } } return FALSE; } static gboolean e_mail_tab_motion_event (ClutterActor *actor, ClutterMotionEvent *event) { EMailTab *tab = E_MAIL_TAB (actor); EMailTabPrivate *priv = tab->priv; if (priv->pressed && priv->drag_enabled) { if ((ABS (event->x - priv->press_x) >= priv->drag_threshold) || (ABS (event->y - priv->press_y) >= priv->drag_threshold)) { /* Ungrab the pointer so that the MxDraggable code can take over */ clutter_ungrab_pointer (); priv->pressed = FALSE; if (!priv->active) { if (priv->hover) mx_stylable_set_style_pseudo_class ( MX_STYLABLE (actor), "hover"); else mx_stylable_set_style_pseudo_class ( MX_STYLABLE (actor), NULL); } } } return FALSE; } static gboolean e_mail_tab_enter_event (ClutterActor *actor, ClutterCrossingEvent *event) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (actor); if (event->source != actor) return FALSE; priv->hover = TRUE; if (priv->pressed) mx_stylable_set_style_pseudo_class ( MX_STYLABLE (actor), "active"); else if (!priv->active) mx_stylable_set_style_pseudo_class ( MX_STYLABLE (actor), "hover"); return FALSE; } static gboolean e_mail_tab_leave_event (ClutterActor *actor, ClutterCrossingEvent *event) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (actor); if ((event->source != actor) || (event->related == (ClutterActor *) priv->close_button)) return FALSE; priv->hover = FALSE; if (!priv->active) mx_stylable_set_style_pseudo_class (MX_STYLABLE (actor), NULL); return FALSE; } static void e_mail_tab_class_init (EMailTabClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class); g_type_class_add_private (class, sizeof (EMailTabPrivate)); object_class->get_property = e_mail_tab_get_property; object_class->set_property = e_mail_tab_set_property; object_class->dispose = e_mail_tab_dispose; actor_class->get_preferred_width = e_mail_tab_get_preferred_width; actor_class->get_preferred_height = e_mail_tab_get_preferred_height; actor_class->button_press_event = e_mail_tab_button_press_event; actor_class->button_release_event = e_mail_tab_button_release_event; actor_class->motion_event = e_mail_tab_motion_event; actor_class->enter_event = e_mail_tab_enter_event; actor_class->leave_event = e_mail_tab_leave_event; actor_class->allocate = e_mail_tab_allocate; actor_class->paint = e_mail_tab_paint; actor_class->pick = e_mail_tab_pick; actor_class->map = e_mail_tab_map; actor_class->unmap = e_mail_tab_unmap; g_object_class_install_property ( object_class, PROP_ICON, g_param_spec_object ( "icon", "Icon", "Icon actor.", CLUTTER_TYPE_ACTOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_TEXT, g_param_spec_string ( "text", "Text", "Tab text.", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_CAN_CLOSE, g_param_spec_boolean ( "can-close", "Can close", "Whether the tab can " "close.", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_TAB_WIDTH, g_param_spec_int ( "tab-width", "Tab width", "Tab width.", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_DOCKING, g_param_spec_boolean ( "docking", "Docking", "Whether the tab should dock to edges when scrolled.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_PREVIEW, g_param_spec_object ( "preview", "Preview actor", "ClutterActor used " "when in preview mode.", CLUTTER_TYPE_ACTOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_PREVIEW_MODE, g_param_spec_boolean ( "preview-mode", "Preview mode", "Whether to display " "in preview mode.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_PREVIEW_DURATION, g_param_spec_uint ( "preview-duration", "Preview duration", "How long the transition " "between preview mode " "states lasts, in ms.", 0, G_MAXUINT, 200, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_SPACING, g_param_spec_float ( "spacing", "Spacing", "Spacing between " "tab elements.", 0, G_MAXFLOAT, 6.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_PRIVATE, g_param_spec_boolean ( "private", "Private", "Set if the tab is " "'private'.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_ACTIVE, g_param_spec_boolean ( "active", "Active", "Set if the tab is " "active.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_override_property ( object_class, PROP_DRAG_THRESHOLD, "drag-threshold"); g_object_class_override_property ( object_class, PROP_DRAG_AXIS, "axis"); g_object_class_override_property ( object_class, PROP_DRAG_CONTAINMENT_AREA, "containment-area"); g_object_class_override_property ( object_class, PROP_DRAG_ENABLED, "drag-enabled"); g_object_class_override_property ( object_class, PROP_DRAG_ACTOR, "drag-actor"); signals[CLICKED] = g_signal_new ( "clicked", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailTabClass, clicked), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[CLOSED] = g_signal_new ( "closed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailTabClass, closed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[TRANSITION_COMPLETE] = g_signal_new ( "transition-complete", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailTabClass, transition_complete), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void e_mail_tab_close_clicked_cb (MxButton *button, EMailTab *self) { g_signal_emit (self, signals[CLOSED], 0); } static void e_mail_tab_anim_completed_cb (ClutterAnimation *animation, EMailTab *tab) { e_mail_tab_dispose_old_bg (tab); } static void e_mail_tab_style_changed_cb (MxWidget *widget) { EMailTabPrivate *priv; priv = E_MAIL_TAB_GET_PRIVATE (widget); /* Don't transition on hover */ if (g_strcmp0 (mx_stylable_get_style_pseudo_class ( MX_STYLABLE (widget)), "hover") == 0) return; if (priv->old_bg) { if (!clutter_actor_get_parent (priv->old_bg)) { ClutterActorBox box; ClutterActor *background; ClutterActor *actor = CLUTTER_ACTOR (widget); clutter_actor_set_parent (priv->old_bg, actor); /* Try to allocate the same size as the background * widget, otherwise allocate the same size as the * widget itself. */ background = mx_widget_get_border_image (widget); if (!background) background = mx_widget_get_background_image (widget); if (background) clutter_actor_get_allocation_box (background, &box); else { clutter_actor_get_allocation_box (actor, &box); box.x2 -= box.x1; box.y2 -= box.y1; box.x1 = 0; box.y1 = 0; } clutter_actor_allocate (priv->old_bg, &box, 0); } clutter_actor_animate ( priv->old_bg, CLUTTER_LINEAR, 150, "opacity", 0, "signal::completed", G_CALLBACK (e_mail_tab_anim_completed_cb), widget, NULL); } } static void e_mail_tab_stylable_changed_cb (MxStylable *stylable) { EMailTab *tab = E_MAIL_TAB (stylable); EMailTabPrivate *priv = tab->priv; e_mail_tab_dispose_old_bg (tab); priv->old_bg = mx_widget_get_border_image (MX_WIDGET (tab)); if (priv->old_bg) g_object_ref (priv->old_bg); } static void e_mail_tab_dnd_notify_cb (GObject *settings, GParamSpec *pspec, EMailTab *tab) { g_object_get ( settings, "gtk-dnd-drag-threshold", &tab->priv->drag_threshold, NULL); } static void e_mail_tab_init (EMailTab *tab) { ClutterActor *text; GtkSettings *settings; tab->priv = E_MAIL_TAB_GET_PRIVATE (tab); tab->priv->width = -1; tab->priv->anim_length = 200; tab->priv->spacing = 6.0; tab->priv->can_close = TRUE; tab->priv->label = mx_label_new (); g_object_set (tab->priv->label, "clip-to-allocation", TRUE, NULL); text = mx_label_get_clutter_text (MX_LABEL (tab->priv->label)); clutter_text_set_ellipsize (CLUTTER_TEXT (text), PANGO_ELLIPSIZE_END); clutter_actor_set_parent ( CLUTTER_ACTOR (tab->priv->label), CLUTTER_ACTOR (tab)); tab->priv->close_button = mx_button_new (); clutter_actor_set_name ( CLUTTER_ACTOR (tab->priv->close_button), "tab-close-button"); clutter_actor_set_parent ( CLUTTER_ACTOR (tab->priv->close_button), CLUTTER_ACTOR (tab)); g_signal_connect ( tab->priv->close_button, "clicked", G_CALLBACK (e_mail_tab_close_clicked_cb), tab); /* Connect up styling signals */ g_signal_connect ( tab, "style-changed", G_CALLBACK (e_mail_tab_style_changed_cb), NULL); g_signal_connect ( tab, "stylable-changed", G_CALLBACK (e_mail_tab_stylable_changed_cb), NULL); clutter_actor_set_reactive (CLUTTER_ACTOR (tab), TRUE); settings = gtk_settings_get_default (); tab->priv->drag_threshold_handler = g_signal_connect ( settings, "notify::gtk-dnd-drag-threshold", G_CALLBACK (e_mail_tab_dnd_notify_cb), tab); g_object_get ( G_OBJECT (settings), "gtk-dnd-drag-threshold", &tab->priv->drag_threshold, NULL); } ClutterActor * e_mail_tab_new (void) { return g_object_new (E_TYPE_MAIL_TAB, NULL); } ClutterActor * e_mail_tab_new_full (const gchar *text, ClutterActor *icon, gint width) { return g_object_new ( E_TYPE_MAIL_TAB, "text", text, "icon", icon, "tab-width", width, NULL); } void e_mail_tab_set_text (EMailTab *tab, const gchar *text) { EMailTabPrivate *priv = tab->priv; if (!text) text = ""; priv->has_text = (text[0] != '\0'); if (priv->label) mx_label_set_text (MX_LABEL (priv->label), text); g_object_notify (G_OBJECT (tab), "text"); } void e_mail_tab_set_default_icon (EMailTab *tab, ClutterActor *icon) { EMailTabPrivate *priv = tab->priv; gboolean changed = !priv->icon || (priv->icon == priv->default_icon); if (icon) g_object_ref_sink (icon); if (priv->default_icon) g_object_unref (priv->default_icon); priv->default_icon = icon; if (changed) e_mail_tab_set_icon (tab, NULL); } void e_mail_tab_set_icon (EMailTab *tab, ClutterActor *icon) { EMailTabPrivate *priv = tab->priv; /* passing NULL for icon will use default icon if available */ if (priv->icon) clutter_actor_unparent (priv->icon); if (icon) priv->icon = icon; else priv->icon = priv->default_icon; if (priv->icon) clutter_actor_set_parent (priv->icon, CLUTTER_ACTOR (tab)); clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); g_object_notify (G_OBJECT (tab), "icon"); } const gchar * e_mail_tab_get_text (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; if (priv->label) return mx_label_get_text (MX_LABEL (priv->label)); else return NULL; } ClutterActor * e_mail_tab_get_icon (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->icon == priv->default_icon ? NULL : priv->icon; } void e_mail_tab_set_can_close (EMailTab *tab, gboolean can_close) { EMailTabPrivate *priv = tab->priv; if (priv->can_close == can_close) return; priv->can_close = can_close; clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); g_object_notify (G_OBJECT (tab), "can-close"); } gboolean e_mail_tab_get_can_close (EMailTab *tab) { return tab->priv->can_close; } void e_mail_tab_set_width (EMailTab *tab, gint width) { EMailTabPrivate *priv = tab->priv; if (priv->width == width) return; priv->width = width; clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); g_object_notify (G_OBJECT (tab), "tab-width"); } gint e_mail_tab_get_width (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->width; } void e_mail_tab_set_docking (EMailTab *tab, gboolean docking) { EMailTabPrivate *priv = tab->priv; if (priv->docking == docking) return; priv->docking = docking; clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); g_object_notify (G_OBJECT (tab), "docking"); } gboolean e_mail_tab_get_docking (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->docking; } void e_mail_tab_set_preview_actor (EMailTab *tab, ClutterActor *actor) { EMailTabPrivate *priv = tab->priv; if (priv->preview) clutter_actor_unparent (priv->preview); priv->preview = actor; if (actor) { clutter_actor_set_parent (actor, CLUTTER_ACTOR (tab)); clutter_actor_set_opacity ( actor, priv->preview_mode ? 0xff : 0x00); if (!priv->preview_mode) clutter_actor_hide (actor); } clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); g_object_notify (G_OBJECT (tab), "preview"); } ClutterActor * e_mail_tab_get_preview_actor (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->preview; } static void preview_new_frame_cb (ClutterTimeline *timeline, guint msecs, EMailTab *tab) { gboolean forwards; EMailTabPrivate *priv = tab->priv; forwards = (clutter_timeline_get_direction (timeline) == CLUTTER_TIMELINE_FORWARD); if (priv->preview_mode) forwards = !forwards; priv->preview_height_progress = clutter_timeline_get_progress (timeline); if (forwards) priv->preview_height_progress = 1.0 - priv->preview_height_progress; if (priv->preview) clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); } static void preview_completed_cb (ClutterTimeline *timeline, EMailTab *tab) { EMailTabPrivate *priv = tab->priv; if (priv->preview_timeline) { clutter_timeline_stop (priv->preview_timeline); g_object_unref (priv->preview_timeline); priv->preview_timeline = NULL; if (priv->preview_mode) priv->preview_height_progress = 1.0; else { priv->preview_height_progress = 0.0; if (priv->preview) clutter_actor_hide (priv->preview); if (priv->can_close) clutter_actor_set_reactive ( CLUTTER_ACTOR (priv->close_button), TRUE); } /* Remove style hint if we're not in preview mode */ if (priv->preview) { if (!priv->preview_mode) clutter_actor_set_name ( CLUTTER_ACTOR (tab), priv->private ? "private-tab" : NULL); } else { /* If there's no preview actor, disable the tab */ clutter_actor_set_reactive ( CLUTTER_ACTOR (tab), !priv->preview_mode); } if (priv->preview) clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); g_signal_emit (tab, signals[TRANSITION_COMPLETE], 0); } } static void preview_s1_started_cb (ClutterTimeline *timeline, EMailTab *tab) { EMailTabPrivate *priv = tab->priv; if (!priv->preview) clutter_actor_animate_with_timeline ( CLUTTER_ACTOR (priv->label), CLUTTER_EASE_IN_OUT_QUAD, timeline, "opacity", 0xff, NULL); } static void preview_s2_started_cb (ClutterTimeline *timeline, EMailTab *tab) { EMailTabPrivate *priv = tab->priv; if (priv->preview) clutter_actor_animate_with_timeline ( CLUTTER_ACTOR (priv->label), CLUTTER_EASE_IN_OUT_QUAD, timeline, "opacity", 0xff, NULL); } void e_mail_tab_set_preview_mode (EMailTab *tab, gboolean preview) { EMailTabPrivate *priv = tab->priv; if (priv->preview_mode != preview) { ClutterTimeline *timeline, *timeline2; gdouble progress, total_duration, duration1, duration2; priv->preview_mode = preview; /* Disable the close button in preview mode */ if (preview && priv->can_close) clutter_actor_set_reactive (CLUTTER_ACTOR (priv->close_button), FALSE); #define DEBUG_MULT 1 if (priv->preview_timeline) { progress = 1.0 - clutter_timeline_get_progress ( priv->preview_timeline); clutter_timeline_stop (priv->preview_timeline); g_object_unref (priv->preview_timeline); } else progress = 0.0; total_duration = priv->anim_length * (1.0 - progress) * DEBUG_MULT; duration1 = total_duration * TAB_S1_ANIM; duration2 = total_duration * TAB_S2_ANIM; priv->preview_timeline = clutter_timeline_new (priv->anim_length * DEBUG_MULT); clutter_timeline_skip ( priv->preview_timeline, clutter_timeline_get_duration ( priv->preview_timeline) * progress); g_signal_connect ( priv->preview_timeline, "completed", G_CALLBACK (preview_completed_cb), tab); clutter_timeline_start (priv->preview_timeline); if (!priv->preview) { clutter_actor_animate_with_timeline ( CLUTTER_ACTOR (tab), CLUTTER_EASE_IN_OUT_QUAD, priv->preview_timeline, "opacity", preview ? 0x00 : 0xff, NULL); return; } g_signal_connect ( priv->preview_timeline, "new-frame", G_CALLBACK (preview_new_frame_cb), tab); timeline = clutter_timeline_new ((guint) duration1); timeline2 = clutter_timeline_new ((guint) duration2); g_signal_connect ( timeline, "started", G_CALLBACK (preview_s1_started_cb), tab); g_signal_connect ( timeline2, "started", G_CALLBACK (preview_s2_started_cb), tab); if (preview) clutter_timeline_set_delay (timeline2, duration1); else clutter_timeline_set_delay (timeline, duration2); /* clutter_actor_animate_with_timeline will start the timelines */ clutter_actor_animate_with_timeline ( CLUTTER_ACTOR (priv->label), CLUTTER_EASE_IN_OUT_QUAD, preview ? timeline : timeline2, "opacity", 0x00, NULL); if (priv->icon) clutter_actor_animate_with_timeline ( priv->icon, CLUTTER_EASE_IN_OUT_QUAD, timeline, "opacity", preview ? 0x00 : 0xff, NULL); if (priv->can_close) clutter_actor_animate_with_timeline ( CLUTTER_ACTOR (priv->close_button), CLUTTER_EASE_IN_OUT_QUAD, timeline, "opacity", preview ? 0x00 : 0xff, NULL); if (priv->preview) clutter_actor_show (priv->preview); clutter_actor_animate_with_timeline ( priv->preview, CLUTTER_EASE_IN_OUT_QUAD, timeline2, "opacity", preview ? 0xff : 0x00, NULL); /* The animations have references on these, drop ours */ g_object_unref (timeline); g_object_unref (timeline2); /* Add an actor name, for style */ clutter_actor_set_name ( CLUTTER_ACTOR (tab), priv->private ? "private-preview-tab" : "preview-tab"); } } gboolean e_mail_tab_get_preview_mode (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->preview_mode; } void e_mail_tab_set_preview_duration (EMailTab *tab, guint duration) { EMailTabPrivate *priv = tab->priv; if (priv->anim_length != duration) { priv->anim_length = duration; g_object_notify (G_OBJECT (tab), "preview-duration"); } } guint e_mail_tab_get_preview_duration (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->anim_length; } void e_mail_tab_set_spacing (EMailTab *tab, gfloat spacing) { EMailTabPrivate *priv = tab->priv; if (priv->spacing != spacing) { priv->spacing = spacing; g_object_notify (G_OBJECT (tab), "spacing"); clutter_actor_queue_relayout (CLUTTER_ACTOR (tab)); } } gfloat e_mail_tab_get_spacing (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->spacing; } void e_mail_tab_set_private (EMailTab *tab, gboolean private) { EMailTabPrivate *priv = tab->priv; if (priv->private == private) return; priv->private = private; if (!priv->preview_mode) clutter_actor_set_name ( CLUTTER_ACTOR (tab), private ? "private-tab" : NULL); g_object_notify (G_OBJECT (tab), "private"); } gboolean e_mail_tab_get_private (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->private; } void e_mail_tab_set_active (EMailTab *tab, gboolean active) { EMailTabPrivate *priv = tab->priv; if (priv->active == active) return; priv->active = active; g_object_notify (G_OBJECT (tab), "active"); if (active) mx_stylable_set_style_pseudo_class (MX_STYLABLE (tab), "active"); else if (!priv->pressed) { if (priv->hover) mx_stylable_set_style_pseudo_class (MX_STYLABLE (tab), "hover"); else mx_stylable_set_style_pseudo_class (MX_STYLABLE (tab), NULL); } } gboolean e_mail_tab_get_active (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; return priv->active; } static gboolean e_mail_tab_alert_cb (EMailTab *tab) { const gchar *name; EMailTabPrivate *priv = tab->priv; /* FIXME: Work in preview mode */ /* Alternate between private mode and non-private to alert */ name = (priv->private ^ (priv->alert_count % 2)) ? NULL : "private-tab"; if (!priv->preview_mode) clutter_actor_set_name (CLUTTER_ACTOR (tab), name); priv->alert_count++; if (priv->alert_count < 4) return TRUE; priv->alert_source = 0; return FALSE; } void e_mail_tab_alert (EMailTab *tab) { EMailTabPrivate *priv = tab->priv; priv->alert_count = 0; if (!priv->alert_source) priv->alert_source = g_timeout_add_full (G_PRIORITY_HIGH, 500, (GSourceFunc) e_mail_tab_alert_cb, tab, NULL); } void e_mail_tab_enable_drag (EMailTab *tab, gboolean enable) { EMailTabPrivate *priv = tab->priv; if (priv->drag_enabled == enable) return; priv->drag_enabled = enable; if (enable) mx_draggable_enable (MX_DRAGGABLE (tab)); else mx_draggable_disable (MX_DRAGGABLE (tab)); }