diff options
author | Srinivasa Ragavan <sragavan@gnome.org> | 2010-07-16 01:12:57 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2010-07-26 09:30:00 +0800 |
commit | 6dd02e26294f270b367de48d5c95de56a1c0eebc (patch) | |
tree | 59f9aabe708ebadde3ac1e4ed6efc7061a1b33b5 | |
parent | 0bb1f50ded812857493a22a260fe2a7cd0b30bc7 (diff) | |
download | gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.tar gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.tar.gz gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.tar.bz2 gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.tar.lz gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.tar.xz gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.tar.zst gsoc2013-evolution-6dd02e26294f270b367de48d5c95de56a1c0eebc.zip |
Add Tab/Tab picker under clutter.
-rw-r--r-- | mail/e-mail-tab-picker.c | 1332 | ||||
-rw-r--r-- | mail/e-mail-tab-picker.h | 112 | ||||
-rw-r--r-- | mail/e-mail-tab.c | 1901 | ||||
-rw-r--r-- | mail/e-mail-tab.h | 108 |
4 files changed, 3453 insertions, 0 deletions
diff --git a/mail/e-mail-tab-picker.c b/mail/e-mail-tab-picker.c new file mode 100644 index 0000000000..6e8f12f88b --- /dev/null +++ b/mail/e-mail-tab-picker.c @@ -0,0 +1,1332 @@ +/* + * 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 "config.h" +#endif + +#include "e-mail-tab-picker.h" + +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; + 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; + + if (clutter_actor_get_parent (CLUTTER_ACTOR (draggable)) != + (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 (tab_picker)); + break; + + case PROP_DROP_ENABLED: + g_value_set_boolean (value, priv->drop_enabled); + break; + + default: + 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 (tab_picker, g_value_get_boolean (value)); + break; + + case PROP_DROP_ENABLED: + priv->drop_enabled = g_value_get_boolean (value); + break; + + default: + 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); + } + + G_OBJECT_CLASS (e_mail_tab_picker_parent_class)->dispose (object); +} + +static void +e_mail_tab_picker_finalize (GObject *object) +{ + G_OBJECT_CLASS (e_mail_tab_picker_parent_class)->finalize (object); +} + +static void +e_mail_tab_picker_paint (ClutterActor *actor) +{ + GList *t; + gfloat width, height, offset; + + EMailTabPickerPrivate *priv = E_MAIL_TAB_PICKER (actor)->priv; + + 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 = E_MAIL_TAB_PICKER (actor)->priv; + + /* 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 = E_MAIL_TAB_PICKER (actor)->priv; + + 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 (G_OBJECT (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 = + 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 = E_MAIL_TAB_PICKER (actor)->priv; + + 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 = E_MAIL_TAB_PICKER (actor)->priv; + + 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 *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, 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; + object_class->finalize = e_mail_tab_picker_finalize; + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_object_class_override_property (object_class, + PROP_DROP_ENABLED, + "drop-enabled"); + + signals[TAB_ACTIVATED] = + g_signal_new ("tab-activated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMailTabPickerClass, tab_activated), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, E_MAIL_TYPE_TAB); + + signals[CHOOSER_CLICKED] = + g_signal_new ("chooser-clicked", + G_TYPE_FROM_CLASS (klass), + 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 *self) +{ + g_signal_emit (self, signals[CHOOSER_CLICKED], 0); +} + +static gboolean +e_mail_tab_picker_scroll_event_cb (ClutterActor *actor, + ClutterScrollEvent *event, + gpointer user_data) +{ + EMailTabPicker *self = E_MAIL_TAB_PICKER (actor); + EMailTabPickerPrivate *priv = self->priv; + + priv->keep_current_visible = FALSE; + + switch (event->direction) + { + case CLUTTER_SCROLL_UP : + case CLUTTER_SCROLL_LEFT : + e_mail_tab_picker_scroll_to (self, priv->scroll_end - 200, 150); + break; + + case CLUTTER_SCROLL_DOWN : + case CLUTTER_SCROLL_RIGHT : + e_mail_tab_picker_scroll_to (self, 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 *self) +{ + EMailTabPickerPrivate *priv = self->priv = TAB_PICKER_PRIVATE (self); + + clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE); + + priv->chooser_button = mx_button_new (); + clutter_actor_set_name (CLUTTER_ACTOR (priv->chooser_button), + "chooser-button"); + clutter_actor_set_parent (CLUTTER_ACTOR (priv->chooser_button), + CLUTTER_ACTOR (self)); + + priv->close_button = mx_button_new (); + clutter_actor_set_name (CLUTTER_ACTOR (priv->close_button), + "chooser-close-button"); + clutter_actor_set_parent (CLUTTER_ACTOR (priv->close_button), + CLUTTER_ACTOR (self)); + clutter_actor_hide (CLUTTER_ACTOR (priv->close_button)); + + priv->scroll_adjustment = mx_adjustment_new_with_values (0, 0, 0, 100, 200, 200); + priv->scroll_bar = mx_scroll_bar_new_with_adjustment (priv->scroll_adjustment); + g_object_unref (priv->scroll_adjustment); + clutter_actor_set_parent (CLUTTER_ACTOR (priv->scroll_bar), + CLUTTER_ACTOR (self)); + clutter_actor_hide (CLUTTER_ACTOR (priv->scroll_bar)); + + g_signal_connect (priv->chooser_button, "clicked", + G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), self); + g_signal_connect (priv->close_button, "clicked", + G_CALLBACK (e_mail_tab_picker_chooser_clicked_cb), self); + g_signal_connect (self, "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; + + if (props->tab == tab) + return 0; + else + return -1; +} + +static void +e_mail_tab_picker_tab_clicked_cb (EMailTab *tab, EMailTabPicker *self) +{ + EMailTabPickerPrivate *priv = self->priv; + EMailTab *old_tab = + ((EMailTabPickerProps *)g_list_nth_data (priv->tabs, priv->current_tab))->tab; + GList *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 (self, 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 (self, signals[TAB_ACTIVATED], 0, tab); +} + +ClutterActor * +e_mail_tab_picker_new (void) +{ + return g_object_new (E_MAIL_TYPE_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 = 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"); +} + diff --git a/mail/e-mail-tab-picker.h b/mail/e-mail-tab-picker.h new file mode 100644 index 0000000000..7de3facc9d --- /dev/null +++ b/mail/e-mail-tab-picker.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + + +#ifndef _E_MAIL_TAB_PICKER_H +#define _E_MAIL_TAB_PICKER_H + +#include <glib-object.h> +#include <clutter/clutter.h> +#include <mx/mx.h> +#include "e-mail-tab.h" + +G_BEGIN_DECLS + +#define E_MAIL_TYPE_TAB_PICKER e_mail_tab_picker_get_type() + +#define E_MAIL_TAB_PICKER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + E_MAIL_TYPE_TAB_PICKER, EMailTabPicker)) + +#define E_MAIL_TAB_PICKER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + E_MAIL_TYPE_TAB_PICKER, EMailTabPickerClass)) + +#define E_MAIL_IS_TAB_PICKER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + E_MAIL_TYPE_TAB_PICKER)) + +#define E_MAIL_IS_TAB_PICKER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + E_MAIL_TYPE_TAB_PICKER)) + +#define E_MAIL_TAB_PICKER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + E_MAIL_TYPE_TAB_PICKER, EMailTabPickerClass)) + +typedef struct _EMailTabPickerPrivate EMailTabPickerPrivate; + +typedef struct { + MxWidget parent; + + EMailTabPickerPrivate *priv; +} EMailTabPicker; + +typedef struct { + MxWidgetClass parent_class; + + void (* tab_activated) (EMailTabPicker *picker, EMailTab *tab); + void (* chooser_clicked) (EMailTabPicker *picker); +} EMailTabPickerClass; + +GType e_mail_tab_picker_get_type (void); + +ClutterActor *e_mail_tab_picker_new (void); + +void e_mail_tab_picker_add_tab (EMailTabPicker *picker, EMailTab *tab, gint position); + +void e_mail_tab_picker_remove_tab (EMailTabPicker *picker, EMailTab *tab); + +GList *e_mail_tab_picker_get_tabs (EMailTabPicker *picker); + +gint e_mail_tab_picker_get_n_tabs (EMailTabPicker *picker); + +EMailTab *e_mail_tab_picker_get_tab (EMailTabPicker *picker, gint tab); + +gint e_mail_tab_picker_get_tab_no (EMailTabPicker *picker, EMailTab *tab); + +gint e_mail_tab_picker_get_current_tab (EMailTabPicker *picker); + +void e_mail_tab_picker_set_current_tab (EMailTabPicker *picker, gint tab); + +void e_mail_tab_picker_reorder (EMailTabPicker *picker, + gint old_position, + gint new_position); + +void e_mail_tab_picker_set_tab_width (EMailTabPicker *picker, + gint width); + +gint e_mail_tab_picker_get_tab_width (EMailTabPicker *picker); + +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); + +void e_mail_tab_picker_set_preview_mode (EMailTabPicker *picker, gboolean preview); + +gboolean e_mail_tab_picker_get_preview_mode (EMailTabPicker *picker); + +void e_mail_tab_picker_enable_drop (EMailTabPicker *picker, gboolean enable); + +G_END_DECLS + +#endif /* _E_MAIL_TAB_PICKER_H */ + diff --git a/mail/e-mail-tab.c b/mail/e-mail-tab.c new file mode 100644 index 0000000000..125f174775 --- /dev/null +++ b/mail/e-mail-tab.c @@ -0,0 +1,1901 @@ +/* + * 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 "config.h" +#endif + +#include <math.h> +#include <string.h> +#include <gtk/gtk.h> +#include "e-mail-tab.h" + +#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)) + +#define TAB_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), E_MAIL_TYPE_TAB, EMailTabPrivate)) + +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_TYPE, + 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; + // MxDragContainment containment; + 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) +{ + gfloat x, y; + + EMailTabPrivate *priv = E_MAIL_TAB (draggable)->priv; + ClutterActor *self = CLUTTER_ACTOR (draggable); + ClutterActor *actor = mx_draggable_get_drag_actor (draggable); + ClutterActor *stage = clutter_actor_get_stage (self); + + 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_TYPE: + // g_value_set_enum (value, priv->containment); + // 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) + { + ClutterActor *fbo = + /*clutter_texture_new_from_actor (CLUTTER_ACTOR (tab));*/ + NULL; + if (fbo) + { + /* This is where we'd setup deformations, or something along + * those lines. + */ + priv->drag_actor = g_object_ref_sink (fbo); + } + else + 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, + CLUTTER_ACTOR (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_TYPE: + // priv->containment = 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 = 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) + { + if (clutter_actor_get_parent (priv->old_bg) == (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 = 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_finalize (GObject *object) +{ + G_OBJECT_CLASS (e_mail_tab_parent_class)->finalize (object); +} + +static void +e_mail_tab_get_preferred_width (ClutterActor *actor, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + MxPadding padding; + EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv; + + /* 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, 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) +{ + MxPadding padding; + ClutterActorBox child_box; + gfloat icon_width, icon_height, label_width, label_height, + close_width, close_height, preview_width, preview_height; + + EMailTabPrivate *priv = E_MAIL_TAB (actor)->priv; + + /* 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 = E_MAIL_TAB (actor)->priv; + + /* 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 = E_MAIL_TAB (actor)->priv; + + 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 = E_MAIL_TAB (actor)->priv; + + 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 = E_MAIL_TAB (actor)->priv; + + 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 = E_MAIL_TAB (actor)->priv; + + 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 = E_MAIL_TAB (actor)->priv; + + 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 *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, 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; + object_class->finalize = e_mail_tab_finalize; + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_object_class_install_property (object_class, + PROP_TEXT, + g_param_spec_string ("text", + "Text", + "Tab text.", + "", + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + 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_TYPE, + // "containment-type"); + 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 (klass), + 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 (klass), + 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 (klass), + 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 = E_MAIL_TAB (widget)->priv; + + /* 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 *self) +{ + ClutterActor *text; + GtkSettings *settings; + + EMailTabPrivate *priv = self->priv = TAB_PRIVATE (self); + + priv->width = -1; + priv->anim_length = 200; + priv->spacing = 6.0; + priv->can_close = TRUE; + + priv->label = mx_label_new (); + g_object_set (G_OBJECT (priv->label), "clip-to-allocation", TRUE, NULL); + text = mx_label_get_clutter_text (MX_LABEL (priv->label)); + clutter_text_set_ellipsize (CLUTTER_TEXT (text), PANGO_ELLIPSIZE_END); + clutter_actor_set_parent (CLUTTER_ACTOR (priv->label), + CLUTTER_ACTOR (self)); + + priv->close_button = mx_button_new (); + clutter_actor_set_name (CLUTTER_ACTOR (priv->close_button), + "tab-close-button"); + clutter_actor_set_parent (CLUTTER_ACTOR (priv->close_button), + CLUTTER_ACTOR (self)); + + g_signal_connect (priv->close_button, "clicked", + G_CALLBACK (e_mail_tab_close_clicked_cb), self); + + /* Connect up styling signals */ + g_signal_connect (self, "style-changed", + G_CALLBACK (e_mail_tab_style_changed_cb), NULL); + g_signal_connect (self, "stylable-changed", + G_CALLBACK (e_mail_tab_stylable_changed_cb), NULL); + + clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE); + + settings = gtk_settings_get_default (); + priv->drag_threshold_handler = + g_signal_connect (settings, "notify::gtk-dnd-drag-threshold", + G_CALLBACK (e_mail_tab_dnd_notify_cb), self); + g_object_get (G_OBJECT (settings), + "gtk-dnd-drag-threshold", &priv->drag_threshold, + NULL); +} + +ClutterActor * +e_mail_tab_new (void) +{ + return g_object_new (E_MAIL_TYPE_TAB, NULL); +} + +ClutterActor * +e_mail_tab_new_full (const gchar *text, ClutterActor *icon, gint width) +{ + return g_object_new (E_MAIL_TYPE_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) ? TRUE : FALSE; + 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; + + /* This call isn't needed, it should be in the correct state as long as + * the above check always checks for < (an even number) + */ + /*if (!priv->preview_mode) + clutter_actor_set_name (CLUTTER_ACTOR (tab), + priv->private ? "private-tab" : NULL);*/ + 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)); +} + diff --git a/mail/e-mail-tab.h b/mail/e-mail-tab.h new file mode 100644 index 0000000000..3966d2752c --- /dev/null +++ b/mail/e-mail-tab.h @@ -0,0 +1,108 @@ +/* + * 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. + */ + +#ifndef _E_MAIL_TAB_H +#define _E_MAIL_TAB_H + +#include <glib-object.h> +#include <clutter/clutter.h> +#include <mx/mx.h> + +G_BEGIN_DECLS + +#define E_MAIL_TYPE_TAB e_mail_tab_get_type() + +#define E_MAIL_TAB(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + E_MAIL_TYPE_TAB, EMailTab)) + +#define E_MAIL_TAB_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + E_MAIL_TYPE_TAB, EMailTabClass)) + +#define E_MAIL_IS_TAB(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + E_MAIL_TYPE_TAB)) + +#define E_MAIL_IS_TAB_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + E_MAIL_TYPE_TAB)) + +#define E_MAIL_TAB_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + E_MAIL_TYPE_TAB, EMailTabClass)) + +typedef struct _EMailTabPrivate EMailTabPrivate; + +typedef struct { + MxWidget parent; + + EMailTabPrivate *priv; +} EMailTab; + +typedef struct { + MxWidgetClass parent_class; + + void (* clicked) (EMailTab *tab); + void (* closed) (EMailTab *tab); + void (* transition_complete) (EMailTab *tab); +} EMailTabClass; + +GType e_mail_tab_get_type (void); + +ClutterActor *e_mail_tab_new (void); +ClutterActor *e_mail_tab_new_full (const gchar *text, + ClutterActor *icon, + gint width); + +void e_mail_tab_set_text (EMailTab *tab, const gchar *text); +void e_mail_tab_set_default_icon (EMailTab *tab, ClutterActor *icon); +void e_mail_tab_set_icon (EMailTab *tab, ClutterActor *icon); +void e_mail_tab_set_can_close (EMailTab *tab, gboolean can_close); +void e_mail_tab_set_width (EMailTab *tab, gint width); +void e_mail_tab_set_docking (EMailTab *tab, gboolean docking); +void e_mail_tab_set_preview_actor (EMailTab *tab, ClutterActor *actor); +void e_mail_tab_set_preview_mode (EMailTab *tab, gboolean preview); +void e_mail_tab_set_preview_duration (EMailTab *tab, guint duration); +void e_mail_tab_set_spacing (EMailTab *tab, gfloat spacing); +void e_mail_tab_set_private (EMailTab *tab, gboolean private); +void e_mail_tab_set_active (EMailTab *tab, gboolean active); + +const gchar *e_mail_tab_get_text (EMailTab *tab); +ClutterActor *e_mail_tab_get_icon (EMailTab *tab); +gboolean e_mail_tab_get_can_close (EMailTab *tab); +gint e_mail_tab_get_width (EMailTab *tab); +gboolean e_mail_tab_get_docking (EMailTab *tab); +ClutterActor *e_mail_tab_get_preview_actor (EMailTab *tab); +gboolean e_mail_tab_get_preview_mode (EMailTab *tab); +void e_mail_tab_get_height_no_preview (EMailTab *tab, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p); +guint e_mail_tab_get_preview_duration (EMailTab *tab); +gfloat e_mail_tab_get_spacing (EMailTab *tab); +gboolean e_mail_tab_get_private (EMailTab *tab); +gboolean e_mail_tab_get_active (EMailTab *tab); + +void e_mail_tab_alert (EMailTab *tab); +void e_mail_tab_enable_drag (EMailTab *tab, gboolean enable); + +G_END_DECLS + +#endif /* _E_MAIL_TAB_H */ + |