667 lines
18 KiB
C
667 lines
18 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/*
|
|
* mx-notebook: notebook actor
|
|
*
|
|
* Copyright 2009, 2010 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.
|
|
*
|
|
* Written by: Thomas Wood <thomas.wood@intel.com>
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "mx-notebook.h"
|
|
#include "mx-private.h"
|
|
#include "mx-focusable.h"
|
|
#ifdef HAVE_CLUTTER_GESTURE
|
|
#include <clutter-gesture/clutter-gesture.h>
|
|
#endif
|
|
|
|
static void clutter_container_iface_init (ClutterContainerIface *iface);
|
|
static void mx_focusable_iface_init (MxFocusableIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (MxNotebook, mx_notebook, MX_TYPE_WIDGET,
|
|
G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_CONTAINER,
|
|
clutter_container_iface_init)
|
|
G_IMPLEMENT_INTERFACE (MX_TYPE_FOCUSABLE,
|
|
mx_focusable_iface_init))
|
|
|
|
#define NOTEBOOK_PRIVATE(o) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((o), MX_TYPE_NOTEBOOK, MxNotebookPrivate))
|
|
|
|
struct _MxNotebookPrivate
|
|
{
|
|
ClutterActor *current_page;
|
|
|
|
GList *children;
|
|
|
|
gboolean enable_gestures;
|
|
|
|
#if HAVE_CLUTTER_GESTURE
|
|
ClutterGesture *gesture;
|
|
#endif
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_CURRENT_PAGE = 1,
|
|
PROP_ENABLE_GESTURES
|
|
};
|
|
|
|
static void
|
|
mx_notebook_show_complete_cb (MxNotebook *book)
|
|
{
|
|
MxNotebookPrivate *priv = book->priv;
|
|
GList *l;
|
|
|
|
for (l = priv->children; l; l = l->next)
|
|
{
|
|
ClutterActor *child = CLUTTER_ACTOR (l->data);
|
|
if (child != priv->current_page)
|
|
{
|
|
clutter_actor_hide (child);
|
|
clutter_actor_set_opacity (child, 0x00);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
mx_notebook_update_children (MxNotebook *book)
|
|
{
|
|
MxNotebookPrivate *priv = book->priv;
|
|
GList *l;
|
|
|
|
for (l = priv->children; l; l = l->next)
|
|
{
|
|
ClutterActor *child = CLUTTER_ACTOR (l->data);
|
|
ClutterAnimation *anim = clutter_actor_get_animation (child);
|
|
|
|
if (anim)
|
|
{
|
|
/* A bit of a hack - we want to just abort the animation,
|
|
* but there's no way of aborting an animation that was
|
|
* started with clutter_actor_animate().
|
|
*/
|
|
guint8 opacity = clutter_actor_get_opacity (child);
|
|
g_signal_handlers_disconnect_by_func (anim,
|
|
mx_notebook_show_complete_cb,
|
|
book);
|
|
clutter_animation_completed (anim);
|
|
clutter_actor_set_opacity (child, opacity);
|
|
}
|
|
|
|
if (child == priv->current_page)
|
|
{
|
|
clutter_actor_show (child);
|
|
clutter_actor_animate (child, CLUTTER_LINEAR, 250,
|
|
"opacity", 255,
|
|
"signal-swapped::completed",
|
|
mx_notebook_show_complete_cb,
|
|
book,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
mx_notebook_add (ClutterContainer *container,
|
|
ClutterActor *actor)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (container)->priv;
|
|
|
|
clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));
|
|
priv->children = g_list_append (priv->children, actor);
|
|
|
|
if (!priv->current_page)
|
|
{
|
|
priv->current_page = actor;
|
|
clutter_actor_set_opacity (actor, 0xff);
|
|
g_object_notify (G_OBJECT (container), "current-page");
|
|
}
|
|
else
|
|
clutter_actor_hide (actor);
|
|
|
|
g_signal_emit_by_name (container, "actor-added", actor);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_remove (ClutterContainer *container,
|
|
ClutterActor *actor)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (container)->priv;
|
|
|
|
GList *item = NULL;
|
|
|
|
item = g_list_find (priv->children, actor);
|
|
|
|
if (item == NULL)
|
|
{
|
|
g_warning ("Actor of type '%s' is not a child of container of type '%s'",
|
|
g_type_name (G_OBJECT_TYPE (actor)),
|
|
g_type_name (G_OBJECT_TYPE (container)));
|
|
return;
|
|
}
|
|
|
|
/* If it was the current page, select either the previous or
|
|
* the next, whichever exists first.
|
|
*/
|
|
if (actor == priv->current_page)
|
|
{
|
|
priv->current_page = item->prev ? item->prev->data :
|
|
(item->next ? item->next->data : NULL);
|
|
g_object_notify (G_OBJECT (container), "current-page");
|
|
}
|
|
|
|
g_object_ref (actor);
|
|
|
|
priv->children = g_list_delete_link (priv->children, item);
|
|
clutter_actor_unparent (actor);
|
|
|
|
g_signal_emit_by_name (container, "actor-removed", actor);
|
|
|
|
g_object_unref (actor);
|
|
|
|
mx_notebook_update_children (MX_NOTEBOOK (container));
|
|
}
|
|
|
|
static void
|
|
mx_notebook_foreach (ClutterContainer *container,
|
|
ClutterCallback callback,
|
|
gpointer callback_data)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (container)->priv;
|
|
|
|
g_list_foreach (priv->children, (GFunc) callback, callback_data);
|
|
}
|
|
|
|
static void
|
|
clutter_container_iface_init (ClutterContainerIface *iface)
|
|
{
|
|
iface->add = mx_notebook_add;
|
|
iface->remove = mx_notebook_remove;
|
|
iface->foreach = mx_notebook_foreach;
|
|
}
|
|
|
|
static MxFocusable *
|
|
mx_notebook_accept_focus (MxFocusable *focusable,
|
|
MxFocusHint hint)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (focusable)->priv;
|
|
|
|
if (priv->current_page && MX_IS_FOCUSABLE (priv->current_page))
|
|
return mx_focusable_accept_focus (MX_FOCUSABLE (priv->current_page), hint);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static MxFocusable *
|
|
mx_notebook_move_focus (MxFocusable *focusable,
|
|
MxFocusDirection direction,
|
|
MxFocusable *from)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
mx_focusable_iface_init (MxFocusableIface *iface)
|
|
{
|
|
iface->accept_focus = mx_notebook_accept_focus;
|
|
iface->move_focus = mx_notebook_move_focus;
|
|
}
|
|
|
|
static void
|
|
mx_notebook_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (object)->priv;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CURRENT_PAGE:
|
|
g_value_set_object (value, priv->current_page);
|
|
break;
|
|
|
|
case PROP_ENABLE_GESTURES:
|
|
g_value_set_boolean (value, priv->enable_gestures);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mx_notebook_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id)
|
|
{
|
|
case PROP_CURRENT_PAGE:
|
|
mx_notebook_set_current_page (MX_NOTEBOOK (object),
|
|
(ClutterActor *)g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_ENABLE_GESTURES:
|
|
mx_notebook_set_enable_gestures (MX_NOTEBOOK (object),
|
|
g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mx_notebook_dispose (GObject *object)
|
|
{
|
|
#ifdef HAVE_CLUTTER_GESTURE
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (object)->priv;
|
|
|
|
if (priv->gesture)
|
|
{
|
|
g_object_unref (priv->gesture);
|
|
priv->gesture = NULL;
|
|
}
|
|
#endif
|
|
|
|
G_OBJECT_CLASS (mx_notebook_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_destroy (ClutterActor *actor)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (actor)->priv;
|
|
|
|
g_list_foreach (priv->children, (GFunc) clutter_actor_destroy, NULL);
|
|
|
|
if (CLUTTER_ACTOR_CLASS (mx_notebook_parent_class)->destroy)
|
|
CLUTTER_ACTOR_CLASS (mx_notebook_parent_class)->destroy (actor);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_finalize (GObject *object)
|
|
{
|
|
G_OBJECT_CLASS (mx_notebook_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_get_preferred_width (ClutterActor *actor,
|
|
gfloat for_height,
|
|
gfloat *min_width_p,
|
|
gfloat *natural_width_p)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (actor)->priv;
|
|
GList *l;
|
|
MxPadding padding;
|
|
|
|
mx_widget_get_padding (MX_WIDGET (actor), &padding);
|
|
|
|
if (min_width_p)
|
|
*min_width_p = 0;
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = 0;
|
|
|
|
for (l = priv->children; l; l = l->next)
|
|
{
|
|
gfloat child_min, child_nat;
|
|
|
|
clutter_actor_get_preferred_width (CLUTTER_ACTOR (l->data), for_height,
|
|
&child_min, &child_nat);
|
|
|
|
if (min_width_p)
|
|
*min_width_p = MAX ((*min_width_p), child_min);
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = MAX ((*natural_width_p), child_nat);
|
|
}
|
|
|
|
if (min_width_p)
|
|
*min_width_p += padding.left + padding.right;
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p += padding.left + padding.right;
|
|
|
|
}
|
|
|
|
static void
|
|
mx_notebook_get_preferred_height (ClutterActor *actor,
|
|
gfloat for_width,
|
|
gfloat *min_height_p,
|
|
gfloat *natural_height_p)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (actor)->priv;
|
|
GList *l;
|
|
MxPadding padding;
|
|
|
|
mx_widget_get_padding (MX_WIDGET (actor), &padding);
|
|
|
|
if (min_height_p)
|
|
*min_height_p = 0;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = 0;
|
|
|
|
for (l = priv->children; l; l = l->next)
|
|
{
|
|
gfloat child_min, child_nat;
|
|
|
|
clutter_actor_get_preferred_height (CLUTTER_ACTOR (l->data), for_width,
|
|
&child_min, &child_nat);
|
|
|
|
if (min_height_p)
|
|
*min_height_p = MAX (*min_height_p, child_min);
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = MAX (*natural_height_p, child_nat);
|
|
}
|
|
|
|
if (min_height_p)
|
|
*min_height_p += padding.top + padding.bottom;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p += padding.top + padding.bottom;
|
|
|
|
}
|
|
|
|
static void
|
|
mx_notebook_paint (ClutterActor *actor)
|
|
{
|
|
GList *l;
|
|
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (actor)->priv;
|
|
|
|
CLUTTER_ACTOR_CLASS (mx_notebook_parent_class)->paint (actor);
|
|
|
|
for (l = priv->children; l; l = l->next)
|
|
{
|
|
ClutterActor *child = CLUTTER_ACTOR (l->data);
|
|
|
|
if (child == priv->current_page)
|
|
continue;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (child))
|
|
clutter_actor_paint (child);
|
|
}
|
|
|
|
if (priv->current_page)
|
|
clutter_actor_paint (priv->current_page);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_pick (ClutterActor *actor,
|
|
const ClutterColor *color)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (actor)->priv;
|
|
|
|
CLUTTER_ACTOR_CLASS (mx_notebook_parent_class)->pick (actor, color);
|
|
|
|
if (priv->current_page)
|
|
clutter_actor_paint (priv->current_page);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_allocate (ClutterActor *actor,
|
|
const ClutterActorBox *box,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
MxNotebookPrivate *priv = MX_NOTEBOOK (actor)->priv;
|
|
GList *l;
|
|
MxPadding padding;
|
|
ClutterActorBox childbox;
|
|
|
|
CLUTTER_ACTOR_CLASS (mx_notebook_parent_class)->allocate (actor, box, flags);
|
|
|
|
mx_widget_get_padding (MX_WIDGET (actor), &padding);
|
|
|
|
childbox.x1 = 0 + padding.left;
|
|
childbox.x2 = box->x2 - box->x1 - padding.right;
|
|
|
|
childbox.y1 = 0 + padding.top;
|
|
childbox.y2 = box->y2 - box->y1 - padding.bottom;
|
|
|
|
for (l = priv->children; l; l = l->next)
|
|
{
|
|
ClutterActor *child;
|
|
|
|
child = CLUTTER_ACTOR (l->data);
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (l->data))
|
|
clutter_actor_allocate (child, &childbox, flags);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_CLUTTER_GESTURE
|
|
static gboolean
|
|
mx_notebook_gesture_slide_event_cb (ClutterGesture *gesture,
|
|
ClutterGestureSlideEvent *event,
|
|
MxNotebook *book)
|
|
{
|
|
GList *item;
|
|
MxNotebookPrivate *priv = book->priv;
|
|
|
|
if (!priv->enable_gestures || !priv->current_page)
|
|
return FALSE;
|
|
|
|
item = g_list_find (priv->children, priv->current_page);
|
|
if (!item)
|
|
{
|
|
g_warning ("Current page not found in child list");
|
|
return FALSE;
|
|
}
|
|
|
|
if (event->direction % 2)
|
|
/* up, left (1, 3) */
|
|
mx_notebook_previous_page (book);
|
|
else
|
|
/* down, right (2, 4) */
|
|
mx_notebook_next_page (book);
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
mx_notebook_class_init (MxNotebookClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
GParamSpec *pspec;
|
|
|
|
g_type_class_add_private (klass, sizeof (MxNotebookPrivate));
|
|
|
|
object_class->get_property = mx_notebook_get_property;
|
|
object_class->set_property = mx_notebook_set_property;
|
|
object_class->dispose = mx_notebook_dispose;
|
|
object_class->finalize = mx_notebook_finalize;
|
|
|
|
actor_class->allocate = mx_notebook_allocate;
|
|
actor_class->get_preferred_width = mx_notebook_get_preferred_width;
|
|
actor_class->get_preferred_height = mx_notebook_get_preferred_height;
|
|
actor_class->paint = mx_notebook_paint;
|
|
actor_class->pick = mx_notebook_pick;
|
|
actor_class->destroy = mx_notebook_destroy;
|
|
|
|
pspec = g_param_spec_object ("current-page",
|
|
"Current page",
|
|
"The current ClutterActor being displayed",
|
|
CLUTTER_TYPE_ACTOR,
|
|
MX_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_CURRENT_PAGE, pspec);
|
|
|
|
pspec = g_param_spec_boolean ("enable-gestures",
|
|
"Enable Gestures",
|
|
"Enable use of pointer gestures to switch page",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_ENABLE_GESTURES, pspec);
|
|
}
|
|
|
|
static void
|
|
mx_notebook_init (MxNotebook *self)
|
|
{
|
|
self->priv = NOTEBOOK_PRIVATE (self);
|
|
}
|
|
|
|
ClutterActor *
|
|
mx_notebook_new (void)
|
|
{
|
|
return g_object_new (MX_TYPE_NOTEBOOK, NULL);
|
|
}
|
|
|
|
void
|
|
mx_notebook_set_current_page (MxNotebook *book,
|
|
ClutterActor *page)
|
|
{
|
|
MxNotebookPrivate *priv;
|
|
|
|
g_return_if_fail (MX_IS_NOTEBOOK (book));
|
|
g_return_if_fail (CLUTTER_IS_ACTOR (page));
|
|
|
|
priv = book->priv;
|
|
|
|
if (page == priv->current_page)
|
|
return;
|
|
|
|
priv->current_page = page;
|
|
|
|
/* ensure the correct child is visible */
|
|
mx_notebook_update_children (book);
|
|
|
|
g_object_notify (G_OBJECT (book), "current-page");
|
|
}
|
|
|
|
/**
|
|
* mx_notebook_get_current_page:
|
|
* @notebook: A #MxNotebook
|
|
*
|
|
* Get the current page
|
|
*
|
|
* Returns: (transfer none): the current page
|
|
*/
|
|
ClutterActor *
|
|
mx_notebook_get_current_page (MxNotebook *notebook)
|
|
{
|
|
g_return_val_if_fail (MX_IS_NOTEBOOK (notebook), NULL);
|
|
|
|
return notebook->priv->current_page;
|
|
}
|
|
|
|
/**
|
|
* mx_notebook_previous_page:
|
|
* @notebook: A #MxNotebook
|
|
*
|
|
* Change the current page to previous one.
|
|
*/
|
|
void
|
|
mx_notebook_previous_page (MxNotebook *notebook)
|
|
{
|
|
GList *item;
|
|
MxNotebookPrivate *priv;
|
|
|
|
g_return_if_fail (MX_IS_NOTEBOOK (notebook));
|
|
priv = notebook->priv;
|
|
|
|
item = g_list_find (priv->children, priv->current_page);
|
|
if (!item)
|
|
{
|
|
g_warning ("Current page not found in child list");
|
|
return;
|
|
}
|
|
|
|
if (item->prev)
|
|
mx_notebook_set_current_page (notebook,
|
|
(ClutterActor *)item->prev->data);
|
|
else
|
|
mx_notebook_set_current_page (notebook,
|
|
(ClutterActor *)g_list_last (item)->data);
|
|
}
|
|
|
|
/**
|
|
* mx_notebook_next_page:
|
|
* @notebook: A #MxNotebook
|
|
*
|
|
* Change the current page to next one.
|
|
*/
|
|
void
|
|
mx_notebook_next_page (MxNotebook *notebook)
|
|
{
|
|
GList *item;
|
|
MxNotebookPrivate *priv;
|
|
|
|
g_return_if_fail (MX_IS_NOTEBOOK (notebook));
|
|
priv = notebook->priv;
|
|
|
|
item = g_list_find (priv->children, priv->current_page);
|
|
if (!item)
|
|
{
|
|
g_warning ("Current page not found in child list");
|
|
return;
|
|
}
|
|
|
|
if (item->next)
|
|
mx_notebook_set_current_page (notebook,
|
|
(ClutterActor *)item->next->data);
|
|
else
|
|
mx_notebook_set_current_page (notebook,
|
|
(ClutterActor *)priv->children->data);
|
|
}
|
|
|
|
void
|
|
mx_notebook_set_enable_gestures (MxNotebook *book,
|
|
gboolean enabled)
|
|
{
|
|
MxNotebookPrivate *priv;
|
|
|
|
g_return_if_fail (MX_IS_NOTEBOOK (book));
|
|
|
|
priv = book->priv;
|
|
|
|
if (priv->enable_gestures != enabled)
|
|
{
|
|
priv->enable_gestures = enabled;
|
|
|
|
#ifndef HAVE_CLUTTER_GESTURE
|
|
g_warning ("Gestures are disabled as Clutter Gesture is not available");
|
|
#else
|
|
if (enabled && !priv->gesture)
|
|
{
|
|
priv->gesture = clutter_gesture_new (CLUTTER_ACTOR (book));
|
|
clutter_gesture_set_gesture_mask (priv->gesture,
|
|
CLUTTER_ACTOR (book),
|
|
GESTURE_MASK_SLIDE);
|
|
g_signal_connect (priv->gesture, "gesture-slide-event",
|
|
G_CALLBACK (mx_notebook_gesture_slide_event_cb),
|
|
book);
|
|
clutter_actor_set_reactive (CLUTTER_ACTOR (book), TRUE);
|
|
}
|
|
#endif
|
|
|
|
g_object_notify (G_OBJECT (book), "enable-gestures");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
mx_notebook_get_enable_gestures (MxNotebook *book)
|
|
{
|
|
g_return_val_if_fail (MX_IS_NOTEBOOK (book), FALSE);
|
|
|
|
return book->priv->enable_gestures;
|
|
}
|