diff options
author | Cody Russell <crussell@canonical.com> | 2010-05-12 23:21:36 +0200 |
---|---|---|
committer | Cody Russell <crussell@canonical.com> | 2010-05-12 23:21:36 +0200 |
commit | a544da2cdf98dc1763a84626e13a1e2e5da98e83 (patch) | |
tree | e4a14450676f6e6bebaba97b2c53fbbac32adeb2 /src/idomessagedialog.c | |
parent | ac894c8ff8c842a8911fbae7998789ada1a080e6 (diff) | |
download | ayatana-ido-a544da2cdf98dc1763a84626e13a1e2e5da98e83.tar.gz ayatana-ido-a544da2cdf98dc1763a84626e13a1e2e5da98e83.tar.bz2 ayatana-ido-a544da2cdf98dc1763a84626e13a1e2e5da98e83.zip |
IdoMessageDialog - a morphing message dialog.
Diffstat (limited to 'src/idomessagedialog.c')
-rw-r--r-- | src/idomessagedialog.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/src/idomessagedialog.c b/src/idomessagedialog.c new file mode 100644 index 0000000..8cb34a0 --- /dev/null +++ b/src/idomessagedialog.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2010 Canonical, Ltd. + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Cody Russell <crussell@canonical.com> + * + * Design and specification: + * Matthew Paul Thomas <mpt@canonical.com> + */ + +#include <string.h> + +#include <gtk/gtk.h> + +#include "idomessagedialog.h" +#include "idotimeline.h" + +#define IDO_MESSAGE_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), IDO_TYPE_MESSAGE_DIALOG, IdoMessageDialogPrivate)) + +static GtkWidget *ido_message_dialog_get_secondary_label (IdoMessageDialog *dialog); +static GtkWidget *ido_message_dialog_get_primary_label (IdoMessageDialog *dialog); + +typedef struct _IdoMessageDialogPrivate IdoMessageDialogPrivate; +typedef struct _IdoMessageDialogMorphContext IdoMessageDialogMorphContext; + +struct _IdoMessageDialogPrivate +{ + GtkWidget *action_area; + GtkWidget *primary_label; + GtkWidget *secondary_label; + + gboolean expanded; +}; + +struct _IdoMessageDialogMorphContext +{ + GtkWidget *widget; + IdoTimeline *timeline; + + GtkRequisition start; + GtkRequisition end; +}; + +G_DEFINE_TYPE (IdoMessageDialog, ido_message_dialog, GTK_TYPE_MESSAGE_DIALOG) + +static void +ido_message_dialog_map (GtkWidget *widget) +{ + IdoMessageDialog *dialog = IDO_MESSAGE_DIALOG (widget); + IdoMessageDialogPrivate *priv = IDO_MESSAGE_DIALOG_GET_PRIVATE (dialog); + + GTK_WIDGET_CLASS (ido_message_dialog_parent_class)->map (widget); + + priv->primary_label = ido_message_dialog_get_primary_label (dialog); + priv->secondary_label = ido_message_dialog_get_secondary_label (dialog); + + gtk_widget_hide (priv->secondary_label); + + gtk_label_set_selectable (GTK_LABEL (priv->primary_label), FALSE); + gtk_label_set_selectable (GTK_LABEL (priv->secondary_label), FALSE); + + /* XXX: We really want to use gtk_window_set_deletable (GTK_WINDOW (widget), FALSE) + * here, but due to a bug in compiz this is more compatible. + * + * See: https://bugs.launchpad.net/ubuntu/+source/compiz/+bug/240794 + */ + gdk_window_set_functions (widget->window, + GDK_FUNC_RESIZE | GDK_FUNC_MOVE); + + ido_message_dialog_get_secondary_label (IDO_MESSAGE_DIALOG (widget)); +} + +static IdoMessageDialogMorphContext * +ido_message_dialog_morph_context_new (GtkWidget *widget, + IdoTimeline *timeline, + gpointer identifier, + GtkRequisition *start, + GtkRequisition *end) +{ + IdoMessageDialogMorphContext *context; + + context = g_slice_new (IdoMessageDialogMorphContext); + + context->widget = widget; + context->timeline = timeline; + context->start = *start; + context->end = *end; + + return context; +} + +static void +ido_message_dialog_morph_context_free (IdoMessageDialogMorphContext *context) +{ + g_object_unref (context->timeline); + + g_slice_free (IdoMessageDialogMorphContext, context); +} + +static void +timeline_frame_cb (IdoTimeline *timeline, + gdouble progress, + gpointer user_data) +{ + IdoMessageDialogMorphContext *context = user_data; + GtkRequisition start = context->start; + GtkRequisition end = context->end; + gint width_diff; + gint height_diff; + gint width, height; + + width_diff = (MAX(start.width, end.width) - MIN(start.width, end.width)) * progress; + height_diff = (MAX(start.height, end.height) - MIN(start.height, end.height)) * progress; + + gtk_window_get_size (GTK_WINDOW (context->widget), + &width, + &height); + + gtk_widget_set_size_request (context->widget, + width_diff ? start.width + width_diff : -1, + height_diff ? start.height + height_diff : -1); +} + +static void +timeline_finished_cb (IdoTimeline *timeline, + gpointer user_data) +{ + IdoMessageDialogMorphContext *context = user_data; + IdoMessageDialogPrivate *priv = IDO_MESSAGE_DIALOG_GET_PRIVATE (context->widget); + + gtk_widget_show (priv->action_area); + gtk_widget_show (priv->secondary_label); + + ido_message_dialog_morph_context_free (context); +} + +static gboolean +button_pressed (GtkWidget *event_box, + GdkEventButton *event, + gpointer user_data) +{ + IdoMessageDialog *dialog = (IdoMessageDialog *) user_data; + IdoMessageDialogPrivate *priv = IDO_MESSAGE_DIALOG_GET_PRIVATE (dialog); + + if (!priv->expanded) + { + GtkRequisition start; + GtkRequisition end; + IdoTimeline *timeline; + IdoMessageDialogMorphContext *context; + + start = GTK_WIDGET (dialog)->requisition; + + priv->expanded = TRUE; + + gtk_widget_show (priv->action_area); + gtk_widget_show (priv->secondary_label); + + gtk_widget_size_request (GTK_WIDGET (dialog), &end); + + gtk_widget_hide (priv->action_area); + gtk_widget_hide (priv->secondary_label); + + timeline = ido_timeline_new (500); + context = ido_message_dialog_morph_context_new (GTK_WIDGET (dialog), + timeline, + "foo", + &start, + &end); + g_signal_connect (timeline, + "frame", + G_CALLBACK (timeline_frame_cb), + context); + g_signal_connect (timeline, + "finished", + G_CALLBACK (timeline_finished_cb), + context); + + ido_timeline_start (timeline); + } + + return FALSE; +} + +static void +ido_message_dialog_constructed (GObject *object) +{ + IdoMessageDialogPrivate *priv = IDO_MESSAGE_DIALOG_GET_PRIVATE (object); + GtkWidget *vbox; + GtkWidget *event_box; + + event_box = gtk_event_box_new (); + gtk_widget_show (event_box); + g_signal_connect (event_box, + "button-press-event", + G_CALLBACK (button_pressed), + object); + + vbox = GTK_DIALOG (object)->vbox; + priv->action_area = gtk_dialog_get_action_area (GTK_DIALOG (object)); + + g_object_ref (G_OBJECT (vbox)); + gtk_container_remove (GTK_CONTAINER (object), vbox); + gtk_container_add (GTK_CONTAINER (event_box), vbox); + gtk_container_add (GTK_CONTAINER (object), event_box); + + gtk_widget_hide (priv->action_area); +} + +static void +ido_message_dialog_class_init (IdoMessageDialogClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + object_class->constructed = ido_message_dialog_constructed; + + widget_class->map = ido_message_dialog_map; + + g_type_class_add_private (object_class, sizeof (IdoMessageDialogPrivate)); +} + +static void +ido_message_dialog_init (IdoMessageDialog *dialog) +{ + IdoMessageDialogPrivate *priv; + + priv = IDO_MESSAGE_DIALOG_GET_PRIVATE (dialog); +} + +/** + * ido_message_dialog_new: + * @parent: transient parent, or %NULL for none + * @flags: flags + * @type: type of message + * @buttons: a set of buttons to use + * @message_format: printf()-style format string, or %NULL + * @Varargs: arguments for @message_format + * + * Creates a new message dialog, which is based upon + * GtkMessageDialog so it shares API and functionality + * with it. IdoMessageDialog differs in that it has two + * states. The initial state hides the action buttons + * and the secondary message. When a user clicks on the + * dialog it will expand to provide the secondary message + * and the action buttons. + * + * Return value: a new #IdoMessageDialog + **/ +GtkWidget* +ido_message_dialog_new (GtkWindow *parent, + GtkDialogFlags flags, + GtkMessageType type, + GtkButtonsType buttons, + const gchar *message_format, + ...) +{ + GtkWidget *widget; + GtkDialog *dialog; + gchar* msg = NULL; + va_list args; + + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL); + + widget = g_object_new (IDO_TYPE_MESSAGE_DIALOG, + "message-type", type, + "buttons", buttons, + NULL); + dialog = GTK_DIALOG (widget); + + if (flags & GTK_DIALOG_NO_SEPARATOR) + { + g_warning ("The GTK_DIALOG_NO_SEPARATOR flag cannot be used for IdoMessageDialog"); + flags &= ~GTK_DIALOG_NO_SEPARATOR; + } + + if (message_format) + { + va_start (args, message_format); + msg = g_strdup_vprintf (message_format, args); + va_end (args); + + gtk_label_set_text (GTK_LABEL (GTK_MESSAGE_DIALOG (widget)->label), + msg); + + g_free (msg); + } + + if (parent != NULL) + gtk_window_set_transient_for (GTK_WINDOW (widget), + GTK_WINDOW (parent)); + + if (flags & GTK_DIALOG_MODAL) + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + if (flags & GTK_DIALOG_DESTROY_WITH_PARENT) + gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); + + return widget; +} + +GtkWidget* +ido_message_dialog_new_with_markup (GtkWindow *parent, + GtkDialogFlags flags, + GtkMessageType type, + GtkButtonsType buttons, + const gchar *message_format, + ...) +{ + GtkWidget *widget; + va_list args; + gchar *msg = NULL; + + g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL); + + widget = ido_message_dialog_new (parent, flags, type, buttons, NULL); + + if (message_format) + { + va_start (args, message_format); + msg = g_markup_vprintf_escaped (message_format, args); + va_end (args); + + gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (widget), msg); + + g_free (msg); + } + + return widget; +} + + /* + * This is almost humorously stupid. We jump through some hoops and kill + * a few kittens here because we want to preserve API compatibility with + * GtkMessageDialog and extend it instead of duplicating its functionality. + * If only GtkMessageDialog were easier to extend then maybe all those + * kittens wouldn't have had to die... + */ +static GtkWidget * +ido_message_dialog_get_label (IdoMessageDialog *dialog, gboolean primary) +{ + GList *list; + gchar *text; + gchar *secondary_text; + + g_object_get (G_OBJECT (dialog), + "text", &text, + "secondary-text", &secondary_text, + NULL); + + g_return_val_if_fail (IDO_IS_MESSAGE_DIALOG (dialog), NULL); + + for (list = GTK_BOX (GTK_DIALOG (dialog)->vbox)->children; list != NULL; list = list->next) + { + GtkBoxChild *child = (GtkBoxChild *)list->data; + + if (G_TYPE_FROM_INSTANCE (child->widget) == GTK_TYPE_HBOX) + { + GList *hlist; + GtkWidget *hbox = child->widget; + + for (hlist = GTK_BOX (hbox)->children; hlist != NULL; hlist = hlist->next) + { + GtkBoxChild *hchild = (GtkBoxChild *)hlist->data; + + if (G_TYPE_FROM_INSTANCE (hchild->widget) == GTK_TYPE_VBOX) + { + GList *vlist; + GtkWidget *vbox = hchild->widget; + + for (vlist = GTK_BOX (vbox)->children; vlist != NULL; vlist = vlist->next) + { + GtkBoxChild *vchild = (GtkBoxChild *)vlist->data; + GtkLabel *label; + + label = GTK_LABEL (vchild->widget); + + if (strcmp ((primary ? text : secondary_text), label->label) == 0) + { + return vchild->widget; + } + } + } + } + } + } + + return NULL; +} + +static GtkWidget * +ido_message_dialog_get_secondary_label (IdoMessageDialog *dialog) +{ + return ido_message_dialog_get_label (dialog, FALSE); +} + +static GtkWidget * +ido_message_dialog_get_primary_label (IdoMessageDialog *dialog) +{ + return ido_message_dialog_get_label (dialog, TRUE); +} |