diff --git a/src/Makefile.am b/src/Makefile.am index faf4494e..d00a56f8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,7 +48,9 @@ gnome_calendar_SOURCES = \ gcal-manager.c \ gcal-manager.h \ gcal-utils.c \ - gcal-utils.h + gcal-utils.h \ + gtk-notification.c \ + gtk-notification.h gnome_calendar_LDFLAGS = \ diff --git a/src/gcal-window.c b/src/gcal-window.c index e3d5b68a..f7aaee11 100644 --- a/src/gcal-window.c +++ b/src/gcal-window.c @@ -117,7 +117,6 @@ gcal_window_constructed (GObject *object) ClutterActor *body_actor; ClutterLayoutManager *body_layout_manager; ClutterLayoutManager *contents_layout_manager; - GtkWidget *holder_parent; GtkWidget *holder; GtkStyleContext *context; @@ -228,10 +227,27 @@ gcal_window_constructed (GObject *object) CLUTTER_BIN_ALIGNMENT_START); clutter_actor_hide (priv->sources_actor); - holder_parent = gtk_widget_get_parent (holder); - gtk_style_context_add_class ( - gtk_widget_get_style_context (holder_parent), - "sources"); + context = + gtk_widget_get_style_context ( + gtk_clutter_actor_get_widget ( + GTK_CLUTTER_ACTOR (priv->sources_actor))); + gtk_style_context_add_class (context, "overlay"); + + /* notifications manager */ + priv->notification_actor = gtk_clutter_actor_new (); + clutter_actor_set_opacity (priv->notification_actor, 0); + clutter_actor_set_clip_to_allocation (priv->notification_actor, TRUE); + clutter_bin_layout_add (CLUTTER_BIN_LAYOUT (contents_layout_manager), + priv->notification_actor, + CLUTTER_BIN_ALIGNMENT_CENTER, + CLUTTER_BIN_ALIGNMENT_START); + + context = + gtk_widget_get_style_context ( + gtk_clutter_actor_get_widget ( + GTK_CLUTTER_ACTOR (priv->notification_actor))); + gtk_style_context_add_class (context, "overlay"); + clutter_actor_hide (priv->notification_actor); /* signals connection/handling */ g_signal_connect (priv->main_toolbar, @@ -650,3 +666,19 @@ gcal_window_new (GcalApplication *app) return GTK_WIDGET (win); } + +void +gcal_window_show_notification (GcalWindow *window, + GtkWidget *notification) +{ + GcalWindowPrivate *priv; + + g_return_if_fail (GCAL_IS_WINDOW (window)); + priv = window->priv; + + gtk_container_add ( + GTK_CONTAINER (gtk_clutter_actor_get_widget (GTK_CLUTTER_ACTOR (priv->notification_actor))), + notification); + gtk_widget_show_all (notification); + clutter_actor_show (priv->notification_actor); +} diff --git a/src/gcal-window.h b/src/gcal-window.h index f87750b7..0ac0de67 100644 --- a/src/gcal-window.h +++ b/src/gcal-window.h @@ -51,9 +51,12 @@ struct _GcalWindowClass }; -GType gcal_window_get_type (void); +GType gcal_window_get_type (void); -GtkWidget* gcal_window_new (GcalApplication *app); +GtkWidget* gcal_window_new (GcalApplication *app); + +void gcal_window_show_notification (GcalWindow *window, + GtkWidget *notification); G_END_DECLS diff --git a/src/gtk-notification.c b/src/gtk-notification.c new file mode 100644 index 00000000..a88fd70d --- /dev/null +++ b/src/gtk-notification.c @@ -0,0 +1,912 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * gtk-notification.c + * Copyright (C) Erick Pérez Castellanos 2011 + * + gtk-notification.c 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 3 of the License, or + * (at your option) any later version. + * + * gtk-notification.c 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 program. If not, see ."; + */ + +#include "gtk-notification.h" + +/** + * SECTION:gtknotification + * @short_description: Report notification messages to the user + * @include: gtk/gtk.h + * @see_also: #GtkStatusbar, #GtkMessageDialog, #GtkInfoBar + * + * #GtkNotification is a widget made for showing notifications to + * the user, allowing them to close the notification or wait for it + * to time out. + * + * #GtkNotification provides one signal (#GtkNotification::dismissed), for when the notification + * times out or is closed. + * + */ + +#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB +#define SHADOW_OFFSET_X 2 +#define SHADOW_OFFSET_Y 3 +#define ANIMATION_TIME 200 /* msec */ +#define ANIMATION_STEP 40 /* msec */ + +enum { + PROP_0, + PROP_TIMEOUT +}; + +struct _GtkNotificationPrivate { + GtkWidget *close_button; + + GdkWindow *bin_window; + + int animate_y; /* from 0 to allocation.height */ + gboolean waiting_for_viewable; + gboolean revealed; + gboolean dismissed; + gboolean sent_dismissed; + guint animate_timeout; + + guint timeout; + guint timeout_source_id; +}; + +enum { + DISMISSED, + LAST_SIGNAL +}; + +static guint notification_signals[LAST_SIGNAL] = { 0 }; + +static gboolean gtk_notification_draw (GtkWidget *widget, + cairo_t *cr); +static void gtk_notification_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_notification_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); +static void gtk_notification_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size); +static void gtk_notification_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); +static void gtk_notification_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gtk_notification_timeout_cb (gpointer user_data); +static void gtk_notification_style_updated (GtkWidget *widget); +static void gtk_notification_show (GtkWidget *widget); +static void gtk_notification_add (GtkContainer *container, + GtkWidget *child); + +/* signals handlers */ +static void gtk_notification_close_button_clicked_cb (GtkWidget *widget, + gpointer user_data); + +G_DEFINE_TYPE(GtkNotification, gtk_notification, GTK_TYPE_BIN); + +static void +gtk_notification_init (GtkNotification *notification) +{ + GtkWidget *close_button_image; + GtkStyleContext *context; + GtkNotificationPrivate *priv; + + context = gtk_widget_get_style_context (GTK_WIDGET (notification)); + gtk_style_context_add_class (context, "contacts-notification"); + + + gtk_widget_set_halign (GTK_WIDGET (notification), GTK_ALIGN_CENTER); + gtk_widget_set_valign (GTK_WIDGET (notification), GTK_ALIGN_START); + + gtk_widget_set_has_window (GTK_WIDGET (notification), TRUE); + + gtk_widget_push_composite_child (); + + priv = notification->priv = + G_TYPE_INSTANCE_GET_PRIVATE (notification, + GTK_TYPE_NOTIFICATION, + GtkNotificationPrivate); + + priv->animate_y = 0; + priv->close_button = gtk_button_new (); + gtk_widget_set_parent (priv->close_button, GTK_WIDGET (notification)); + gtk_widget_show (priv->close_button); + g_object_set (priv->close_button, + "relief", GTK_RELIEF_NONE, + "focus-on-click", FALSE, + NULL); + g_signal_connect (priv->close_button, + "clicked", + G_CALLBACK (gtk_notification_close_button_clicked_cb), + notification); + close_button_image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (notification->priv->close_button), close_button_image); + + gtk_widget_pop_composite_child (); + + priv->timeout_source_id = 0; +} + +static void +gtk_notification_finalize (GObject *object) +{ + GtkNotification *notification; + GtkNotificationPrivate *priv; + + g_return_if_fail (GTK_IS_NOTIFICATION (object)); + + notification = GTK_NOTIFICATION (object); + priv = notification->priv; + + if (priv->animate_timeout != 0) + g_source_remove (priv->animate_timeout); + + if (priv->timeout_source_id != 0) + g_source_remove (priv->timeout_source_id); + + G_OBJECT_CLASS (gtk_notification_parent_class)->finalize (object); +} + +static void +gtk_notification_destroy (GtkWidget *widget) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + + if (!priv->sent_dismissed) + { + g_signal_emit (notification, notification_signals[DISMISSED], 0); + priv->sent_dismissed = TRUE; + } + + if (priv->close_button) + { + gtk_widget_unparent (priv->close_button); + priv->close_button = NULL; + } + + GTK_WIDGET_CLASS (gtk_notification_parent_class)->destroy (widget); +} + +static void +gtk_notification_realize (GtkWidget *widget) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkBin *bin = GTK_BIN (widget); + GtkAllocation allocation; + GtkStyleContext *context; + GtkWidget *child; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + + attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; + + window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gtk_widget_set_window (widget, window); + gdk_window_set_user_data (window, notification); + + attributes.x = 0; + attributes.y = attributes.height + priv->animate_y; + attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_VISIBILITY_NOTIFY_MASK; + + priv->bin_window = gdk_window_new (window, &attributes, attributes_mask); + gdk_window_set_user_data (priv->bin_window, notification); + + child = gtk_bin_get_child (bin); + if (child) + gtk_widget_set_parent_window (child, priv->bin_window); + gtk_widget_set_parent_window (priv->close_button, priv->bin_window); + + context = gtk_widget_get_style_context (widget); + gtk_style_context_set_background (context, window); + gtk_style_context_set_background (context, priv->bin_window); + + gdk_window_show (priv->bin_window); +} + +static void +gtk_notification_unrealize (GtkWidget *widget) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + + gdk_window_set_user_data (priv->bin_window, NULL); + gdk_window_destroy (priv->bin_window); + priv->bin_window = NULL; + + GTK_WIDGET_CLASS (gtk_notification_parent_class)->unrealize (widget); +} + +static int +animation_target (GtkNotification *notification) +{ + GtkNotificationPrivate *priv = notification->priv; + GtkAllocation allocation; + + if (priv->revealed) { + gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation); + return allocation.height; + } else { + return 0; + } +} + +static gboolean +animation_timeout_cb (gpointer user_data) +{ + GtkNotification *notification = GTK_NOTIFICATION (user_data); + GtkNotificationPrivate *priv = notification->priv; + GtkAllocation allocation; + int target, delta; + + target = animation_target (notification); + + if (priv->animate_y != target) { + gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation); + + delta = allocation.height * ANIMATION_STEP / ANIMATION_TIME; + + if (priv->revealed) + priv->animate_y += delta; + else + priv->animate_y -= delta; + + priv->animate_y = CLAMP (priv->animate_y, 0, allocation.height); + + if (priv->bin_window != NULL) + gdk_window_move (priv->bin_window, + 0, + -allocation.height + priv->animate_y); + return TRUE; + } + + if (priv->dismissed && priv->animate_y == 0) + gtk_widget_destroy (GTK_WIDGET (notification)); + + priv->animate_timeout = 0; + return FALSE; +} + +static void +start_animation (GtkNotification *notification) +{ + GtkNotificationPrivate *priv = notification->priv; + int target; + + if (priv->animate_timeout != 0) + return; /* Already running */ + + target = animation_target (notification); + if (priv->animate_y != target) + notification->priv->animate_timeout = + gdk_threads_add_timeout (ANIMATION_STEP, + animation_timeout_cb, + notification); +} + +static void +gtk_notification_show (GtkWidget *widget) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + + GTK_WIDGET_CLASS (gtk_notification_parent_class)->show (widget); + priv->revealed = TRUE; + priv->waiting_for_viewable = TRUE; +} + +static void +gtk_notification_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GtkNotification *notification = GTK_NOTIFICATION (object); + + g_return_if_fail (GTK_IS_NOTIFICATION (object)); + + switch (prop_id) { + case PROP_TIMEOUT: + gtk_notification_set_timeout (notification, + g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_notification_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GtkNotification *notification; + + g_return_if_fail (GTK_IS_NOTIFICATION (object)); + notification = GTK_NOTIFICATION (object); + + switch (prop_id) { + case PROP_TIMEOUT: + g_value_set_uint (value, notification->priv->timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_notification_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GtkBin *bin = GTK_BIN (container); + GtkNotification *notification = GTK_NOTIFICATION (container); + GtkNotificationPrivate *priv = notification->priv; + GtkWidget *child; + + child = gtk_bin_get_child (bin); + if (child) + (* callback) (child, callback_data); + + if (include_internals) + (* callback) (priv->close_button, callback_data); +} + +static gboolean +gtk_notification_visibility_notify_event (GtkWidget *widget, + GdkEventVisibility *event) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + + if (priv->waiting_for_viewable) + { + start_animation (notification); + priv->waiting_for_viewable = FALSE; + } + + if (notification->priv->timeout_source_id == 0) + notification->priv->timeout_source_id = + gdk_threads_add_timeout (notification->priv->timeout * 1000, + gtk_notification_timeout_cb, + widget); + + return FALSE; +} + +static void +gtk_notification_class_init (GtkNotificationClass *klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->finalize = gtk_notification_finalize; + object_class->set_property = gtk_notification_set_property; + object_class->get_property = gtk_notification_get_property; + + widget_class->show = gtk_notification_show; + widget_class->destroy = gtk_notification_destroy; + widget_class->get_preferred_width = gtk_notification_get_preferred_width; + widget_class->get_preferred_height_for_width = gtk_notification_get_preferred_height_for_width; + widget_class->get_preferred_height = gtk_notification_get_preferred_height; + widget_class->get_preferred_width_for_height = gtk_notification_get_preferred_width_for_height; + widget_class->size_allocate = gtk_notification_size_allocate; + widget_class->draw = gtk_notification_draw; + widget_class->realize = gtk_notification_realize; + widget_class->unrealize = gtk_notification_unrealize; + widget_class->style_updated = gtk_notification_style_updated; + widget_class->visibility_notify_event = gtk_notification_visibility_notify_event; + + container_class->add = gtk_notification_add; + container_class->forall = gtk_notification_forall; + gtk_container_class_handle_border_width (container_class); + + + /** + * GtkNotification:timeout: + * + * The time it takes to hide the widget, in seconds. + * + * Since: 0.1 + */ + g_object_class_install_property (object_class, + PROP_TIMEOUT, + g_param_spec_uint("timeout", "timeout", + "The time it takes to hide the widget, in seconds", + 0, G_MAXUINT, 10, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + notification_signals[DISMISSED] = g_signal_new ("dismissed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkNotificationClass, dismissed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private (object_class, sizeof (GtkNotificationPrivate)); +} + +static void +draw_shadow_box (cairo_t *cr, GdkRectangle rect, int left_border, int right_border, + int bottom_border, double inner_alpha) +{ + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + double x0, x1, x2, x3; + double y0, y2, y3; + + cairo_save (cr); + + x0 = rect.x; + x1 = rect.x + left_border; + x2 = rect.x + rect.width - right_border; + x3 = rect.x + rect.width; + + y0 = rect.y; + y2 = rect.y + rect.height - bottom_border; + y3 = rect.y + rect.height; + + /* Bottom border */ + + pattern = cairo_pattern_create_linear(0, y2, 0, y3); + + cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, inner_alpha); + cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, 0.0); + + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + cairo_rectangle(cr, x1, y2, x2 - x1, y3 - y2); + cairo_fill(cr); + + /* Left border */ + + pattern = cairo_pattern_create_linear(x0, 0, x1, 0); + + cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, 0.0); + cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, inner_alpha); + + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + cairo_rectangle(cr, x0, y0, x1 - x0, y2 - y0); + cairo_fill(cr); + + /* Right border */ + + pattern = cairo_pattern_create_linear(x2, 0, x3, 0); + + cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, inner_alpha); + cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, 0.0); + + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + cairo_rectangle(cr, x2, y0, x3 - x2, y2 - y0); + cairo_fill(cr); + + /* SW corner */ + + pattern = cairo_pattern_create_radial(0, 0, 0, 0.0, 0, 1.0); + cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0, 0, 0, inner_alpha); + cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0, 0, 0, 0.0); + + cairo_matrix_init_scale (&matrix, 1.0 / left_border, 1.0 / bottom_border); + cairo_matrix_translate (&matrix, - x1, -y2); + cairo_pattern_set_matrix (pattern, &matrix); + + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + cairo_rectangle(cr, x0, y2, x1 - x0, y3 - y2); + cairo_fill(cr); + + /* SE corner */ + + pattern = cairo_pattern_create_radial(0, 0, 0, 0, 0, 1.0); + cairo_pattern_add_color_stop_rgba(pattern, 0.0, 0.0, 0, 0, inner_alpha); + cairo_pattern_add_color_stop_rgba(pattern, 1.0, 0.0, 0, 0, 0.0); + + cairo_matrix_init_scale (&matrix, 1.0 / left_border, 1.0 / bottom_border); + cairo_matrix_translate (&matrix, - x2, -y2); + cairo_pattern_set_matrix (pattern, &matrix); + + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + cairo_rectangle(cr, x2, y2, x3 - x2, y3 - y2); + cairo_fill(cr); + + cairo_restore (cr); +} + +static void +get_padding_and_border (GtkNotification *notification, + GtkBorder *border) +{ + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder tmp; + + context = gtk_widget_get_style_context (GTK_WIDGET (notification)); + state = gtk_widget_get_state_flags (GTK_WIDGET (notification)); + + gtk_style_context_get_padding (context, state, border); + + gtk_style_context_get_border (context, state, &tmp); + border->top += tmp.top; + border->right += tmp.right; + border->bottom += tmp.bottom; + border->left += tmp.left; +} + +static gboolean +gtk_notification_draw (GtkWidget *widget, cairo_t *cr) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkStyleContext *context; + GdkRectangle rect; + int inner_radius; + + if (gtk_cairo_should_draw_window (cr, priv->bin_window)) + { + gtk_widget_get_allocation (widget, &rect); + + context = gtk_widget_get_style_context(widget); + + inner_radius = 5; + draw_shadow_box (cr, rect, SHADOW_OFFSET_X + inner_radius, SHADOW_OFFSET_X + inner_radius, + SHADOW_OFFSET_Y + inner_radius, 0.8); + + gtk_style_context_save (context); + gtk_render_background (context, cr, + SHADOW_OFFSET_X, 0, + gtk_widget_get_allocated_width (widget) - 2 *SHADOW_OFFSET_X, + gtk_widget_get_allocated_height (widget) - SHADOW_OFFSET_Y); + gtk_render_frame (context,cr, + SHADOW_OFFSET_X, 0, + gtk_widget_get_allocated_width (widget) - 2 *SHADOW_OFFSET_X, + gtk_widget_get_allocated_height (widget) - SHADOW_OFFSET_Y); + + gtk_style_context_restore (context); + + if (GTK_WIDGET_CLASS (gtk_notification_parent_class)->draw) + GTK_WIDGET_CLASS (gtk_notification_parent_class)->draw(widget, cr); + } + + return FALSE; +} + +static void +gtk_notification_add (GtkContainer *container, + GtkWidget *child) +{ + GtkBin *bin = GTK_BIN (container); + GtkNotification *notification = GTK_NOTIFICATION (bin); + GtkNotificationPrivate *priv = notification->priv; + + g_return_if_fail (gtk_bin_get_child (bin) == NULL); + + gtk_widget_set_parent_window (child, priv->bin_window); + + GTK_CONTAINER_CLASS (gtk_notification_parent_class)->add (container, child); +} + + +static void +gtk_notification_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkBin *bin = GTK_BIN (widget); + gint child_min, child_nat; + GtkWidget *child; + GtkBorder padding; + gint minimum, natural; + + get_padding_and_border (notification, &padding); + + minimum = 0; + natural = 0; + + child = gtk_bin_get_child (bin); + if (child && gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_width (child, + &child_min, &child_nat); + minimum += child_min; + natural += child_nat; + } + + gtk_widget_get_preferred_width (priv->close_button, + &child_min, &child_nat); + minimum += child_min; + natural += child_nat; + + + minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X; + natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X; + + if (minimum_size) + *minimum_size = minimum; + + if (natural_size) + *natural_size = natural; +} + +static void +gtk_notification_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkBin *bin = GTK_BIN (widget); + gint child_min, child_nat, child_height; + GtkWidget *child; + GtkBorder padding; + gint minimum, natural; + + get_padding_and_border (notification, &padding); + + minimum = 0; + natural = 0; + + child_height = height - SHADOW_OFFSET_Y - padding.top - padding.bottom; + + child = gtk_bin_get_child (bin); + if (child && gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_width_for_height (child, child_height, + &child_min, &child_nat); + minimum += child_min; + natural += child_nat; + } + + gtk_widget_get_preferred_width_for_height (priv->close_button, child_height, + &child_min, &child_nat); + minimum += child_min; + natural += child_nat; + + minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X; + natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X; + + if (minimum_width) + *minimum_width = minimum; + + if (natural_width) + *natural_width = natural; +} + +static void +gtk_notification_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkBin *bin = GTK_BIN (widget); + gint child_min, child_nat, child_width, button_width; + GtkWidget *child; + GtkBorder padding; + gint minimum, natural; + + get_padding_and_border (notification, &padding); + + gtk_widget_get_preferred_height (priv->close_button, + &minimum, &natural); + gtk_widget_get_preferred_width (priv->close_button, + NULL, &button_width); + + child = gtk_bin_get_child (bin); + if (child && gtk_widget_get_visible (child)) + { + child_width = width - 2 * SHADOW_OFFSET_X - padding.left - padding.top - button_width; + + gtk_widget_get_preferred_height_for_width (child, child_width, + &child_min, &child_nat); + minimum = MAX (minimum, child_min); + natural = MAX (natural, child_nat); + } + + minimum += padding.top + padding.top + SHADOW_OFFSET_Y; + natural += padding.top + padding.top + SHADOW_OFFSET_Y; + + if (minimum_height) + *minimum_height = minimum; + + if (natural_height) + *natural_height = natural; +} + +static void +gtk_notification_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkBin *bin = GTK_BIN (widget); + gint child_min, child_nat; + GtkWidget *child; + GtkBorder padding; + gint minimum, natural; + + get_padding_and_border (notification, &padding); + + gtk_widget_get_preferred_height (priv->close_button, + &minimum, &natural); + + child = gtk_bin_get_child (bin); + if (child && gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_height (child, + &child_min, &child_nat); + minimum = MAX (minimum, child_min); + natural = MAX (natural, child_nat); + } + + minimum += padding.top + padding.top + SHADOW_OFFSET_Y; + natural += padding.top + padding.top + SHADOW_OFFSET_Y; + + if (minimum_height) + *minimum_height = minimum; + + if (natural_height) + *natural_height = natural; +} + +static void +gtk_notification_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + GtkBin *bin = GTK_BIN (widget); + GtkAllocation child_allocation; + GtkBorder padding; + GtkWidget *child; + int button_width; + + gtk_widget_set_allocation (widget, allocation); + + /* If somehow the notification changes while not hidden + and we're not animating, immediately follow the resize */ + if (priv->animate_y > 0 && + !priv->animate_timeout) + priv->animate_y = allocation->height; + + get_padding_and_border (notification, &padding); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (gtk_widget_get_window (widget), + allocation->x, + allocation->y, + allocation->width, + allocation->height); + gdk_window_move_resize (priv->bin_window, + 0, + -allocation->height + priv->animate_y, + allocation->width, + allocation->height); + } + + child_allocation.x = SHADOW_OFFSET_X + padding.left; + child_allocation.y = padding.top; + child_allocation.height = MAX (1, allocation->height - SHADOW_OFFSET_Y - padding.top - padding.bottom); + gtk_widget_get_preferred_width_for_height (priv->close_button, child_allocation.height, + NULL, &button_width); + + child_allocation.width = MAX (1, allocation->width - 2 * SHADOW_OFFSET_X - padding.left - padding.right - button_width); + + child = gtk_bin_get_child (bin); + if (child && gtk_widget_get_visible (child)) + gtk_widget_size_allocate (child, &child_allocation); + + child_allocation.x += child_allocation.width; + child_allocation.width = button_width; + + gtk_widget_size_allocate (priv->close_button, &child_allocation); +} + +static gboolean +gtk_notification_timeout_cb (gpointer user_data) +{ + GtkNotification *notification = GTK_NOTIFICATION (user_data); + + gtk_notification_dismiss (notification); + + return FALSE; +} + +static void +gtk_notification_style_updated (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (gtk_notification_parent_class)->style_updated (widget); + + if (gtk_widget_get_realized (widget)) + { + GtkStyleContext *context; + GtkNotification *notification = GTK_NOTIFICATION (widget); + GtkNotificationPrivate *priv = notification->priv; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_set_background (context, priv->bin_window); + gtk_style_context_set_background (context, gtk_widget_get_window (widget)); + } +} + +void +gtk_notification_set_timeout (GtkNotification *notification, + guint timeout_msec) +{ + GtkNotificationPrivate *priv = notification->priv; + + priv->timeout = timeout_msec; + g_object_notify (G_OBJECT (notification), "timeout"); +} + +void +gtk_notification_dismiss (GtkNotification *notification) +{ + GtkNotificationPrivate *priv = notification->priv; + + if (notification->priv->timeout_source_id) + { + g_source_remove (notification->priv->timeout_source_id); + notification->priv->timeout_source_id = 0; + } + + priv->dismissed = TRUE; + priv->revealed = FALSE; + start_animation (notification); +} + +static void +gtk_notification_close_button_clicked_cb (GtkWidget *widget, gpointer user_data) +{ + GtkNotification *notification = GTK_NOTIFICATION(user_data); + + gtk_notification_dismiss (notification); +} + +GtkWidget * +gtk_notification_new (void) +{ + return g_object_new (GTK_TYPE_NOTIFICATION, NULL); +} diff --git a/src/gtk-notification.h b/src/gtk-notification.h new file mode 100644 index 00000000..67ecc864 --- /dev/null +++ b/src/gtk-notification.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * gtk-notification.c + * Copyright (C) Erick Pérez Castellanos 2011 + * + gtk-notification.c 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 3 of the License, or + * (at your option) any later version. + * + * gtk-notification.c 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 program. If not, see ."; + */ + +#ifndef _GTK_NOTIFICATION_H_ +#define _GTK_NOTIFICATION_H_ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_NOTIFICATION (gtk_notification_get_type ()) +#define GTK_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_NOTIFICATION, GtkNotification)) +#define GTK_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_NOTIFICATION, GtkNotificationClass)) +#define GTK_IS_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_NOTIFICATION)) +#define GTK_IS_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_NOTIFICATION)) +#define GTK_NOTIFICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_NOTIFICATION, GtkNotificationClass)) + +typedef struct _GtkNotificationPrivate GtkNotificationPrivate; +typedef struct _GtkNotificationClass GtkNotificationClass; +typedef struct _GtkNotification GtkNotification; + +struct _GtkNotificationClass { + GtkBinClass parent_class; + + /* Signals */ + void (*dismissed) (GtkNotification *self); +}; + +struct _GtkNotification { + GtkBin parent_instance; + + /*< private > */ + GtkNotificationPrivate *priv; +}; + +GType gtk_notification_get_type (void) G_GNUC_CONST; + +GtkWidget *gtk_notification_new (void); +void gtk_notification_set_timeout (GtkNotification *notification, + guint timeout_msec); +void gtk_notification_dismiss (GtkNotification *notification); + +G_END_DECLS + +#endif /* _GTK_NOTIFICATION_H_ */