aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-alert-bar.c
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2012-12-10 21:09:59 +0800
committerMatthew Barnes <mbarnes@redhat.com>2012-12-13 03:33:43 +0800
commitd09d8de870b6697c8a8b262e7e077b871a69b315 (patch)
tree3b718882e7a0bb0a996daf2967a033d91714c9b5 /e-util/e-alert-bar.c
parentb61331ed03ac1c7a9b8614e25510040b9c60ae02 (diff)
downloadgsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.gz
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.zst
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.zip
Consolidate base utility libraries into libeutil.
Evolution consists of entirely too many small utility libraries, which increases linking and loading time, places a burden on higher layers of the application (e.g. modules) which has to remember to link to all the small in-tree utility libraries, and makes it difficult to generate API documentation for these utility libraries in one Gtk-Doc module. Merge the following utility libraries under the umbrella of libeutil, and enforce a single-include policy on libeutil so we can reorganize the files as desired without disrupting its pseudo-public API. libemail-utils/libemail-utils.la libevolution-utils/libevolution-utils.la filter/libfilter.la widgets/e-timezone-dialog/libetimezonedialog.la widgets/menus/libmenus.la widgets/misc/libemiscwidgets.la widgets/table/libetable.la widgets/text/libetext.la This also merges libedataserverui from the Evolution-Data-Server module, since Evolution is its only consumer nowadays, and I'd like to make some improvements to those APIs without concern for backward-compatibility. And finally, start a Gtk-Doc module for libeutil. It's going to be a project just getting all the symbols _listed_ much less _documented_. But the skeletal structure is in place and I'm off to a good start.
Diffstat (limited to 'e-util/e-alert-bar.c')
-rw-r--r--e-util/e-alert-bar.c390
1 files changed, 390 insertions, 0 deletions
diff --git a/e-util/e-alert-bar.c b/e-util/e-alert-bar.c
new file mode 100644
index 0000000000..2022af99f1
--- /dev/null
+++ b/e-util/e-alert-bar.c
@@ -0,0 +1,390 @@
+/*
+ * e-alert-bar.c
+ *
+ * This program 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) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-alert-bar.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_ALERT_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ALERT_BAR, EAlertBarPrivate))
+
+#define E_ALERT_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ALERT_BAR, EAlertBarPrivate))
+
+/* GTK_ICON_SIZE_DIALOG is a tad too big. */
+#define ICON_SIZE GTK_ICON_SIZE_DND
+
+/* Dismiss warnings automatically after 5 minutes. */
+#define WARNING_TIMEOUT_SECONDS (5 * 60)
+
+struct _EAlertBarPrivate {
+ GQueue alerts;
+ GtkWidget *image; /* not referenced */
+ GtkWidget *primary_label; /* not referenced */
+ GtkWidget *secondary_label; /* not referenced */
+};
+
+G_DEFINE_TYPE (
+ EAlertBar,
+ e_alert_bar,
+ GTK_TYPE_INFO_BAR)
+
+static void
+alert_bar_response_close (EAlert *alert)
+{
+ e_alert_response (alert, GTK_RESPONSE_CLOSE);
+}
+
+static void
+alert_bar_show_alert (EAlertBar *alert_bar)
+{
+ GtkImage *image;
+ GtkInfoBar *info_bar;
+ GtkWidget *action_area;
+ GtkWidget *widget;
+ EAlert *alert;
+ GList *actions;
+ GList *children;
+ GtkMessageType message_type;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+ const gchar *stock_id;
+ gboolean have_primary_text;
+ gboolean have_secondary_text;
+ gboolean visible;
+ gint response_id;
+ gchar *markup;
+
+ info_bar = GTK_INFO_BAR (alert_bar);
+ action_area = gtk_info_bar_get_action_area (info_bar);
+
+ alert = g_queue_peek_head (&alert_bar->priv->alerts);
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ /* Remove all buttons from the previous alert. */
+ children = gtk_container_get_children (GTK_CONTAINER (action_area));
+ while (children != NULL) {
+ GtkWidget *child = GTK_WIDGET (children->data);
+ gtk_container_remove (GTK_CONTAINER (action_area), child);
+ children = g_list_delete_link (children, children);
+ }
+
+ /* Add alert-specific buttons. */
+ actions = e_alert_peek_actions (alert);
+ while (actions != NULL) {
+ /* These actions are already wired to trigger an
+ * EAlert::response signal when activated, which
+ * will in turn call gtk_info_bar_response(), so
+ * we can add buttons directly to the action
+ * area without knowning their response IDs. */
+
+ widget = gtk_button_new ();
+
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (widget),
+ GTK_ACTION (actions->data));
+
+ gtk_box_pack_end (
+ GTK_BOX (action_area), widget, FALSE, FALSE, 0);
+
+ actions = g_list_next (actions);
+ }
+
+ /* Add a dismiss button. */
+ widget = gtk_button_new ();
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (
+ GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (
+ widget, _("Close this message"));
+ gtk_box_pack_end (
+ GTK_BOX (action_area), widget, FALSE, FALSE, 0);
+ gtk_button_box_set_child_non_homogeneous (
+ GTK_BUTTON_BOX (action_area), widget, TRUE);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (alert_bar_response_close), alert);
+
+ primary_text = e_alert_get_primary_text (alert);
+ secondary_text = e_alert_get_secondary_text (alert);
+
+ if (primary_text == NULL)
+ primary_text = "";
+
+ if (secondary_text == NULL)
+ secondary_text = "";
+
+ have_primary_text = (*primary_text != '\0');
+ have_secondary_text = (*secondary_text != '\0');
+
+ response_id = e_alert_get_default_response (alert);
+ gtk_info_bar_set_default_response (info_bar, response_id);
+
+ message_type = e_alert_get_message_type (alert);
+ gtk_info_bar_set_message_type (info_bar, message_type);
+
+ widget = alert_bar->priv->primary_label;
+ if (have_primary_text && have_secondary_text)
+ markup = g_markup_printf_escaped (
+ "<b>%s</b>", primary_text);
+ else
+ markup = g_markup_escape_text (primary_text, -1);
+ gtk_label_set_markup (GTK_LABEL (widget), markup);
+ gtk_widget_set_visible (widget, have_primary_text);
+ g_free (markup);
+
+ widget = alert_bar->priv->secondary_label;
+ if (have_primary_text && have_secondary_text)
+ markup = g_markup_printf_escaped (
+ "<small>%s</small>", secondary_text);
+ else
+ markup = g_markup_escape_text (secondary_text, -1);
+ gtk_label_set_markup (GTK_LABEL (widget), markup);
+ gtk_widget_set_visible (widget, have_secondary_text);
+ g_free (markup);
+
+ stock_id = e_alert_get_stock_id (alert);
+ image = GTK_IMAGE (alert_bar->priv->image);
+ gtk_image_set_from_stock (image, stock_id, ICON_SIZE);
+
+ /* Avoid showing an image for one-line alerts,
+ * which are usually questions or informational. */
+ visible = have_primary_text && have_secondary_text;
+ gtk_widget_set_visible (alert_bar->priv->image, visible);
+
+ gtk_widget_show (GTK_WIDGET (alert_bar));
+
+ /* Warnings are generally meant for transient errors.
+ * No need to leave them up indefinitely. Close them
+ * automatically if the user hasn't responded after a
+ * reasonable period of time has elapsed. */
+ if (message_type == GTK_MESSAGE_WARNING)
+ e_alert_start_timer (alert, WARNING_TIMEOUT_SECONDS);
+}
+
+static void
+alert_bar_response_cb (EAlert *alert,
+ gint response_id,
+ EAlertBar *alert_bar)
+{
+ GQueue *queue;
+ EAlert *head;
+ gboolean was_head;
+
+ queue = &alert_bar->priv->alerts;
+ head = g_queue_peek_head (queue);
+ was_head = (alert == head);
+
+ g_signal_handlers_disconnect_by_func (
+ alert, alert_bar_response_cb, alert_bar);
+
+ if (g_queue_remove (queue, alert))
+ g_object_unref (alert);
+
+ if (g_queue_is_empty (queue))
+ gtk_widget_hide (GTK_WIDGET (alert_bar));
+ else if (was_head) {
+ GtkInfoBar *info_bar = GTK_INFO_BAR (alert_bar);
+ gtk_info_bar_response (info_bar, response_id);
+ alert_bar_show_alert (alert_bar);
+ }
+}
+
+static void
+alert_bar_dispose (GObject *object)
+{
+ EAlertBarPrivate *priv;
+
+ priv = E_ALERT_BAR_GET_PRIVATE (object);
+
+ while (!g_queue_is_empty (&priv->alerts)) {
+ EAlert *alert = g_queue_pop_head (&priv->alerts);
+ g_signal_handlers_disconnect_by_func (
+ alert, alert_bar_response_cb, object);
+ g_object_unref (alert);
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_alert_bar_parent_class)->dispose (object);
+}
+
+static void
+alert_bar_constructed (GObject *object)
+{
+ EAlertBarPrivate *priv;
+ GtkInfoBar *info_bar;
+ GtkWidget *action_area;
+ GtkWidget *content_area;
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ priv = E_ALERT_BAR_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_alert_bar_parent_class)->constructed (object);
+
+ g_queue_init (&priv->alerts);
+
+ info_bar = GTK_INFO_BAR (object);
+ action_area = gtk_info_bar_get_action_area (info_bar);
+ content_area = gtk_info_bar_get_content_area (info_bar);
+
+ gtk_orientable_set_orientation (
+ GTK_ORIENTABLE (action_area), GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_valign (action_area, GTK_ALIGN_START);
+
+ container = content_area;
+
+ widget = gtk_image_new ();
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.5, 0.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ priv->image = widget;
+ gtk_widget_show (widget);
+
+ widget = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ priv->primary_label = widget;
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ priv->secondary_label = widget;
+ gtk_widget_show (widget);
+
+ container = action_area;
+}
+
+static GtkSizeRequestMode
+alert_bar_get_request_mode (GtkWidget *widget)
+{
+ /* GtkBox does width-for-height by default. But we
+ * want the alert bar to be as short as possible. */
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+e_alert_bar_class_init (EAlertBarClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EAlertBarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = alert_bar_dispose;
+ object_class->constructed = alert_bar_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->get_request_mode = alert_bar_get_request_mode;
+}
+
+static void
+e_alert_bar_init (EAlertBar *alert_bar)
+{
+ alert_bar->priv = E_ALERT_BAR_GET_PRIVATE (alert_bar);
+}
+
+GtkWidget *
+e_alert_bar_new (void)
+{
+ return g_object_new (E_TYPE_ALERT_BAR, NULL);
+}
+
+void
+e_alert_bar_clear (EAlertBar *alert_bar)
+{
+ GQueue *queue;
+ EAlert *alert;
+
+ g_return_if_fail (E_IS_ALERT_BAR (alert_bar));
+
+ queue = &alert_bar->priv->alerts;
+
+ while ((alert = g_queue_pop_head (queue)) != NULL)
+ alert_bar_response_close (alert);
+}
+
+typedef struct {
+ gboolean found;
+ EAlert *looking_for;
+} DuplicateData;
+
+static void
+alert_bar_find_duplicate_cb (EAlert *alert,
+ DuplicateData *dd)
+{
+ g_return_if_fail (dd->looking_for != NULL);
+
+ dd->found |= (
+ e_alert_get_message_type (alert) ==
+ e_alert_get_message_type (dd->looking_for) &&
+ g_strcmp0 (
+ e_alert_get_primary_text (alert),
+ e_alert_get_primary_text (dd->looking_for)) == 0 &&
+ g_strcmp0 (
+ e_alert_get_secondary_text (alert),
+ e_alert_get_secondary_text (dd->looking_for)) == 0);
+}
+
+void
+e_alert_bar_add_alert (EAlertBar *alert_bar,
+ EAlert *alert)
+{
+ DuplicateData dd;
+
+ g_return_if_fail (E_IS_ALERT_BAR (alert_bar));
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ dd.found = FALSE;
+ dd.looking_for = alert;
+
+ g_queue_foreach (
+ &alert_bar->priv->alerts,
+ (GFunc) alert_bar_find_duplicate_cb, &dd);
+
+ if (dd.found)
+ return;
+
+ g_signal_connect (
+ alert, "response",
+ G_CALLBACK (alert_bar_response_cb), alert_bar);
+
+ g_queue_push_head (&alert_bar->priv->alerts, g_object_ref (alert));
+
+ alert_bar_show_alert (alert_bar);
+}