/* * 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 "e-mail-tab-picker.h" #define E_MAIL_TAB_PICKER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_TAB_PICKER, EMailTabPickerPrivate)) static void mx_droppable_iface_init (MxDroppableIface *iface); static gint e_mail_tab_picker_find_tab_cb (gconstpointer a, gconstpointer b); G_DEFINE_TYPE_WITH_CODE ( EMailTabPicker, e_mail_tab_picker, MX_TYPE_WIDGET, G_IMPLEMENT_INTERFACE ( MX_TYPE_DROPPABLE, mx_droppable_iface_init)) #define TAB_PICKER_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_MAIL_TYPE_TAB_PICKER, EMailTabPickerPrivate)) enum { PROP_0, PROP_PREVIEW_MODE, PROP_DROP_ENABLED, }; enum { TAB_ACTIVATED, CHOOSER_CLICKED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0, }; typedef struct { EMailTab *tab; gfloat position; gfloat width; gboolean docking; gboolean docked; } EMailTabPickerProps; struct _EMailTabPickerPrivate { GList *tabs; gint n_tabs; ClutterActor *chooser_button; ClutterActor *close_button; gint current_tab; gboolean preview_mode; gboolean drop_enabled; gboolean in_drag; gboolean drag_preview; gint width; gint total_width; gint max_offset; gboolean docked_tabs; ClutterTimeline *scroll_timeline; ClutterAlpha *scroll_alpha; gint scroll_start; gint scroll_end; gint scroll_offset; gboolean keep_current_visible; MxAdjustment *scroll_adjustment; ClutterActor *scroll_bar; ClutterTimeline *preview_timeline; gfloat preview_progress; }; static void e_mail_tab_picker_over_in (MxDroppable *droppable, MxDraggable *draggable) { } static void e_mail_tab_picker_over_out (MxDroppable *droppable, MxDraggable *draggable) { } static void e_mail_tab_picker_drop (MxDroppable *droppable, MxDraggable *draggable, gfloat event_x, gfloat event_y, gint button, ClutterModifierType modifiers) { GList *t; EMailTabPickerProps *tab; ClutterActor *parent; gint current_position, new_position; EMailTabPicker *picker = E_MAIL_TAB_PICKER (droppable); EMailTabPickerPrivate *priv = picker->priv; /* Make sure this is a valid drop */ if (!priv->drop_enabled) return; if (!E_MAIL_IS_TAB (draggable)) return; parent = clutter_actor_get_parent (CLUTTER_ACTOR (draggable)); if (parent != (ClutterActor *) picker) return; /* Get current position and property data structure */ t = g_list_find_custom (priv->tabs, draggable, e_mail_tab_picker_find_tab_cb); tab = (EMailTabPickerProps *) t->data; if (!tab) { g_warning ("Tab that's parented to a picker not actually in picker"); return; } current_position = g_list_position (priv->tabs, t); /* Work out new position */ for (new_position = 0, t = priv->tabs; t; t = t->next) { EMailTabPickerProps *props = t->data; /* Ignore docked tabs */ if (!props->docked) { /* If the tab is beyond the dragged tab and not * draggable, we don't want to drag past it. */ if ((event_x >= props->position + priv->scroll_offset) && (tab->position + tab->width <= props->position) && !mx_draggable_is_enabled (MX_DRAGGABLE (props->tab))) { new_position--; break; } /* The same check for dragging left instead of right */ if ((event_x < props->position + props->width + priv->scroll_offset) && (tab->position >= props->position) && !mx_draggable_is_enabled (MX_DRAGGABLE (props->tab))) break; /* If the tab-end position is after the drop position, * break - we want to drop before here. */ if (props->position + props->width + priv->scroll_offset > event_x) break; } /* Increment the position */ new_position++; } /* Re-order */ e_mail_tab_picker_reorder (picker, current_position, new_position); } static void mx_droppable_iface_init (MxDroppableIface *iface) { iface->over_in = e_mail_tab_picker_over_in; iface->over_out = e_mail_tab_picker_over_out; iface->drop = e_mail_tab_picker_drop; } static void e_mail_tab_picker_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EMailTabPicker *tab_picker = E_MAIL_TAB_PICKER (object); EMailTabPickerPrivate *priv = tab_picker->priv; switch (property_id) { case PROP_PREVIEW_MODE: g_value_set_boolean ( value, e_mail_tab_picker_get_preview_mode ( E_MAIL_TAB_PICKER (object))); return; case PROP_DROP_ENABLED: g_value_set_boolean (value, priv->drop_enabled); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_mail_tab_picker_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { EMailTabPicker *tab_picker = E_MAIL_TAB_PICKER (object); EMailTabPickerPrivate *priv = tab_picker->priv; switch (property_id) { case PROP_PREVIEW_MODE: e_mail_tab_picker_set_preview_mode ( E_MAIL_TAB_PICKER (object), g_value_get_boolean (value)); return; case PROP_DROP_ENABLED: priv->drop_enabled = g_value_get_boolean (value); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_mail_tab_picker_dispose (GObject *object) { EMailTabPicker *picker = E_MAIL_TAB_PICKER (object); EMailTabPickerPrivate *priv = picker->priv; if (priv->scroll_bar) { clutter_actor_unparent (CLUTTER_ACTOR (priv->scroll_bar)); priv->scroll_bar = NULL; } if (priv->scroll_timeline) { clutter_timeline_stop (priv->scroll_timeline); g_object_unref (priv->scroll_alpha); g_object_unref (priv->scroll_timeline); priv->scroll_timeline = NULL; priv->scroll_alpha = NULL; } if (priv->preview_timeline) { clutter_timeline_stop (priv->preview_timeline); g_object_unref (priv->preview_timeline); priv->preview_timeline = NULL; } if (priv->chooser_button) { clutter_actor_unparent (CLUTTER_ACTOR (priv->chooser_button)); priv->chooser_button = NULL; } if (priv->close_button) { clutter_actor_unparent (CLUTTER_ACTOR (priv->close_button)); priv->close_button = NULL; } while (priv->tabs) { EMailTabPickerProps *props = priv->tabs->data; e_mail_tab_picker_remove_tab (picker, props->tab); } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_mail_tab_picker_parent_class)->dispose (object); } static void e_mail_tab_picker_paint (ClutterActor *actor) { GList *t; gfloat width, height, offset; EMailTabPickerPrivate *priv; priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor); CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->paint (actor); clutter_actor_get_size (actor, &width, &height); cogl_clip_push_rectangle (0, 0, width, height); offset = priv->scroll_offset; cogl_translate (-priv->scroll_offset, 0, 0); /* Draw normal tabs */ for (t = priv->tabs; t; t = t->next) { EMailTabPickerProps *props = t->data; if (props->docked) continue; if (props->position + props->width < offset) continue; if (props->position > width + offset) break; if (CLUTTER_ACTOR_IS_MAPPED (props->tab)) clutter_actor_paint (CLUTTER_ACTOR (props->tab)); } cogl_translate (priv->scroll_offset, 0, 0); /* Draw docked tabs */ if (priv->docked_tabs) { for (t = priv->tabs; t; t = t->next) { EMailTabPickerProps *props = t->data; if (!props->docked) continue; if (CLUTTER_ACTOR_IS_MAPPED (props->tab)) clutter_actor_paint (CLUTTER_ACTOR (props->tab)); } } cogl_clip_pop (); /* Draw tab chooser button */ if (CLUTTER_ACTOR_IS_MAPPED (priv->chooser_button)) clutter_actor_paint (CLUTTER_ACTOR (priv->chooser_button)); /* Draw scrollbar */ if (CLUTTER_ACTOR_IS_MAPPED (priv->scroll_bar)) { gfloat height; clutter_actor_get_preferred_height ( CLUTTER_ACTOR (priv->close_button), -1, NULL, &height); height *= priv->preview_progress; if (height >= 1.0) { cogl_clip_push_rectangle (0, 0, width, height); if (CLUTTER_ACTOR_IS_MAPPED (priv->close_button)) clutter_actor_paint (CLUTTER_ACTOR (priv->close_button)); clutter_actor_paint (CLUTTER_ACTOR (priv->scroll_bar)); cogl_clip_pop (); } } } static void e_mail_tab_picker_pick (ClutterActor *actor, const ClutterColor *color) { EMailTabPickerPrivate *priv; priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor); /* Chain up to paint background */ CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->pick (actor, color); if (!priv->in_drag) e_mail_tab_picker_paint (actor); } static void e_mail_tab_picker_get_preferred_width (ClutterActor *actor, gfloat for_height, gfloat *min_width_p, gfloat *natural_width_p) { GList *t; MxPadding padding; EMailTabPickerPrivate *priv; priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor); clutter_actor_get_preferred_width ( CLUTTER_ACTOR (priv->chooser_button), for_height, min_width_p, natural_width_p); 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; for (t = priv->tabs; t; t = t->next) { gfloat min_width, natural_width; EMailTabPickerProps *props = t->data; clutter_actor_get_preferred_width ( CLUTTER_ACTOR (props->tab), for_height, &min_width, &natural_width); if (min_width_p && !t->prev) *min_width_p += min_width; if (natural_width_p) *natural_width_p += natural_width; } } void e_mail_tab_picker_get_preferred_height (EMailTabPicker *tab_picker, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p, gboolean with_previews) { MxPadding padding; ClutterActor *actor = CLUTTER_ACTOR (tab_picker); EMailTabPickerPrivate *priv = tab_picker->priv; clutter_actor_get_preferred_height ( CLUTTER_ACTOR (priv->chooser_button), for_width, min_height_p, natural_height_p); if (priv->tabs) { gfloat min_height, natural_height, scroll_height; EMailTabPickerProps *props = priv->tabs->data; /* Get the height of the first tab - it's assumed that * tabs are fixed height. */ if (with_previews) { clutter_actor_get_preferred_height ( CLUTTER_ACTOR (props->tab), for_width, &min_height, &natural_height); if (CLUTTER_ACTOR_IS_VISIBLE (priv->scroll_bar)) { /* Add the height of the scrollbar-section */ clutter_actor_get_preferred_height ( CLUTTER_ACTOR (priv->close_button), -1, NULL, &scroll_height); scroll_height *= priv->preview_progress; min_height += scroll_height; natural_height += scroll_height; } } else e_mail_tab_get_height_no_preview ( props->tab, for_width, &min_height, &natural_height); if (min_height_p && (*min_height_p < min_height)) *min_height_p = min_height; if (natural_height_p && (*natural_height_p < natural_height)) *natural_height_p = natural_height; } 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; } static void _e_mail_tab_picker_get_preferred_height (ClutterActor *actor, gfloat for_width, gfloat *min_height_p, gfloat *natural_height_p) { e_mail_tab_picker_get_preferred_height ( E_MAIL_TAB_PICKER (actor), for_width, min_height_p, natural_height_p, TRUE); } static void e_mail_tab_picker_allocate_docked (EMailTabPicker *tab_picker, const ClutterActorBox *picker_box_p, const ClutterActorBox *chooser_box_p, ClutterAllocationFlags flags) { GList *t; MxPadding padding; ClutterActorBox picker_box, chooser_box, child_box; gfloat offset, width, left, right, height; EMailTabPickerPrivate *priv = tab_picker->priv; if (!picker_box_p) { clutter_actor_get_allocation_box ( CLUTTER_ACTOR (tab_picker), &picker_box); picker_box_p = &picker_box; } if (!chooser_box_p) { clutter_actor_get_allocation_box ( CLUTTER_ACTOR (priv->chooser_button), &chooser_box); chooser_box_p = &chooser_box; } mx_widget_get_padding (MX_WIDGET (tab_picker), &padding); /* Calculate available width and height */ width = picker_box_p->x2 - picker_box_p->x1 - padding.right; e_mail_tab_picker_get_preferred_height ( tab_picker, -1, NULL, &height, FALSE); child_box.y2 = picker_box_p->y2 - picker_box_p->y1 - padding.bottom; child_box.y1 = child_box.y2 - height; /* Don't dock over the chooser button */ width -= chooser_box_p->x2 - chooser_box_p->x1; offset = priv->scroll_offset; left = 0; right = width; priv->docked_tabs = FALSE; for (t = g_list_last (priv->tabs); t; t = t->prev) { EMailTabPickerProps *props = t->data; props->docked = FALSE; if (!props->docking) continue; if (props->position < offset) { /* Dock left */ priv->docked_tabs = TRUE; props->docked = TRUE; child_box.x1 = left; child_box.x2 = child_box.x1 + props->width; left += props->width; } else if (props->position + props->width > width + offset) { /* Dock right */ priv->docked_tabs = TRUE; props->docked = TRUE; child_box.x2 = right; child_box.x1 = child_box.x2 - props->width; right -= props->width; } else { child_box.x1 = props->position; child_box.x2 = child_box.x1 + props->width; } clutter_actor_allocate ( CLUTTER_ACTOR (props->tab), &child_box, flags); } } static void e_mail_tab_picker_scroll_new_frame_cb (ClutterTimeline *timeline, guint msecs, EMailTabPicker *tab_picker) { EMailTabPickerPrivate *priv = tab_picker->priv; gdouble alpha = clutter_alpha_get_alpha (priv->scroll_alpha); priv->scroll_offset = (priv->scroll_start * (1.0 - alpha)) + (priv->scroll_end * alpha); mx_adjustment_set_value (priv->scroll_adjustment, priv->scroll_offset); e_mail_tab_picker_allocate_docked (tab_picker, NULL, NULL, 0); clutter_actor_queue_redraw (CLUTTER_ACTOR (tab_picker)); } static void e_mail_tab_picker_scroll_completed_cb (ClutterTimeline *timeline, EMailTabPicker *tab_picker) { EMailTabPickerPrivate *priv = tab_picker->priv; priv->scroll_offset = priv->scroll_end; mx_adjustment_set_value (priv->scroll_adjustment, priv->scroll_offset); e_mail_tab_picker_allocate_docked (tab_picker, NULL, NULL, 0); clutter_actor_queue_redraw (CLUTTER_ACTOR (tab_picker)); g_object_unref (priv->scroll_alpha); g_object_unref (priv->scroll_timeline); priv->scroll_alpha = NULL; priv->scroll_timeline = NULL; } static void e_mail_tab_picker_scroll_to (EMailTabPicker *tab_picker, gint destination, guint duration) { EMailTabPickerPrivate *priv = tab_picker->priv; priv->scroll_start = priv->scroll_offset; priv->scroll_end = CLAMP (destination, 0, priv->max_offset); if (priv->scroll_timeline) { clutter_timeline_stop (priv->scroll_timeline); clutter_timeline_rewind (priv->scroll_timeline); clutter_timeline_set_duration (priv->scroll_timeline, duration); } else { if (priv->scroll_end == priv->scroll_offset) return; priv->scroll_timeline = clutter_timeline_new (duration); priv->scroll_alpha = clutter_alpha_new_full ( priv->scroll_timeline, CLUTTER_EASE_OUT_QUAD); g_signal_connect ( priv->scroll_timeline, "new_frame", G_CALLBACK (e_mail_tab_picker_scroll_new_frame_cb), tab_picker); g_signal_connect ( priv->scroll_timeline, "completed", G_CALLBACK (e_mail_tab_picker_scroll_completed_cb), tab_picker); } clutter_timeline_start (priv->scroll_timeline); } static void e_mail_tab_picker_allocate (ClutterActor *actor, const ClutterActorBox *box, ClutterAllocationFlags flags) { GList *t; MxPadding padding; gint old_max_offset, old_scroll_offset; ClutterActorBox child_box, scroll_box; gfloat width, total_width, height; EMailTabPicker *tab_picker = E_MAIL_TAB_PICKER (actor); EMailTabPickerPrivate *priv = tab_picker->priv; mx_widget_get_padding (MX_WIDGET (actor), &padding); /* Allocate for scroll-bar and close button */ clutter_actor_get_preferred_size ( CLUTTER_ACTOR (priv->close_button), NULL, NULL, &width, &height); child_box.x1 = 0; child_box.x2 = box->x2 - box->x1 - padding.right; child_box.y1 = 0; child_box.y2 = child_box.y1 + height; clutter_actor_allocate ( CLUTTER_ACTOR (priv->close_button), &child_box, flags); /* FIXME: Make this a property */ #define SPACING 4.0 /* Work out allocation for scroll-bar, but allocate it later */ scroll_box = child_box; scroll_box.x2 -= width + SPACING; scroll_box.x1 += SPACING; scroll_box.y1 += SPACING; scroll_box.y2 -= SPACING; child_box.y1 += (height * priv->preview_progress) + padding.top; /* Allocate for tabs */ total_width = 0; child_box.x1 = padding.left; e_mail_tab_picker_get_preferred_height ( tab_picker, -1, NULL, &height, FALSE); for (t = priv->tabs; t; t = t->next) { EMailTabPickerProps *props = t->data; ClutterActor *actor = CLUTTER_ACTOR (props->tab); clutter_actor_get_preferred_width ( actor, child_box.y2, NULL, &width); /* Fill out data - note it's ok to fill out docking here * as when it changes, the tab queues a relayout. */ props->docking = e_mail_tab_get_docking (props->tab); props->position = child_box.x1; props->width = width; total_width += width; /* Don't stretch tabs without a preview to fit tabs * with a preview. */ if (e_mail_tab_get_preview_actor (props->tab)) child_box.y2 = box->y2 - box->y1 - padding.bottom; else child_box.y2 = child_box.y1 + height; child_box.x2 = child_box.x1 + width; clutter_actor_allocate (actor, &child_box, flags); child_box.x1 = child_box.x2; } /* Allocate for the chooser button */ clutter_actor_get_preferred_width ( CLUTTER_ACTOR (priv->chooser_button), box->y2 - box->y1, NULL, &width); child_box.x2 = box->x2 - box->x1 - padding.right; child_box.x1 = child_box.x2 - width; child_box.y1 = 0; child_box.y2 = child_box.y1 + height; clutter_actor_allocate ( CLUTTER_ACTOR (priv->chooser_button), &child_box, flags); /* Cache some useful size values */ priv->width = (gint)(box->x2 - box->x1); priv->total_width = (gint)(total_width + padding.left + padding.right); old_max_offset = priv->max_offset; priv->max_offset = priv->total_width - priv->width + (gint) (child_box.x2 - child_box.x1); if (priv->max_offset < 0) priv->max_offset = 0; /* Allocate for tab picker */ old_scroll_offset = priv->scroll_offset; priv->scroll_offset = CLAMP (priv->scroll_offset, 0, priv->max_offset); e_mail_tab_picker_allocate_docked (tab_picker, box, &child_box, flags); /* Chain up (store box) */ CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)-> allocate (actor, box, flags); /* Sync up the scroll-bar properties */ g_object_set ( priv->scroll_adjustment, "page-increment", (gdouble)(box->x2 - box->x1), "page-size", (gdouble)(box->x2 - box->x1), "upper", (gdouble)total_width, NULL); if ((priv->max_offset != old_max_offset) || (priv->scroll_offset != old_scroll_offset)) mx_adjustment_set_value ( priv->scroll_adjustment, (gdouble) priv->scroll_offset); /* Allocate for scroll-bar */ clutter_actor_allocate ( CLUTTER_ACTOR (priv->scroll_bar), &scroll_box, flags); /* Keep current tab visible */ if (priv->keep_current_visible) { EMailTabPickerProps *current; current = g_list_nth_data (priv->tabs, priv->current_tab); if ((current->position < priv->scroll_offset) || (current->position + current->width >= priv->max_offset)) e_mail_tab_picker_scroll_to ( tab_picker, current->position, 150); } } static void e_mail_tab_picker_map (ClutterActor *actor) { GList *t; EMailTabPickerPrivate *priv; priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor); CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->map (actor); clutter_actor_map (CLUTTER_ACTOR (priv->chooser_button)); clutter_actor_map (CLUTTER_ACTOR (priv->close_button)); clutter_actor_map (CLUTTER_ACTOR (priv->scroll_bar)); for (t = priv->tabs; t; t = t->next) { EMailTabPickerProps *props = t->data; clutter_actor_map (CLUTTER_ACTOR (props->tab)); } } static void e_mail_tab_picker_unmap (ClutterActor *actor) { GList *t; EMailTabPickerPrivate *priv; priv = E_MAIL_TAB_PICKER_GET_PRIVATE (actor); CLUTTER_ACTOR_CLASS (e_mail_tab_picker_parent_class)->unmap (actor); clutter_actor_unmap (CLUTTER_ACTOR (priv->chooser_button)); clutter_actor_unmap (CLUTTER_ACTOR (priv->close_button)); clutter_actor_unmap (CLUTTER_ACTOR (priv->scroll_bar)); for (t = priv->tabs; t; t = t->next) { EMailTabPickerProps *props = t->data; clutter_actor_unmap (CLUTTER_ACTOR (props->tab)); } } static void e_mail_tab_picker_class_init (EMailTabPickerClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class); g_type_class_add_private (class, sizeof (EMailTabPickerPrivate)); object_class->get_property = e_mail_tab_picker_get_property; object_class->set_property = e_mail_tab_picker_set_property; object_class->dispose = e_mail_tab_picker_dispose; actor_class->paint = e_mail_tab_picker_paint; actor_class->pick = e_mail_tab_picker_pick; actor_class->get_preferred_width = e_mail_tab_picker_get_preferred_width; actor_class->get_preferred_height = _e_mail_tab_picker_get_preferred_height; actor_class->allocate = e_mail_tab_picker_allocate; actor_class->map = e_mail_tab_picker_map; actor_class->unmap = e_mail_tab_picker_unmap; 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_override_property ( object_class, PROP_DROP_ENABLED, "drop-enabled"); signals[TAB_ACTIVATED] = g_signal_new ( "tab-activated", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailTabPickerClass, tab_activated), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, E_TYPE_MAIL_TAB); signals[CHOOSER_CLICKED] = g_signal_new ( "chooser-clicked", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMailTabPickerClass, chooser_clicked), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void e_mail_tab_picker_chooser_clicked_cb (ClutterActor *button, EMailTabPicker *picker) { g_signal_emit (picker, signals[CHOOSER_CLICKED], 0); } static gboolean e_mail_tab_picker_scroll_event_cb (ClutterActor *actor, ClutterScrollEvent *event, gpointer user_data) { EMailTabPicker *picker = E_MAIL_TAB_PICKER (actor); EMailTabPickerPrivate *priv = picker->priv; priv->keep_current_visible = FALSE; switch (event->direction) { case CLUTTER_SCROLL_UP : case CLUTTER_SCROLL_LEFT : e_mail_tab_picker_scroll_to ( picker, priv->scroll_end - 200, 150); break; case CLUTTER_SCROLL_DOWN : case CLUTTER_SCROLL_RIGHT : e_mail_tab_picker_scroll_to ( picker, priv->scroll_end + 200, 150); break; } return TRUE; } static void e_mail_tab_picker_scroll_value_cb (MxAdjustment *adjustment, GParamSpec *pspec, EMailTabPicker *picker) { EMailTabPickerPrivate *priv = picker->priv; gdouble value = mx_adjustment_get_value (adjustment); if ((gint) value != priv->scroll_offset) { priv->keep_current_visible = FALSE; priv->scroll_offset = (gint) value; clutter_actor_queue_relayout (CLUTTER_ACTOR (picker)); } } static void e_mail_tab_picker_init (EMailTabPicker *picker) { picker->priv = E_MAIL_TAB_PICKER_GET_PRIVATE (picker); clutter_actor_set_reactive (CLUTTER_ACTOR (picker), TRUE); picker->priv->chooser_button = mx_button_new (); clutter_actor_set_name ( CLUTTER_ACTOR (picker->priv->chooser_button), "chooser-button"); clutter_actor_set_parent ( CLUTTER_ACTOR (picker->priv->chooser_button), CLUTTER_ACTOR (picker)); picker->priv->close_button = mx_button_new (); clutter_actor_set_name ( CLUTTER_ACTOR (picker->priv->close_button), "chooser-close-button"); clutter_actor_set_parent ( CLUTTER_ACTOR (picker->priv->close_button), CLUTTER_ACTOR (picker)); clutter_actor_hide (CLUTTER_ACTOR (picker->priv->close_button)); picker->priv->scroll_adjustment = mx_adjustment_new_with_values (0, 0, 0, 100, 200, 200); picker->priv->scroll_bar = mx_scroll_bar_new_with_adjustment ( picker->priv->scroll_adjustment); g_object_unref (picker->priv->scroll_adjustment); clutter_actor_set_parent ( CLUTTER_ACTOR (picker->priv->scroll_bar), CLUTTER_ACTOR (picker)); clutter_actor_hide (CLUTTER_ACTOR (picker->priv->scroll_bar)); g_signal_connect ( picker->priv->chooser_button, "clicked", G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), picker); g_signal_connect ( picker->priv->close_button, "clicked", G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), picker); g_signal_connect ( picker, "scroll-event", G_CALLBACK (e_mail_tab_picker_scroll_event_cb), NULL); } static gint e_mail_tab_picker_find_tab_cb (gconstpointer a, gconstpointer b) { EMailTabPickerProps *props = (EMailTabPickerProps *) a; EMailTab *tab = (EMailTab *) b; return (props->tab == tab) ? 0 : -1; } static void e_mail_tab_picker_tab_clicked_cb (EMailTab *tab, EMailTabPicker *picker) { EMailTabPickerPrivate *priv = picker->priv; EMailTab *old_tab; GList *new_tab_link; old_tab = ((EMailTabPickerProps *) g_list_nth_data ( priv->tabs, priv->current_tab))->tab; new_tab_link = g_list_find_custom ( priv->tabs, tab, e_mail_tab_picker_find_tab_cb); if (!new_tab_link) return; priv->keep_current_visible = TRUE; /* If the same tab is clicked, make sure we remain active and return */ if (tab == old_tab) { e_mail_tab_set_active (tab, TRUE); if (priv->preview_mode) g_signal_emit (picker, signals[TAB_ACTIVATED], 0, tab); return; } /* Deselect old tab */ e_mail_tab_set_active (old_tab, FALSE); /* Set new tab */ priv->current_tab = g_list_position (priv->tabs, new_tab_link); g_signal_emit (picker, signals[TAB_ACTIVATED], 0, tab); } ClutterActor * e_mail_tab_picker_new (void) { return g_object_new (E_TYPE_MAIL_TAB_PICKER, NULL); } static void e_mail_tab_picker_tab_drag_begin_cb (MxDraggable *draggable, gfloat event_x, gfloat event_y, gint event_button, ClutterModifierType modifiers, EMailTabPicker *picker) { EMailTabPickerPrivate *priv = picker->priv; priv->in_drag = TRUE; if (!priv->preview_mode) { e_mail_tab_picker_set_preview_mode (picker, TRUE); priv->drag_preview = TRUE; } } static void e_mail_tab_picker_tab_drag_end_cb (MxDraggable *draggable, gfloat event_x, gfloat event_y, EMailTabPicker *picker) { EMailTabPickerPrivate *priv = picker->priv; priv->in_drag = FALSE; if (priv->drag_preview) { e_mail_tab_picker_set_preview_mode (picker, FALSE); priv->drag_preview = FALSE; } } void e_mail_tab_picker_add_tab (EMailTabPicker *picker, EMailTab *tab, gint position) { EMailTabPickerProps *props; EMailTabPickerPrivate *priv = picker->priv; if (priv->tabs && (priv->current_tab >= position)) priv->current_tab++; props = g_slice_new (EMailTabPickerProps); props->tab = tab; priv->tabs = g_list_insert (priv->tabs, props, position); priv->n_tabs++; clutter_actor_set_parent (CLUTTER_ACTOR (tab), CLUTTER_ACTOR (picker)); mx_draggable_set_axis (MX_DRAGGABLE (tab), MX_DRAG_AXIS_X); g_signal_connect_after ( tab, "clicked", G_CALLBACK (e_mail_tab_picker_tab_clicked_cb), picker); g_signal_connect ( tab, "drag-begin", G_CALLBACK (e_mail_tab_picker_tab_drag_begin_cb), picker); g_signal_connect ( tab, "drag-end", G_CALLBACK (e_mail_tab_picker_tab_drag_end_cb), picker); e_mail_tab_set_preview_mode (tab, priv->preview_mode); clutter_actor_queue_relayout (CLUTTER_ACTOR (picker)); } void e_mail_tab_picker_remove_tab (EMailTabPicker *picker, EMailTab *tab) { GList *tab_link; EMailTabPickerPrivate *priv = picker->priv; tab_link = g_list_find_custom ( priv->tabs, tab, e_mail_tab_picker_find_tab_cb); if (!tab_link) return; g_signal_handlers_disconnect_by_func ( tab, e_mail_tab_picker_tab_clicked_cb, picker); g_signal_handlers_disconnect_by_func ( tab, e_mail_tab_picker_tab_drag_begin_cb, picker); g_signal_handlers_disconnect_by_func ( tab, e_mail_tab_picker_tab_drag_end_cb, picker); /* We don't want to do this during dispose, checking if chooser_button * exists is a way of checking if we're in dispose without keeping an * extra variable around. */ if (priv->chooser_button) { gint position = g_list_position (priv->tabs, tab_link); if (priv->current_tab) { if (priv->current_tab > position) priv->current_tab--; else if (priv->current_tab == position) e_mail_tab_picker_set_current_tab ( picker, priv->current_tab - 1); } else if (priv->tabs->next && (position == 0)) { e_mail_tab_picker_set_current_tab ( picker, priv->current_tab + 1); priv->current_tab--; } } g_slice_free (EMailTabPickerProps, tab_link->data); priv->tabs = g_list_delete_link (priv->tabs, tab_link); clutter_actor_unparent (CLUTTER_ACTOR (tab)); priv->n_tabs--; clutter_actor_queue_relayout (CLUTTER_ACTOR (picker)); } GList * e_mail_tab_picker_get_tabs (EMailTabPicker *picker) { GList *tab_list, *t; EMailTabPickerPrivate *priv = picker->priv; tab_list = NULL; for (t = g_list_last (priv->tabs); t; t = t->prev) { EMailTabPickerProps *props = t->data; tab_list = g_list_prepend (tab_list, props->tab); } return tab_list; } EMailTab * e_mail_tab_picker_get_tab (EMailTabPicker *picker, gint tab) { EMailTabPickerProps *props = g_list_nth_data (picker->priv->tabs, tab); return props->tab; } gint e_mail_tab_picker_get_tab_no (EMailTabPicker *picker, EMailTab *tab) { GList *tab_link; tab_link = g_list_find_custom ( picker->priv->tabs, tab, e_mail_tab_picker_find_tab_cb); return g_list_position (picker->priv->tabs, tab_link); } gint e_mail_tab_picker_get_current_tab (EMailTabPicker *picker) { return picker->priv->current_tab; } void e_mail_tab_picker_set_current_tab (EMailTabPicker *picker, gint tab_no) { EMailTabPickerPrivate *priv = picker->priv; EMailTabPickerProps *props; printf("OLD %d new %d\n", priv->current_tab, tab_no); if (priv->n_tabs == 0) return; if (ABS (tab_no) >= priv->n_tabs) return; if (tab_no < 0) tab_no = priv->n_tabs + tab_no; props = g_list_nth_data (priv->tabs, (guint) tab_no); if (props) { e_mail_tab_picker_tab_clicked_cb (props->tab, picker); e_mail_tab_set_active (props->tab, TRUE); } } void e_mail_tab_picker_reorder (EMailTabPicker *picker, gint old_position, gint new_position) { GList *link; gpointer data; EMailTabPickerPrivate *priv = picker->priv; if (old_position == new_position) return; if (!(link = g_list_nth (priv->tabs, old_position))) return; data = link->data; priv->tabs = g_list_delete_link (priv->tabs, link); priv->tabs = g_list_insert (priv->tabs, data, new_position); if (priv->current_tab == old_position) { if (new_position < 0) priv->current_tab = priv->n_tabs - 1; else priv->current_tab = CLAMP ( new_position, 0, priv->n_tabs - 1); } else if ((priv->current_tab > old_position) && (new_position >= priv->current_tab)) priv->current_tab--; else if ((priv->current_tab < old_position) && (new_position <= priv->current_tab)) priv->current_tab++; clutter_actor_queue_relayout (CLUTTER_ACTOR (picker)); } gint e_mail_tab_picker_get_n_tabs (EMailTabPicker *picker) { return picker->priv->n_tabs; } static void preview_new_frame_cb (ClutterTimeline *timeline, guint msecs, EMailTabPicker *picker) { picker->priv->preview_progress = clutter_timeline_get_progress (timeline); clutter_actor_queue_relayout (CLUTTER_ACTOR (picker)); } static void preview_completed_cb (ClutterTimeline *timeline, EMailTabPicker *picker) { EMailTabPickerPrivate *priv = picker->priv; if (priv->preview_timeline) { g_object_unref (priv->preview_timeline); priv->preview_timeline = NULL; if (priv->preview_mode) { priv->preview_progress = 1.0; clutter_actor_hide ( CLUTTER_ACTOR (priv->chooser_button)); } else { priv->preview_progress = 0.0; clutter_actor_hide (CLUTTER_ACTOR (priv->scroll_bar)); clutter_actor_hide (CLUTTER_ACTOR (priv->close_button)); } clutter_actor_queue_relayout (CLUTTER_ACTOR (picker)); } } void e_mail_tab_picker_set_preview_mode (EMailTabPicker *picker, gboolean preview) { GList *t; EMailTabPickerPrivate *priv = picker->priv; if (priv->preview_mode == preview) return; priv->preview_mode = preview; /* Put all tabs in preview mode */ for (t = priv->tabs; t; t = t->next) { EMailTabPickerProps *prop = t->data; e_mail_tab_set_preview_mode (prop->tab, preview); } /* Slide in the scroll-bar */ if (!priv->preview_timeline) { if (preview) clutter_actor_show (CLUTTER_ACTOR (priv->scroll_bar)); priv->preview_timeline = clutter_timeline_new (150); g_signal_connect ( priv->preview_timeline, "new-frame", G_CALLBACK (preview_new_frame_cb), picker); g_signal_connect ( priv->preview_timeline, "completed", G_CALLBACK (preview_completed_cb), picker); clutter_timeline_start (priv->preview_timeline); } clutter_timeline_set_direction ( priv->preview_timeline, preview ? CLUTTER_TIMELINE_FORWARD : CLUTTER_TIMELINE_BACKWARD); /* Connect/disconnect the scrollbar */ if (preview) g_signal_connect ( priv->scroll_adjustment, "notify::value", G_CALLBACK (e_mail_tab_picker_scroll_value_cb), picker); else g_signal_handlers_disconnect_by_func ( priv->scroll_adjustment, e_mail_tab_picker_scroll_value_cb, picker); if (preview) { /* Fade out the chooser button show close button */ clutter_actor_animate ( CLUTTER_ACTOR (priv->chooser_button), CLUTTER_EASE_IN_OUT_QUAD, 150, "opacity", 0x00, NULL); clutter_actor_show (CLUTTER_ACTOR (priv->close_button)); } else { /* Fade in the chooser button */ clutter_actor_show (CLUTTER_ACTOR (priv->chooser_button)); clutter_actor_animate ( CLUTTER_ACTOR (priv->chooser_button), CLUTTER_EASE_IN_OUT_QUAD, 150, "opacity", 0xff, NULL); } clutter_actor_set_reactive ( CLUTTER_ACTOR (priv->chooser_button), !preview); /* Remove the hover state, which likely got stuck when we clicked it */ if (!preview) mx_stylable_set_style_pseudo_class ( MX_STYLABLE (priv->chooser_button), NULL); g_object_notify (G_OBJECT (picker), "preview-mode"); } gboolean e_mail_tab_picker_get_preview_mode (EMailTabPicker *picker) { EMailTabPickerPrivate *priv = picker->priv; return priv->preview_mode; } void e_mail_tab_picker_enable_drop (EMailTabPicker *picker, gboolean enable) { EMailTabPickerPrivate *priv = picker->priv; if (priv->drop_enabled == enable) return; priv->drop_enabled = enable; if (enable) mx_droppable_enable (MX_DROPPABLE (picker)); else mx_droppable_disable (MX_DROPPABLE (picker)); g_object_notify (G_OBJECT (picker), "enabled"); }