gnome-control-center/shell/cc-notebook.c
Bastien Nocera 948867b6d3 shell: Fix resetting page selection
When the page selection happens when another page selection hasn't
started yet, we need to cancel the previous animation, or only one
of them might be effectively acted upon.

This fixes the page from being selected if the search term is changed
quickly (you'd end up at the overview page instead of staying on the
search results page).

Requires the clutter patch from:
https://bugzilla.gnome.org/show_bug.cgi?id=676334

https://bugzilla.gnome.org/show_bug.cgi?id=676328
2012-05-18 17:43:08 +01:00

507 lines
15 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright © 2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Written by:
* Bastien Nocera <hadess@hadess.net>
* Emmanuele Bassi <ebassi@linux.intel.com>
*/
#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "cc-notebook.h"
#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CC_TYPE_NOTEBOOK, CcNotebookPrivate))
/*
* Structure:
*
* Notebook
* +---- GtkClutterEmbed
* +---- ClutterStage
* +---- ClutterScrollActor:scroll
* +---- ClutterActor:bin
* +---- ClutterActor:frame<ClutterBinLayout>
* +---- GtkClutterActor:embed<GtkWidget>
*
* the frame element is needed to make the GtkClutterActor contents fill the allocation
*/
struct _CcNotebookPrivate
{
GtkWidget *embed;
ClutterActor *stage;
ClutterActor *scroll;
ClutterActor *bin;
int last_width;
GtkWidget *selected_page;
GList *pages; /* GList of GtkWidgets */
GList *removed_pages; /* GList of RemoveData, see setup_delayed_remove() */
};
enum
{
PROP_0,
PROP_CURRENT_PAGE,
LAST_PROP
};
static GParamSpec *obj_props[LAST_PROP] = { NULL, };
G_DEFINE_TYPE (CcNotebook, cc_notebook, GTK_TYPE_BOX)
static void
cc_notebook_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CcNotebookPrivate *priv = CC_NOTEBOOK (gobject)->priv;
switch (prop_id) {
case PROP_CURRENT_PAGE:
g_value_set_pointer (value, priv->selected_page);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
cc_notebook_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CcNotebook *self = CC_NOTEBOOK (gobject);
switch (prop_id) {
case PROP_CURRENT_PAGE:
cc_notebook_select_page (self, g_value_get_pointer (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
cc_notebook_finalize (GObject *gobject)
{
CcNotebook *self = CC_NOTEBOOK (gobject);
g_list_free_full (self->priv->removed_pages, (GDestroyNotify) g_free);
self->priv->removed_pages = NULL;
g_list_free (self->priv->pages);
self->priv->pages = NULL;
G_OBJECT_CLASS (cc_notebook_parent_class)->finalize (gobject);
}
static GtkSizeRequestMode
cc_notebook_get_request_mode (GtkWidget *widget)
{
CcNotebook *notebook;
GtkWidget *target;
notebook = CC_NOTEBOOK (widget);
target = notebook->priv->selected_page ? notebook->priv->selected_page : notebook->priv->embed;
return gtk_widget_get_request_mode (target);
}
static void
cc_notebook_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_height (notebook->priv->selected_page, minimum_height, natural_height);
return;
}
*minimum_height = 0;
*natural_height = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_height (page, &page_min, &page_nat);
*minimum_height = MAX(page_min, *minimum_height);
*natural_height = MAX(page_nat, *natural_height);
}
}
static void
cc_notebook_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_width_for_height (notebook->priv->selected_page, height, minimum_width, natural_width);
return;
}
*minimum_width = 0;
*natural_width = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_width_for_height (page, height, &page_min, &page_nat);
*minimum_width = MAX(page_min, *minimum_width);
*natural_width = MAX(page_nat, *natural_width);
}
}
static void
cc_notebook_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_height (notebook->priv->selected_page, minimum_width, natural_width);
return;
}
*minimum_width = 0;
*natural_width = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_width (page, &page_min, &page_nat);
*minimum_width = MAX(page_min, *minimum_width);
*natural_width = MAX(page_nat, *natural_width);
}
}
static void
cc_notebook_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height,
gint *natural_height)
{
CcNotebook *notebook;
GList *l;
notebook = CC_NOTEBOOK (widget);
if (notebook->priv->selected_page == NULL) {
gtk_widget_get_preferred_height_for_width (notebook->priv->selected_page, width, minimum_height, natural_height);
return;
}
*minimum_height = 0;
*natural_height = 0;
for (l = notebook->priv->pages; l != NULL; l = l->next) {
GtkWidget *page = l->data;
int page_min, page_nat;
gtk_widget_get_preferred_height_for_width (page, width, &page_min, &page_nat);
*minimum_height = MAX(page_min, *minimum_height);
*natural_height = MAX(page_nat, *natural_height);
}
}
static void
cc_notebook_class_init (CcNotebookClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
g_type_class_add_private (klass, sizeof (CcNotebookPrivate));
obj_props[PROP_CURRENT_PAGE] =
g_param_spec_pointer (g_intern_static_string ("current-page"),
"Current Page",
"The currently selected page widget",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
gobject_class->get_property = cc_notebook_get_property;
gobject_class->set_property = cc_notebook_set_property;
gobject_class->finalize = cc_notebook_finalize;
g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
widget_class->get_request_mode = cc_notebook_get_request_mode;
widget_class->get_preferred_height = cc_notebook_get_preferred_height;
widget_class->get_preferred_width_for_height = cc_notebook_get_preferred_width_for_height;
widget_class->get_preferred_width = cc_notebook_get_preferred_width;
widget_class->get_preferred_height_for_width = cc_notebook_get_preferred_height_for_width;
}
static void
on_embed_size_allocate (GtkWidget *embed,
GtkAllocation *allocation,
CcNotebook *self)
{
ClutterActorIter iter;
ClutterActor *child;
ClutterActor *frame;
float page_w, page_h;
float offset = 0.f;
ClutterPoint pos;
if (self->priv->selected_page == NULL)
return;
page_w = allocation->width;
page_h = allocation->height;
clutter_actor_iter_init (&iter, self->priv->bin);
while (clutter_actor_iter_next (&iter, &child)) {
clutter_actor_set_x (child, offset);
clutter_actor_set_size (child, page_w, page_h);
offset += page_w;
}
/* This stops the non-animated scrolling from happening
* if we're still scrolling there */
if (clutter_actor_get_transition (self->priv->scroll, "scroll-to") != NULL)
return;
self->priv->last_width = allocation->width;
frame = g_object_get_data (G_OBJECT (self->priv->selected_page),
"cc-notebook-frame");
pos.y = 0;
pos.x = clutter_actor_get_x (frame);
clutter_scroll_actor_scroll_to_point (CLUTTER_SCROLL_ACTOR (self->priv->scroll), &pos);
}
static void
cc_notebook_init (CcNotebook *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CC_TYPE_NOTEBOOK, CcNotebookPrivate);
self->priv->embed = gtk_clutter_embed_new ();
gtk_widget_push_composite_child ();
gtk_container_add (GTK_CONTAINER (self), self->priv->embed);
gtk_widget_pop_composite_child ();
g_signal_connect (self->priv->embed, "size-allocate", G_CALLBACK (on_embed_size_allocate), self);
gtk_widget_show (self->priv->embed);
self->priv->stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (self->priv->embed));
self->priv->scroll = clutter_scroll_actor_new ();
clutter_scroll_actor_set_scroll_mode (CLUTTER_SCROLL_ACTOR (self->priv->scroll),
CLUTTER_SCROLL_HORIZONTALLY);
clutter_actor_add_constraint (self->priv->scroll, clutter_bind_constraint_new (self->priv->stage, CLUTTER_BIND_SIZE, 0.f));
clutter_actor_add_child (self->priv->stage, self->priv->scroll);
self->priv->bin = clutter_actor_new ();
clutter_actor_add_child (self->priv->scroll, self->priv->bin);
self->priv->selected_page = NULL;
gtk_widget_set_name (GTK_WIDGET (self), "GtkBox");
}
GtkWidget *
cc_notebook_new (void)
{
return g_object_new (CC_TYPE_NOTEBOOK, NULL);
}
static void
_cc_notebook_select_page (CcNotebook *self,
GtkWidget *widget,
int index)
{
ClutterPoint pos;
g_return_if_fail (CC_IS_NOTEBOOK (self));
g_return_if_fail (GTK_IS_WIDGET (widget));
pos.y = 0;
pos.x = self->priv->last_width * index;
if (clutter_actor_get_transition (self->priv->scroll, "scroll-to") != NULL)
clutter_actor_remove_transition (self->priv->scroll, "scroll-to");
clutter_actor_save_easing_state (self->priv->scroll);
clutter_actor_set_easing_duration (self->priv->scroll, 500);
clutter_scroll_actor_scroll_to_point (CLUTTER_SCROLL_ACTOR (self->priv->scroll), &pos);
clutter_actor_restore_easing_state (self->priv->scroll);
/* Remember the last selected page */
self->priv->selected_page = widget;
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURRENT_PAGE]);
}
void
cc_notebook_select_page (CcNotebook *self,
GtkWidget *widget)
{
int i, n_children;
GList *children, *l;
ClutterActor *frame;
gboolean found;
if (widget == self->priv->selected_page)
return;
found = FALSE;
frame = g_object_get_data (G_OBJECT (widget), "cc-notebook-frame");
n_children = clutter_actor_get_n_children (self->priv->bin);
children = clutter_actor_get_children (self->priv->bin);
for (i = 0, l = children; i < n_children; i++, l = l->next) {
if (frame == l->data) {
_cc_notebook_select_page (self, widget, i);
found = TRUE;
break;
}
}
g_list_free (children);
if (found == FALSE)
g_warning ("Could not find widget '%p' in CcNotebook '%p'", widget, self);
}
void
cc_notebook_add_page (CcNotebook *self,
GtkWidget *widget)
{
ClutterActor *frame;
ClutterActor *embed;
int res;
g_return_if_fail (CC_IS_NOTEBOOK (self));
g_return_if_fail (GTK_IS_WIDGET (widget));
frame = clutter_actor_new ();
clutter_actor_set_layout_manager (frame, clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FILL,
CLUTTER_BIN_ALIGNMENT_FILL));
embed = gtk_clutter_actor_new_with_contents (widget);
g_object_set_data (G_OBJECT (widget), "cc-notebook-frame", frame);
clutter_actor_add_child (frame, embed);
gtk_widget_show (widget);
res = clutter_actor_get_n_children (self->priv->bin);
clutter_actor_insert_child_at_index (self->priv->bin, frame, res);
self->priv->pages = g_list_prepend (self->priv->pages, widget);
if (self->priv->selected_page == NULL)
_cc_notebook_select_page (self, widget, res);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
typedef struct {
CcNotebook *notebook;
ClutterActor *frame;
} RemoveData;
static void
remove_on_complete (ClutterTimeline *timeline,
RemoveData *data)
{
data->notebook->priv->removed_pages = g_list_remove (data->notebook->priv->removed_pages, data);
clutter_actor_destroy (data->frame);
g_free (data);
}
static gboolean
setup_delayed_remove (CcNotebook *self,
ClutterActor *frame)
{
ClutterTransition *transition;
RemoveData *data;
transition = clutter_actor_get_transition (self->priv->scroll, "scroll-to");
if (transition == NULL)
return FALSE;
data = g_new0 (RemoveData, 1);
data->notebook = self;
data->frame = frame;
self->priv->removed_pages = g_list_prepend (self->priv->removed_pages, data);
g_signal_connect (transition, "completed",
G_CALLBACK (remove_on_complete), data);
return TRUE;
}
void
cc_notebook_remove_page (CcNotebook *self,
GtkWidget *widget)
{
ClutterActorIter iter;
ClutterActor *child, *frame;
int index;
g_return_if_fail (CC_IS_NOTEBOOK (self));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (widget != self->priv->selected_page);
frame = g_object_get_data (G_OBJECT (widget), "cc-notebook-frame");
index = 0;
clutter_actor_iter_init (&iter, self->priv->bin);
while (clutter_actor_iter_next (&iter, &child)) {
if (frame == child) {
if (setup_delayed_remove (self, frame) == FALSE)
clutter_actor_iter_remove (&iter);
break;
}
index++;
}
self->priv->pages = g_list_remove (self->priv->pages, widget);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
GtkWidget *
cc_notebook_get_selected_page (CcNotebook *self)
{
g_return_val_if_fail (CC_IS_NOTEBOOK (self), NULL);
return self->priv->selected_page;
}