summaryrefslogtreecommitdiff
path: root/applets/clock/clock-face.c
diff options
context:
space:
mode:
Diffstat (limited to 'applets/clock/clock-face.c')
-rw-r--r--applets/clock/clock-face.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/applets/clock/clock-face.c b/applets/clock/clock-face.c
new file mode 100644
index 00000000..53f0e4f4
--- /dev/null
+++ b/applets/clock/clock-face.c
@@ -0,0 +1,462 @@
+/**
+ * clock.c
+ *
+ * A GTK+ widget that implements a clock face
+ *
+ * (c) 2007, Peter Teichman
+ * (c) 2005-2006, Davyd Madeley
+ *
+ * Authors:
+ * Davyd Madeley <[email protected]>
+ * Peter Teichman <[email protected]>
+ */
+
+#include <gtk/gtk.h>
+#include <math.h>
+#include <time.h>
+
+#include <librsvg/rsvg.h>
+
+#include "clock-face.h"
+#include "clock-location.h"
+
+static GHashTable *pixbuf_cache = NULL;
+
+#define CLOCK_FACE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), INTL_TYPE_CLOCK_FACE, ClockFacePrivate))
+
+G_DEFINE_TYPE (ClockFace, clock_face, GTK_TYPE_WIDGET)
+
+static void clock_face_finalize (GObject *);
+static gboolean clock_face_expose (GtkWidget *clock, GdkEventExpose *event);
+static void clock_face_size_request (GtkWidget *clock,
+ GtkRequisition *requisition);
+static void clock_face_size_allocate (GtkWidget *clock,
+ GtkAllocation *allocation);
+
+static void update_time_and_face (ClockFace *this,
+ gboolean force_face_loading);
+static void clock_face_load_face (ClockFace *this,
+ gint width, gint height);
+
+typedef struct _ClockFacePrivate ClockFacePrivate;
+
+typedef enum {
+ CLOCK_FACE_MORNING,
+ CLOCK_FACE_DAY,
+ CLOCK_FACE_EVENING,
+ CLOCK_FACE_NIGHT,
+ CLOCK_FACE_INVALID
+} ClockFaceTimeOfDay;
+
+struct _ClockFacePrivate
+{
+ struct tm time; /* the time on the clock face */
+ int minute_offset; /* the offset of the minutes hand */
+
+ ClockFaceSize size;
+ ClockFaceTimeOfDay timeofday;
+ ClockLocation *location;
+ GdkPixbuf *face_pixbuf;
+ GtkWidget *size_widget;
+};
+
+static void
+clock_face_class_init (ClockFaceClass *class)
+{
+ GObjectClass *obj_class;
+ GtkWidgetClass *widget_class;
+
+ obj_class = G_OBJECT_CLASS (class);
+ widget_class = GTK_WIDGET_CLASS (class);
+
+ /* GtkWidget signals */
+ widget_class->expose_event = clock_face_expose;
+ widget_class->size_request = clock_face_size_request;
+ widget_class->size_allocate = clock_face_size_allocate;
+
+ /* GObject signals */
+ obj_class->finalize = clock_face_finalize;
+
+ g_type_class_add_private (obj_class, sizeof (ClockFacePrivate));
+}
+
+static void
+clock_face_init (ClockFace *this)
+{
+ ClockFacePrivate *priv = CLOCK_FACE_GET_PRIVATE (this);
+
+ priv->size = CLOCK_FACE_SMALL;
+ priv->timeofday = CLOCK_FACE_INVALID;
+ priv->location = NULL;
+ priv->size_widget = NULL;
+
+ gtk_widget_set_has_window (GTK_WIDGET (this), FALSE);
+}
+
+static void
+draw (GtkWidget *this, cairo_t *cr)
+{
+ ClockFacePrivate *priv;
+ GtkAllocation allocation;
+ double x, y;
+ double radius;
+ int hours, minutes, seconds;
+
+ /* Hand lengths as a multiple of the clock radius */
+ double hour_length, min_length, sec_length;
+
+ priv = CLOCK_FACE_GET_PRIVATE (this);
+
+ if (priv->size == CLOCK_FACE_LARGE) {
+ hour_length = 0.45;
+ min_length = 0.6;
+ sec_length = 0.65;
+ } else {
+ hour_length = 0.5;
+ min_length = 0.7;
+ sec_length = 0.8; /* not drawn currently */
+ }
+
+ gtk_widget_get_allocation (this, &allocation);
+
+ x = allocation.x + allocation.width / 2;
+ y = allocation.y + allocation.height / 2;
+ radius = MIN (allocation.width / 2,
+ allocation.height / 2) - 5;
+
+ cairo_save (cr);
+ cairo_translate (cr, allocation.x, allocation.y);
+
+ /* clock back */
+ if (priv->face_pixbuf) {
+ GdkWindow *window = gtk_widget_get_window (this);
+ gdk_draw_pixbuf (GDK_DRAWABLE (window),
+ NULL,
+ priv->face_pixbuf,
+ 0, 0,
+ allocation.x,
+ allocation.y,
+ allocation.width,
+ allocation.height,
+ GDK_RGB_DITHER_NONE, 0, 0);
+ }
+
+ cairo_restore (cr);
+
+ /* clock hands */
+ hours = priv->time.tm_hour;
+ minutes = priv->time.tm_min + priv->minute_offset;
+ seconds = priv->time.tm_sec;
+
+ cairo_set_line_width (cr, 1);
+
+ /* hour hand:
+ * the hour hand is rotated 30 degrees (pi/6 r) per hour +
+ * 1/2 a degree (pi/360 r) per minute
+ */
+ cairo_save (cr);
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + radius * hour_length * sin (M_PI / 6 * hours +
+ M_PI / 360 * minutes),
+ y + radius * hour_length * -cos (M_PI / 6 * hours +
+ M_PI / 360 * minutes));
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ /* minute hand:
+ * the minute hand is rotated 6 degrees (pi/30 r) per minute
+ */
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + radius * min_length * sin (M_PI / 30 * minutes),
+ y + radius * min_length * -cos (M_PI / 30 * minutes));
+ cairo_stroke (cr);
+
+ /* seconds hand:
+ * operates identically to the minute hand
+ */
+ if (priv->size == CLOCK_FACE_LARGE) {
+ cairo_save (cr);
+ cairo_set_source_rgb (cr, 0.937, 0.161, 0.161); /* tango red */
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + radius * sec_length * sin (M_PI / 30 * seconds),
+ y + radius * sec_length * -cos (M_PI / 30 * seconds));
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ }
+}
+
+static gboolean
+clock_face_expose (GtkWidget *this, GdkEventExpose *event)
+{
+ cairo_t *cr;
+
+ /* get a cairo_t */
+ cr = gdk_cairo_create (gtk_widget_get_window (this));
+
+ cairo_rectangle (cr,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ cairo_clip (cr);
+
+ draw (this, cr);
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static void
+clock_face_redraw_canvas (ClockFace *this)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (this));
+}
+
+static void
+clock_face_size_request (GtkWidget *this,
+ GtkRequisition *requisition)
+{
+ ClockFacePrivate *priv = CLOCK_FACE_GET_PRIVATE (this);
+
+ if (priv->size_widget != NULL) {
+ GtkRequisition req;
+
+ /* Tie our size to the height of the size_widget */
+ gtk_widget_size_request (GTK_WIDGET (priv->size_widget), &req);
+
+ /* Pad out our height by a little bit - this improves
+ the balance */
+ requisition->width = req.height + req.height / 8;
+ requisition->height = req.height + req.height / 8;
+ } else if (priv->face_pixbuf != NULL) {
+ int w, h;
+
+ /* Use the size of the current pixbuf */
+ w = gdk_pixbuf_get_width (GDK_PIXBUF (priv->face_pixbuf));
+ h = gdk_pixbuf_get_height (GDK_PIXBUF (priv->face_pixbuf));
+
+ requisition->width = w;
+ requisition->height = h;
+ } else {
+ /* we don't know anything, so use known dimensions for the svg
+ * files */
+ if (priv->size == CLOCK_FACE_LARGE) {
+ requisition->width = 50;
+ requisition->height = 50;
+ } else {
+ requisition->width = 36;
+ requisition->height = 36;
+ }
+ }
+}
+
+static void
+clock_face_size_allocate (GtkWidget *this,
+ GtkAllocation *allocation)
+{
+ GtkAllocation this_allocation;
+ GtkAllocation old_allocation;
+
+ gtk_widget_get_allocation (this, &this_allocation);
+
+ old_allocation.width = this_allocation.width;
+ old_allocation.height = this_allocation.height;
+
+ if (GTK_WIDGET_CLASS (clock_face_parent_class)->size_allocate)
+ GTK_WIDGET_CLASS (clock_face_parent_class)->size_allocate (this, allocation);
+
+ if (old_allocation.width == allocation->width &&
+ old_allocation.height == allocation->height)
+ return;
+
+ /* Reload the face for the new size */
+ update_time_and_face (CLOCK_FACE (this), TRUE);
+}
+
+static void
+update_time_and_face (ClockFace *this,
+ gboolean force_face_loading)
+{
+ ClockFacePrivate *priv;
+ ClockFaceTimeOfDay timeofday;
+
+ priv = CLOCK_FACE_GET_PRIVATE (this);
+
+ /* update the time */
+ if (priv->location) {
+ clock_location_localtime (priv->location, &priv->time);
+ } else {
+ time_t timet;
+ time (&timet);
+ localtime_r (&timet, &priv->time);
+ }
+
+ /* FIXME this should be a mateconf setting
+ * Or we could use some code from clock-sun.c?
+ * currently we hardcode
+ * morning 7-9
+ * day 9-17
+ * evening 17-22
+ * night 22-7
+ */
+ if (priv->time.tm_hour < 7)
+ timeofday = CLOCK_FACE_NIGHT;
+ else if (priv->time.tm_hour < 9)
+ timeofday = CLOCK_FACE_MORNING;
+ else if (priv->time.tm_hour < 17)
+ timeofday = CLOCK_FACE_DAY;
+ else if (priv->time.tm_hour < 22)
+ timeofday = CLOCK_FACE_EVENING;
+ else
+ timeofday = CLOCK_FACE_NIGHT;
+
+ if (priv->timeofday != timeofday || force_face_loading) {
+ GtkAllocation allocation;
+ gint width, height;
+
+ priv->timeofday = timeofday;
+
+ gtk_widget_get_allocation (GTK_WIDGET (this), &allocation);
+
+ width = allocation.width;
+ height = allocation.height;
+
+ /* Only load the pixbuf if we have some space allocated.
+ * Note that 1x1 is not really some space... */
+ if (width > 1 && height > 1)
+ clock_face_load_face (this, width, height);
+ }
+}
+
+gboolean
+clock_face_refresh (ClockFace *this)
+{
+ update_time_and_face (this, FALSE);
+ clock_face_redraw_canvas (this);
+
+ return TRUE; /* keep running this event */
+}
+
+GtkWidget *
+clock_face_new (ClockFaceSize size)
+{
+ GObject *obj = g_object_new (INTL_TYPE_CLOCK_FACE, NULL);
+ ClockFacePrivate *priv = CLOCK_FACE_GET_PRIVATE (obj);
+
+ priv->size = size;
+
+ return GTK_WIDGET (obj);
+}
+
+GtkWidget *
+clock_face_new_with_location (ClockFaceSize size,
+ ClockLocation *loc,
+ GtkWidget *size_widget)
+{
+ GObject *obj = g_object_new (INTL_TYPE_CLOCK_FACE, NULL);
+ ClockFacePrivate *priv = CLOCK_FACE_GET_PRIVATE (obj);
+
+ priv->size = size;
+ priv->location = g_object_ref (loc);
+ priv->size_widget = g_object_ref (size_widget);
+
+ return GTK_WIDGET (obj);
+}
+
+static void
+clock_face_finalize (GObject *obj)
+{
+ ClockFacePrivate *priv = CLOCK_FACE_GET_PRIVATE (obj);
+
+ if (priv->location) {
+ g_object_unref (priv->location);
+ priv->location = NULL;
+ }
+
+ if (priv->face_pixbuf) {
+ g_object_unref (priv->face_pixbuf);
+ priv->face_pixbuf = NULL;
+ }
+
+ if (priv->size_widget) {
+ g_object_unref (priv->size_widget);
+ priv->size_widget = NULL;
+ }
+
+ G_OBJECT_CLASS (clock_face_parent_class)->finalize (obj);
+
+ if (pixbuf_cache && g_hash_table_size (pixbuf_cache) == 0) {
+ g_hash_table_destroy (pixbuf_cache);
+ pixbuf_cache = NULL;
+ }
+}
+
+/* The pixbuf is being disposed, so remove it from the cache */
+static void
+remove_pixbuf_from_cache (const char *key,
+ GObject *pixbuf)
+{
+ g_hash_table_remove (pixbuf_cache, key);
+}
+
+static void
+clock_face_load_face (ClockFace *this, gint width, gint height)
+{
+ ClockFacePrivate *priv = CLOCK_FACE_GET_PRIVATE (this);
+ const gchar *size_string[2] = { "small", "large" };
+ const gchar *daytime_string[4] = { "morning", "day", "evening", "night" };
+ gchar *cache_name;
+ gchar *name;
+
+ if (!pixbuf_cache)
+ pixbuf_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ if (priv->face_pixbuf != NULL) {
+ /* This might empty the cache, but it's useless to destroy
+ * it since this object is still alive and might add another
+ * pixbuf in the cache later (eg, a few lines below) */
+ g_object_unref (priv->face_pixbuf);
+ priv->face_pixbuf = NULL;
+ }
+
+ /* Look for the pixbuf in the process-wide cache first */
+ cache_name = g_strdup_printf ("%d-%d-%d-%d",
+ priv->size, priv->timeofday,
+ width, height);
+
+ priv->face_pixbuf = g_hash_table_lookup (pixbuf_cache, cache_name);
+ if (priv->face_pixbuf) {
+ g_object_ref (priv->face_pixbuf);
+ return;
+ }
+
+ /* The pixbuf is not cached, let's load it */
+ name = g_strconcat (ICONDIR, "/clock-face-", size_string[priv->size],
+ "-", daytime_string[priv->timeofday], ".svg",
+ NULL);
+ priv->face_pixbuf = rsvg_pixbuf_from_file_at_size (name,
+ width, height,
+ NULL);
+ g_free (name);
+
+ if (!priv->face_pixbuf) {
+ name = g_strconcat (ICONDIR, "/clock-face-",
+ size_string[priv->size], ".svg", NULL);
+ priv->face_pixbuf = rsvg_pixbuf_from_file_at_size (name,
+ width,
+ height,
+ NULL);
+ g_free (name);
+ }
+
+ /* Save the found pixbuf in the cache */
+ if (priv->face_pixbuf) {
+ g_hash_table_replace (pixbuf_cache,
+ cache_name, priv->face_pixbuf);
+ /* This will handle automatic removal from the cache when
+ * the pixbuf isn't needed anymore */
+ g_object_weak_ref (G_OBJECT (priv->face_pixbuf),
+ (GWeakNotify) remove_pixbuf_from_cache,
+ cache_name);
+ } else
+ g_free (cache_name);
+}