summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Jon McCann <jmccann@redhat.com>2009-09-22 15:06:03 -0400
committerWilliam Jon McCann <jmccann@redhat.com>2009-09-22 15:06:03 -0400
commita40097cae0248713b4779ae80567c14e02bd7e1f (patch)
treed9185164eba0c09944cc647e348c413c2bce44d2
Initial commit
-rw-r--r--Makefile.am60
-rwxr-xr-xautogen.sh32
-rw-r--r--configure.ac137
-rw-r--r--src/Makefile.am11
-rw-r--r--src/theme.c799
5 files changed, 1039 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..7d73fb1
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,60 @@
+# This file will be processed with automake-1.7 to create Makefile.in
+
+AUTOMAKE_OPTIONS = 1.7
+
+NULL =
+
+SUBDIRS = \
+ src \
+ $(NULL)
+
+EXTRA_DIST = \
+ COPYING \
+ AUTHORS \
+ INSTALL \
+ README \
+ HACKING \
+ NEWS \
+ ChangeLog \
+ $(NULL)
+
+DISTCLEANFILES = \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ intltool-*.in \
+ compile \
+ configure \
+ INSTALL \
+ install-sh \
+ missing \
+ mkinstalldirs \
+ config.guess \
+ ltmain.sh \
+ config.sub \
+ depcomp \
+ Makefile.in \
+ config.h.* \
+ aclocal.m4 \
+ $(NULL)
+
+# Creating ChangeLog from git log (taken from cairo/Makefile.am):
+
+ChangeLog: $(srcdir)/ChangeLog
+
+$(srcdir)/ChangeLog:
+ @if test -d "$(srcdir)/.git"; then \
+ (cd "$(srcdir)" && \
+ ./missing --run git log --stat) | fmt --split-only > $@.tmp \
+ && mv -f $@.tmp $@ \
+ || ($(RM) $@.tmp; \
+ echo Failed to generate ChangeLog, your ChangeLog may be outdated >&2; \
+ (test -f $@ || echo git-log is required to generate this file >> $@)); \
+ else \
+ test -f $@ || \
+ (echo A git checkout and git-log is required to generate ChangeLog >&2 && \
+ echo A git checkout and git-log is required to generate this file >> $@); \
+ fi
+
+.PHONY: ChangeLog $(srcdir)/ChangeLog
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..969e839
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+(test -f $srcdir/configure.ac) || {
+ echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+ echo " top-level package directory"
+ exit 1
+}
+
+if test -z "$*"; then
+ echo "**Warning**: I am going to run \`configure' with no arguments."
+ echo "If you wish to pass any to it, please specify them on the"
+ echo \`$0\'" command line."
+ echo
+fi
+
+(cd $srcdir && autoreconf --force --install) || exit 1
+
+conf_flags="--enable-maintainer-mode --cache-file=config.cache --disable-static"
+
+if test x$NOCONFIGURE = x; then
+ echo Running $srcdir/configure $conf_flags "$@" ...
+ $srcdir/configure $conf_flags "$@" \
+ && echo Now type \`make\' to compile. || exit 1
+else
+ echo Skipping configure process.
+fi
+
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..39de48b
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,137 @@
+
+AC_PREREQ(2.59c)
+AC_INIT([notification-daemon-engine-chelonia],
+ [0.1.0],
+ [])
+
+AC_CONFIG_SRCDIR([src/theme.c])
+
+AM_INIT_AUTOMAKE([1.9 no-dist-gzip dist-bzip2])
+
+AM_MAINTAINER_MODE
+
+AC_PROG_CC
+AC_STDC_HEADERS
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+
+AC_HEADER_STDC
+
+AC_SUBST(VERSION)
+
+# Save flags to aclocal
+ACLOCAL="$ACLOCAL $ACLOCAL_FLAGS"
+
+# Dependencies
+
+GTK_REQUIRED_VERSION=2.17.0
+
+PKG_CHECK_MODULES(THEME,
+ gtk+-2.0 >= $GTK_REQUIRED_VERSION
+)
+
+AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
+
+EXTRA_COMPILE_WARNINGS(yes)
+
+dnl ---------------------------------------------------------------------------
+dnl Finish
+dnl ---------------------------------------------------------------------------
+
+# Turn on the additional warnings last, so -Werror doesn't affect other tests.
+
+AC_ARG_ENABLE(more-warnings,
+ [AC_HELP_STRING([--enable-more-warnings],
+ [Maximum compiler warnings])],
+ set_more_warnings="$enableval",[
+ if test -d $srcdir/.git; then
+ set_more_warnings=yes
+ else
+ set_more_warnings=no
+ fi
+ ])
+AC_MSG_CHECKING(for more warnings)
+if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then
+ AC_MSG_RESULT(yes)
+ CFLAGS="\
+ -Wall \
+ -Wchar-subscripts -Wmissing-declarations -Wmissing-prototypes \
+ -Wnested-externs -Wpointer-arith \
+ -Wcast-align -Wsign-compare -Wp,-D_FORTIFY_SOURCE=2 \
+ $CFLAGS"
+
+ for option in -Wno-strict-aliasing -Wno-sign-compare; do
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $option"
+ AC_MSG_CHECKING([whether gcc understands $option])
+ AC_TRY_COMPILE([], [],
+ has_option=yes,
+ has_option=no,)
+ if test $has_option = no; then
+ CFLAGS="$SAVE_CFLAGS"
+ fi
+ AC_MSG_RESULT($has_option)
+ unset has_option
+ unset SAVE_CFLAGS
+ done
+ unset option
+else
+ AC_MSG_RESULT(no)
+fi
+
+#
+# Enable Debug
+#
+AC_ARG_ENABLE(debug,
+ [AC_HELP_STRING([--enable-debug],
+ [turn on debugging])],
+ , enable_debug=yes)
+if test "$enable_debug" = "yes"; then
+ DEBUG_CFLAGS="-DG_ENABLE_DEBUG"
+else
+ if test "x$enable_debug" = "xno"; then
+ DEBUG_CFLAGS="-DG_DISABLE_ASSERT -DG_DISABLE_CHECKS"
+ else
+ DEBUG_CFLAGS=""
+ fi
+fi
+AC_SUBST(DEBUG_CFLAGS)
+
+# Flags
+
+AC_SUBST(THEME_CFLAGS)
+AC_SUBST(THEME_LIBS)
+
+AC_SUBST(CFLAGS)
+AC_SUBST(CPPFLAGS)
+AC_SUBST(LDFLAGS)
+
+# Files
+
+AC_CONFIG_FILES([
+Makefile
+src/Makefile
+])
+
+AC_CONFIG_HEADERS([config.h])
+
+AC_OUTPUT
+
+echo "
+ ConsoleKit $VERSION
+ ========================
+
+ prefix: ${prefix}
+ exec_prefix: ${exec_prefix}
+ libdir: ${libdir}
+ bindir: ${bindir}
+ sbindir: ${sbindir}
+ sysconfdir: ${sysconfdir}
+ localstatedir: ${localstatedir}
+ datadir: ${datadir}
+ source code location: ${srcdir}
+ compiler: ${CC}
+ cflags: ${CFLAGS}
+ Base libs: ${THEME_LIBS}
+ Maintainer mode: ${USE_MAINTAINER_MODE}
+"
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..61bab6b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,11 @@
+NULL =
+
+enginedir = $(libdir)/notification-daemon-1.0/engines
+engine_LTLIBRARIES = libchelonia.la
+
+libchelonia_la_SOURCES = theme.c
+
+libchelonia_la_LDFLAGS = -module -avoid-version
+libchelonia_la_LIBADD = \
+ $(THEME_LIBS) \
+ $(NULL)
diff --git a/src/theme.c b/src/theme.c
new file mode 100644
index 0000000..c551966
--- /dev/null
+++ b/src/theme.c
@@ -0,0 +1,799 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2006-2007 Christian Hammond <chipx86@chipx86.com>
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+typedef void (*ActionInvokedCb)(GtkWindow *nw, const char *key);
+typedef void (*UrlClickedCb)(GtkWindow *nw, const char *url);
+
+typedef struct
+{
+ GtkWidget *win;
+ GtkWidget *main_hbox;
+ GtkWidget *iconbox;
+ GtkWidget *icon;
+ GtkWidget *content_hbox;
+ GtkWidget *summary_label;
+ GtkWidget *close_button;
+ GtkWidget *body_label;
+ GtkWidget *actions_box;
+ GtkWidget *last_sep;
+ GtkWidget *pie_countdown;
+
+ gboolean has_arrow;
+ gboolean composited;
+
+ int width;
+ int height;
+
+ guchar urgency;
+ glong timeout;
+ glong remaining;
+
+ UrlClickedCb url_clicked;
+} WindowData;
+
+enum
+{
+ URGENCY_LOW,
+ URGENCY_NORMAL,
+ URGENCY_CRITICAL
+};
+
+#define WIDTH 400
+#define DEFAULT_X0 0
+#define DEFAULT_Y0 0
+#define DEFAULT_RADIUS 16
+#define IMAGE_SIZE 32
+#define PIE_RADIUS 12
+#define PIE_WIDTH (2 * PIE_RADIUS)
+#define PIE_HEIGHT (2 * PIE_RADIUS)
+#define BODY_X_OFFSET (IMAGE_SIZE + 8)
+#define BACKGROUND_OPACITY 0.75
+
+#if GTK_CHECK_VERSION(2, 10, 0)
+# define USE_COMPOSITE
+#endif
+
+static void
+draw_round_rect (cairo_t* cr,
+ gdouble aspect,
+ gdouble x,
+ gdouble y,
+ gdouble corner_radius,
+ gdouble width,
+ gdouble height)
+{
+ gdouble radius = corner_radius / aspect;
+
+ cairo_move_to (cr, x + radius, y);
+
+ // top-right, left of the corner
+ cairo_line_to (cr,
+ x + width - radius,
+ y);
+
+ // top-right, below the corner
+ cairo_arc (cr,
+ x + width - radius,
+ y + radius,
+ radius,
+ -90.0f * G_PI / 180.0f,
+ 0.0f * G_PI / 180.0f);
+
+ // bottom-right, above the corner
+ cairo_line_to (cr,
+ x + width,
+ y + height - radius);
+
+ // bottom-right, left of the corner
+ cairo_arc (cr,
+ x + width - radius,
+ y + height - radius,
+ radius,
+ 0.0f * G_PI / 180.0f,
+ 90.0f * G_PI / 180.0f);
+
+ // bottom-left, right of the corner
+ cairo_line_to (cr,
+ x + radius,
+ y + height);
+
+ // bottom-left, above the corner
+ cairo_arc (cr,
+ x + radius,
+ y + height - radius,
+ radius,
+ 90.0f * G_PI / 180.0f,
+ 180.0f * G_PI / 180.0f);
+
+ // top-left, below the corner
+ cairo_line_to (cr,
+ x,
+ y + radius);
+
+ // top-left, right of the corner
+ cairo_arc (cr,
+ x + radius,
+ y + radius,
+ radius,
+ 180.0f * G_PI / 180.0f,
+ 270.0f * G_PI / 180.0f);
+}
+
+static void
+fill_background (GtkWidget *widget, WindowData *windata, cairo_t *cr)
+{
+ GdkColor color;
+ double r, g, b;
+
+ draw_round_rect (cr,
+ 1.0f,
+ DEFAULT_X0 + 1,
+ DEFAULT_Y0 + 1,
+ DEFAULT_RADIUS,
+ widget->allocation.width - 2,
+ widget->allocation.height - 2);
+
+ color = widget->style->bg [GTK_STATE_NORMAL];
+ r = (float)color.red / 65535.0;
+ g = (float)color.green / 65535.0;
+ b = (float)color.blue / 65535.0;
+ cairo_set_source_rgba (cr, r, g, b, BACKGROUND_OPACITY);
+ cairo_fill_preserve(cr);
+
+ switch (windata->urgency) {
+ case URGENCY_LOW:
+ color = widget->style->fg[GTK_STATE_NORMAL];
+ break;
+
+ case URGENCY_CRITICAL:
+ gdk_color_parse("#CC0000", &color);
+ break;
+
+ case URGENCY_NORMAL:
+ default:
+ color = widget->style->fg[GTK_STATE_SELECTED];
+ break;
+ }
+
+ r = (float)color.red / 65535.0;
+ g = (float)color.green / 65535.0;
+ b = (float)color.blue / 65535.0;
+ cairo_set_source_rgba (cr, r, g, b, BACKGROUND_OPACITY);
+ cairo_set_line_width (cr, 2);
+ cairo_stroke (cr);
+}
+
+static void
+update_shape (WindowData *windata)
+{
+ GdkBitmap *mask;
+ cairo_t *cr;
+
+ if (windata->composited) {
+ gtk_widget_shape_combine_mask (windata->win, NULL, 0, 0);
+ return;
+ }
+
+ mask = (GdkBitmap *) gdk_pixmap_new (NULL, windata->width, windata->height, 1);
+ if (mask == NULL) {
+ return;
+ }
+
+ cr = gdk_cairo_create (mask);
+ if (cairo_status (cr) == CAIRO_STATUS_SUCCESS) {
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_set_source_rgb (cr, 1.0f, 1.0f, 1.0f);
+ draw_round_rect (cr,
+ 1.0f,
+ DEFAULT_X0,
+ DEFAULT_Y0,
+ DEFAULT_RADIUS,
+ windata->width,
+ windata->height);
+ cairo_fill (cr);
+
+ gtk_widget_shape_combine_mask (windata->win, mask, 0, 0);
+ }
+ cairo_destroy (cr);
+
+ g_object_unref (mask);
+}
+
+static gboolean
+paint_window(GtkWidget *widget,
+ GdkEventExpose *event,
+ WindowData *windata)
+{
+ cairo_t *context;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ if (windata->width == 0) {
+ windata->width = windata->win->allocation.width;
+ windata->height = windata->win->allocation.height;
+ }
+
+ context = gdk_cairo_create(widget->window);
+
+ cairo_set_operator(context, CAIRO_OPERATOR_SOURCE);
+ surface = cairo_surface_create_similar(cairo_get_target(context),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ widget->allocation.width,
+ widget->allocation.height);
+ cr = cairo_create(surface);
+
+ fill_background(widget, windata, cr);
+
+ cairo_destroy(cr);
+ cairo_set_source_surface(context, surface, 0, 0);
+ cairo_paint(context);
+ cairo_surface_destroy(surface);
+ cairo_destroy(context);
+
+ update_shape (windata);
+
+ return FALSE;
+}
+
+static void
+destroy_windata(WindowData *windata)
+{
+ g_free(windata);
+}
+
+static void
+update_content_hbox_visibility(WindowData *windata)
+{
+ /*
+ * This is all a hack, but until we have a libview-style ContentBox,
+ * it'll just have to do.
+ */
+ if (GTK_WIDGET_VISIBLE(windata->icon) ||
+ GTK_WIDGET_VISIBLE(windata->body_label) ||
+ GTK_WIDGET_VISIBLE(windata->actions_box))
+ {
+ gtk_widget_show(windata->content_hbox);
+ }
+ else
+ {
+ gtk_widget_hide(windata->content_hbox);
+ }
+}
+
+static gboolean
+configure_event_cb(GtkWidget *nw,
+ GdkEventConfigure *event,
+ WindowData *windata)
+{
+ windata->width = event->width;
+ windata->height = event->height;
+
+ gtk_widget_queue_draw(nw);
+
+ return FALSE;
+}
+
+static void
+invert_style (GtkWidget *widget)
+{
+ GtkStateType state;
+ GtkStyle *style;
+
+ style = gtk_style_copy (widget->style);
+
+ state = (GtkStateType) 0;
+ while (state < (GtkStateType) G_N_ELEMENTS (widget->style->bg)) {
+ gtk_widget_modify_bg (widget, state, &style->fg[state]);
+ gtk_widget_modify_fg (widget, state, &style->bg[state]);
+ state++;
+ }
+
+ g_object_unref (style);
+}
+
+static void
+style_set_cb (GtkWidget *widget,
+ GtkStyle *previous_style,
+ WindowData *windata)
+{
+ if (GTK_WIDGET_REALIZED (widget)) {
+ g_signal_handlers_block_by_func (G_OBJECT(widget), style_set_cb, windata);
+ invert_style (widget);
+ g_signal_handlers_unblock_by_func (G_OBJECT(widget), style_set_cb, windata);
+ }
+}
+
+static void
+on_composited_changed (GtkWidget *window,
+ WindowData *windata)
+{
+ windata->composited = gdk_screen_is_composited (gtk_widget_get_screen (window));
+ update_shape (windata);
+}
+
+GtkWindow *
+create_notification(UrlClickedCb url_clicked)
+{
+ GtkWidget *win;
+ GtkWidget *drawbox;
+ GtkWidget *main_vbox;
+ GtkWidget *vbox;
+ GtkWidget *close_button;
+ GtkWidget *image;
+ GtkWidget *alignment;
+ AtkObject *atkobj;
+ GtkRcStyle *rcstyle;
+ WindowData *windata;
+#ifdef USE_COMPOSITE
+ GdkColormap *colormap;
+ GdkScreen *screen;
+#endif
+
+ windata = g_new0(WindowData, 1);
+ windata->urgency = URGENCY_NORMAL;
+ windata->url_clicked = url_clicked;
+
+ win = gtk_window_new(GTK_WINDOW_POPUP);
+ invert_style (win);
+ g_signal_connect (G_OBJECT(win), "style-set",
+ G_CALLBACK(style_set_cb), windata);
+ windata->win = win;
+
+ windata->composited = FALSE;
+#ifdef USE_COMPOSITE
+ screen = gtk_window_get_screen(GTK_WINDOW(win));
+ colormap = gdk_screen_get_rgba_colormap(screen);
+ if (colormap != NULL) {
+ gtk_widget_set_colormap(win, colormap);
+ if (gdk_screen_is_composited(screen))
+ windata->composited = TRUE;
+ }
+ g_signal_connect (win,
+ "composited-changed",
+ G_CALLBACK (on_composited_changed),
+ windata);
+#endif
+
+ gtk_window_set_title(GTK_WINDOW(win), "Notification");
+ gtk_window_set_type_hint(GTK_WINDOW(win),
+ GDK_WINDOW_TYPE_HINT_NOTIFICATION);
+ gtk_widget_add_events(win, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ gtk_widget_realize(win);
+
+ g_object_set_data_full(G_OBJECT(win), "windata", windata,
+ (GDestroyNotify)destroy_windata);
+ atk_object_set_role(gtk_widget_get_accessible(win), ATK_ROLE_ALERT);
+
+ g_signal_connect(G_OBJECT(win), "configure-event",
+ G_CALLBACK(configure_event_cb), windata);
+
+ /*
+ * For some reason, there are occasionally graphics glitches when
+ * repainting the window. Despite filling the window with a background
+ * color, parts of the other windows on the screen or the shadows around
+ * notifications will appear on the notification. Somehow, adding this
+ * eventbox makes that problem just go away. Whatever works for now.
+ */
+ drawbox = gtk_event_box_new();
+ gtk_widget_show(drawbox);
+ gtk_container_add(GTK_CONTAINER(win), drawbox);
+
+ main_vbox = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(main_vbox);
+ gtk_container_add(GTK_CONTAINER(drawbox), main_vbox);
+ gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 12);
+ invert_style (main_vbox);
+
+ g_signal_connect(G_OBJECT(main_vbox), "expose-event",
+ G_CALLBACK(paint_window), windata);
+
+ windata->main_hbox = gtk_hbox_new(FALSE, 0);
+ gtk_widget_show(windata->main_hbox);
+ gtk_box_pack_start(GTK_BOX(main_vbox), windata->main_hbox,
+ FALSE, FALSE, 0);
+
+ /* First row (icon, vbox, close) */
+ windata->iconbox = gtk_hbox_new(FALSE, 0);
+ gtk_widget_show(windata->iconbox);
+ gtk_box_pack_start(GTK_BOX(windata->main_hbox), windata->iconbox,
+ FALSE, FALSE, 0);
+ gtk_widget_set_size_request(windata->iconbox, BODY_X_OFFSET, -1);
+
+ windata->icon = gtk_image_new();
+ gtk_box_pack_start(GTK_BOX(windata->iconbox), windata->icon,
+ TRUE, TRUE, 0);
+ gtk_misc_set_alignment(GTK_MISC(windata->icon), 0.5, 0);
+
+ vbox = gtk_vbox_new(FALSE, 6);
+ gtk_widget_show(vbox);
+ gtk_box_pack_start(GTK_BOX(windata->main_hbox), vbox, TRUE, TRUE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
+
+ /* Add the close button */
+ alignment = gtk_alignment_new(0.5, 0, 0, 0);
+ gtk_widget_show(alignment);
+ gtk_box_pack_start(GTK_BOX(windata->main_hbox), alignment, FALSE, FALSE, 0);
+
+ close_button = gtk_button_new();
+ windata->close_button = close_button;
+ gtk_widget_show(close_button);
+ gtk_container_add(GTK_CONTAINER(alignment), close_button);
+ gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
+ gtk_container_set_border_width(GTK_CONTAINER(close_button), 0);
+ g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
+ G_CALLBACK(gtk_widget_destroy), win);
+
+ rcstyle = gtk_rc_style_new();
+ rcstyle->xthickness = rcstyle->ythickness = 0;
+ gtk_widget_modify_style(close_button, rcstyle);
+ gtk_rc_style_unref(rcstyle);
+
+ atkobj = gtk_widget_get_accessible(close_button);
+ atk_action_set_description(ATK_ACTION(atkobj), 0,
+ "Closes the notification.");
+ atk_object_set_name(atkobj, "");
+ atk_object_set_description(atkobj, "Closes the notification.");
+
+ image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ gtk_widget_show(image);
+ gtk_container_add(GTK_CONTAINER(close_button), image);
+
+ /* center vbox */
+ windata->summary_label = gtk_label_new(NULL);
+ invert_style (windata->summary_label);
+ gtk_widget_show(windata->summary_label);
+ gtk_box_pack_start(GTK_BOX(vbox), windata->summary_label, TRUE, TRUE, 0);
+ gtk_misc_set_alignment(GTK_MISC(windata->summary_label), 0, 0);
+ gtk_label_set_line_wrap(GTK_LABEL(windata->summary_label), TRUE);
+ g_signal_connect (G_OBJECT(windata->summary_label), "style-set",
+ G_CALLBACK(style_set_cb), windata);
+
+ atkobj = gtk_widget_get_accessible(windata->summary_label);
+ atk_object_set_description(atkobj, "Notification summary text.");
+
+ windata->content_hbox = gtk_hbox_new(FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(vbox), windata->content_hbox, FALSE, FALSE, 0);
+
+
+ vbox = gtk_vbox_new(FALSE, 6);
+ gtk_widget_show(vbox);
+ gtk_box_pack_start(GTK_BOX(windata->content_hbox), vbox, TRUE, TRUE, 0);
+
+ windata->body_label = gtk_label_new(NULL);
+ invert_style (windata->body_label);
+ gtk_box_pack_start(GTK_BOX(vbox), windata->body_label, TRUE, TRUE, 0);
+ gtk_misc_set_alignment(GTK_MISC(windata->body_label), 0, 0);
+ gtk_label_set_line_wrap(GTK_LABEL(windata->body_label), TRUE);
+ g_signal_connect_swapped(G_OBJECT(windata->body_label), "activate-link",
+ G_CALLBACK(windata->url_clicked), win);
+ g_signal_connect (G_OBJECT(windata->body_label), "style-set",
+ G_CALLBACK(style_set_cb), windata);
+
+ atkobj = gtk_widget_get_accessible(windata->body_label);
+ atk_object_set_description(atkobj, "Notification body text.");
+
+ alignment = gtk_alignment_new(1, 0.5, 0, 0);
+ gtk_widget_show(alignment);
+ gtk_box_pack_start(GTK_BOX(vbox), alignment, FALSE, TRUE, 0);
+
+ windata->actions_box = gtk_hbox_new(FALSE, 6);
+ gtk_container_add(GTK_CONTAINER(alignment), windata->actions_box);
+
+ return GTK_WINDOW(win);
+}
+
+void
+set_notification_hints(GtkWindow *nw, GHashTable *hints)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ GValue *value;
+
+ g_assert(windata != NULL);
+
+ value = (GValue *)g_hash_table_lookup(hints, "urgency");
+
+ if (value != NULL && G_VALUE_HOLDS_UCHAR(value))
+ {
+ windata->urgency = g_value_get_uchar(value);
+
+ if (windata->urgency == URGENCY_CRITICAL) {
+ gtk_window_set_title(GTK_WINDOW(nw), "Critical Notification");
+ } else {
+ gtk_window_set_title(GTK_WINDOW(nw), "Notification");
+ }
+ }
+}
+
+void
+set_notification_timeout(GtkWindow *nw, glong timeout)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ g_assert(windata != NULL);
+
+ windata->timeout = timeout;
+}
+
+void
+notification_tick(GtkWindow *nw, glong remaining)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ windata->remaining = remaining;
+
+ if (windata->pie_countdown != NULL)
+ {
+ gtk_widget_queue_draw_area(windata->pie_countdown, 0, 0,
+ PIE_WIDTH, PIE_HEIGHT);
+ }
+}
+
+void
+set_notification_text(GtkWindow *nw, const char *summary, const char *body)
+{
+ char *str, *quoted;
+ GtkRequisition req;
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ g_assert(windata != NULL);
+
+ quoted = g_markup_escape_text(summary, -1);
+ str = g_strdup_printf("<b><big>%s</big></b>", quoted);
+ g_free(quoted);
+
+ gtk_label_set_markup(GTK_LABEL(windata->summary_label), str);
+ g_free(str);
+
+ gtk_label_set_markup(GTK_LABEL(windata->body_label), body);
+
+ if (body == NULL || *body == '\0')
+ gtk_widget_hide(windata->body_label);
+ else
+ gtk_widget_show(windata->body_label);
+
+ update_content_hbox_visibility(windata);
+
+ if (body != NULL && *body != '\0')
+ {
+ gtk_widget_size_request (windata->iconbox, &req);
+ gtk_widget_set_size_request(
+ windata->body_label,
+ /* -1: border width for
+ -6: spacing for hbox */
+ WIDTH - (1*2) - (10*2) - req.width - 6,
+ -1);
+ }
+
+ gtk_widget_size_request (windata->close_button, &req);
+ gtk_widget_set_size_request(windata->summary_label,
+ /* -1: main_vbox border width
+ -10: vbox border width
+ -6: spacing for hbox */
+ WIDTH - (1*2) - (10*2) - BODY_X_OFFSET - req.width - (6*2),
+ -1);
+}
+
+void
+set_notification_icon(GtkWindow *nw, GdkPixbuf *pixbuf)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ g_assert(windata != NULL);
+
+ gtk_image_set_from_pixbuf(GTK_IMAGE(windata->icon), pixbuf);
+
+ if (pixbuf != NULL)
+ {
+ int pixbuf_width = gdk_pixbuf_get_width(pixbuf);
+
+ gtk_widget_show(windata->icon);
+ gtk_widget_set_size_request(windata->iconbox,
+ MAX(BODY_X_OFFSET, pixbuf_width), -1);
+ }
+ else
+ {
+ gtk_widget_hide(windata->icon);
+ gtk_widget_set_size_request(windata->iconbox, BODY_X_OFFSET, -1);
+ }
+
+ update_content_hbox_visibility(windata);
+}
+
+void
+set_notification_arrow(GtkWidget *nw, gboolean visible, int x, int y)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ g_assert(windata != NULL);
+
+}
+
+static gboolean
+countdown_expose_cb(GtkWidget *pie, GdkEventExpose *event,
+ WindowData *windata)
+{
+ GtkStyle *style = gtk_widget_get_style(windata->win);
+ cairo_t *context;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ context = gdk_cairo_create(GDK_DRAWABLE(windata->pie_countdown->window));
+ cairo_set_operator(context, CAIRO_OPERATOR_SOURCE);
+ surface = cairo_surface_create_similar(
+ cairo_get_target(context),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ pie->allocation.width,
+ pie->allocation.height);
+ cr = cairo_create(surface);
+
+ fill_background(pie, windata, cr);
+
+ if (windata->timeout > 0)
+ {
+ gdouble pct = (gdouble)windata->remaining / (gdouble)windata->timeout;
+
+ gdk_cairo_set_source_color(cr, &style->bg[GTK_STATE_ACTIVE]);
+
+ cairo_move_to(cr, PIE_RADIUS, PIE_RADIUS);
+ cairo_arc_negative(cr, PIE_RADIUS, PIE_RADIUS, PIE_RADIUS,
+ -G_PI_2, -(pct * G_PI * 2) - G_PI_2);
+ cairo_line_to(cr, PIE_RADIUS, PIE_RADIUS);
+ cairo_fill(cr);
+ }
+
+ cairo_destroy(cr);
+ cairo_set_source_surface(context, surface, 0, 0);
+ cairo_paint(context);
+ cairo_surface_destroy(surface);
+ cairo_destroy(context);
+
+ return TRUE;
+}
+
+static void
+action_clicked_cb(GtkWidget *w, GdkEventButton *event,
+ ActionInvokedCb action_cb)
+{
+ GtkWindow *nw = g_object_get_data(G_OBJECT(w), "_nw");
+ const char *key = g_object_get_data(G_OBJECT(w), "_action_key");
+
+ action_cb(nw, key);
+}
+
+void
+add_notification_action(GtkWindow *nw, const char *text, const char *key,
+ ActionInvokedCb cb)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GdkPixbuf *pixbuf;
+ char *buf;
+
+ g_assert(windata != NULL);
+
+ if (!GTK_WIDGET_VISIBLE(windata->actions_box))
+ {
+ GtkWidget *alignment;
+
+ gtk_widget_show(windata->actions_box);
+ update_content_hbox_visibility(windata);
+
+ alignment = gtk_alignment_new(1, 0.5, 0, 0);
+ gtk_widget_show(alignment);
+ gtk_box_pack_end(GTK_BOX(windata->actions_box), alignment,
+ FALSE, TRUE, 0);
+
+ windata->pie_countdown = gtk_drawing_area_new();
+ gtk_widget_show(windata->pie_countdown);
+ gtk_container_add(GTK_CONTAINER(alignment), windata->pie_countdown);
+ gtk_widget_set_size_request(windata->pie_countdown,
+ PIE_WIDTH, PIE_HEIGHT);
+ g_signal_connect(G_OBJECT(windata->pie_countdown), "expose_event",
+ G_CALLBACK(countdown_expose_cb), windata);
+ }
+
+ button = gtk_button_new();
+ gtk_widget_show(button);
+ gtk_box_pack_start(GTK_BOX(windata->actions_box), button, FALSE, FALSE, 0);
+
+ hbox = gtk_hbox_new(FALSE, 6);
+ gtk_widget_show(hbox);
+ gtk_container_add(GTK_CONTAINER(button), hbox);
+
+ /* Try to be smart and find a suitable icon. */
+ buf = g_strdup_printf("stock_%s", key);
+ pixbuf = gtk_icon_theme_load_icon(
+ gtk_icon_theme_get_for_screen(
+ gdk_drawable_get_screen(GTK_WIDGET(nw)->window)),
+ buf, 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
+ g_free(buf);
+
+ if (pixbuf != NULL)
+ {
+ GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
+ gtk_widget_show(image);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5);
+ }
+
+ label = gtk_label_new(NULL);
+ gtk_widget_show(label);
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ buf = g_strdup_printf("<small>%s</small>", text);
+ gtk_label_set_markup(GTK_LABEL(label), buf);
+ g_free(buf);
+
+ g_object_set_data(G_OBJECT(button), "_nw", nw);
+ g_object_set_data_full(G_OBJECT(button),
+ "_action_key", g_strdup(key), g_free);
+ g_signal_connect(G_OBJECT(button), "button-release-event",
+ G_CALLBACK(action_clicked_cb), cb);
+}
+
+void
+clear_notification_actions(GtkWindow *nw)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+
+ windata->pie_countdown = NULL;
+
+ gtk_widget_hide(windata->actions_box);
+ gtk_container_foreach(GTK_CONTAINER(windata->actions_box),
+ (GtkCallback)gtk_object_destroy, NULL);
+}
+
+void
+move_notification(GtkWidget *nw, int x, int y)
+{
+ WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
+ g_assert(windata != NULL);
+
+ gtk_window_move(GTK_WINDOW(nw), x, y);
+}
+
+void
+get_theme_info(char **theme_name,
+ char **theme_ver,
+ char **author,
+ char **homepage)
+{
+ *theme_name = g_strdup("Standard");
+ *theme_ver = g_strdup_printf("%d.%d.%d",
+ NOTIFICATION_DAEMON_MAJOR_VERSION,
+ NOTIFICATION_DAEMON_MINOR_VERSION,
+ NOTIFICATION_DAEMON_MICRO_VERSION);
+ *author = g_strdup("Christian Hammond");
+ *homepage = g_strdup("http://www.galago-project.org/");
+}
+
+gboolean
+theme_check_init(unsigned int major_ver, unsigned int minor_ver,
+ unsigned int micro_ver)
+{
+ return major_ver == NOTIFICATION_DAEMON_MAJOR_VERSION &&
+ minor_ver == NOTIFICATION_DAEMON_MINOR_VERSION &&
+ micro_ver == NOTIFICATION_DAEMON_MICRO_VERSION;
+}