/* * Author: Chris Lord * * Copyright (c) 2007 OpenedHand Ltd - http://www.openedhand.com/ * * 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, 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. * */ #include "jana-gtk-clock.h" #include G_DEFINE_TYPE (JanaGtkClock, jana_gtk_clock, GTK_TYPE_EVENT_BOX) #define CLOCK_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), JANA_GTK_TYPE_CLOCK, JanaGtkClockPrivate)) typedef struct _JanaGtkClockPrivate JanaGtkClockPrivate; struct _JanaGtkClockPrivate { gboolean digital; gboolean show_seconds; gboolean buffer_time; gboolean draw_shadow; cairo_surface_t *buffer; JanaTime *time; }; enum { PROP_TIME = 1, PROP_DIGITAL, PROP_SHOW_SECONDS, PROP_BUFFER_TIME, PROP_DRAW_SHADOW, }; static void jana_gtk_clock_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (object); switch (property_id) { case PROP_TIME : g_value_set_object (value, priv->time); break; case PROP_DIGITAL : g_value_set_boolean (value, priv->digital); break; case PROP_SHOW_SECONDS : g_value_set_boolean (value, priv->show_seconds); break; case PROP_BUFFER_TIME : g_value_set_boolean (value, priv->buffer_time); break; case PROP_DRAW_SHADOW : g_value_set_boolean (value, priv->draw_shadow); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void jana_gtk_clock_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_TIME : jana_gtk_clock_set_time (JANA_GTK_CLOCK (object), g_value_get_object (value)); break; case PROP_DIGITAL : jana_gtk_clock_set_digital (JANA_GTK_CLOCK (object), g_value_get_boolean (value)); break; case PROP_SHOW_SECONDS : jana_gtk_clock_set_show_seconds (JANA_GTK_CLOCK (object), g_value_get_boolean (value)); break; case PROP_BUFFER_TIME : jana_gtk_clock_set_buffer_time (JANA_GTK_CLOCK (object), g_value_get_boolean (value)); break; case PROP_DRAW_SHADOW : jana_gtk_clock_set_draw_shadow (JANA_GTK_CLOCK (object), g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void jana_gtk_clock_dispose (GObject *object) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (object); if (priv->time) { g_object_unref (priv->time); priv->time = NULL; } if (G_OBJECT_CLASS (jana_gtk_clock_parent_class)->dispose) G_OBJECT_CLASS (jana_gtk_clock_parent_class)->dispose (object); } static void jana_gtk_clock_finalize (GObject *object) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (object); if (priv->buffer) { cairo_surface_destroy (priv->buffer); priv->buffer = NULL; } G_OBJECT_CLASS (jana_gtk_clock_parent_class)->finalize (object); } static void draw_analogue_face (JanaGtkClock *clock, cairo_t *cr) { gdouble pi_ratio; gint width, height, size, thickness; GtkWidget *widget = (GtkWidget *)clock; JanaGtkClockPrivate *priv = CLOCK_PRIVATE (clock); if (!priv->time) return; width = widget->allocation.width; height = widget->allocation.height; if (priv->draw_shadow) height -= height/20; size = MIN (width, height); gdk_cairo_set_source_color (cr, &widget->style->fg[GTK_STATE_NORMAL]); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_set_line_width (cr, MAX (1.5, size / 60)); thickness = size / 20; /* Draw hour hand */ pi_ratio = (gdouble)jana_time_get_hours (priv->time)/6; cairo_new_path (cr); cairo_move_to (cr, (width/2) + ((size/2 - thickness/2 - size/4) * cos ((pi_ratio * M_PI)-(M_PI/2))), (height/2) + ((size/2 - thickness/2 - size/4) * sin ((pi_ratio * M_PI)-(M_PI/2)))); cairo_line_to (cr, (width/2) + ((size/35) * cos ((pi_ratio * M_PI)-(M_PI/2))), (height/2) +((size/35) * sin ((pi_ratio * M_PI)-(M_PI/2)))); cairo_close_path (cr); cairo_stroke (cr); /* Draw minute hand */ pi_ratio = (gdouble)jana_time_get_minutes (priv->time)/30.0; cairo_new_path (cr); cairo_move_to (cr, (width/2) + ((size/2 - thickness/2 - size/8) * cos ((pi_ratio * M_PI)-(M_PI/2))), (height/2) + ((size/2 - thickness/2 - size/8) * sin ((pi_ratio * M_PI)-(M_PI/2)))); cairo_line_to (cr, (width/2) + ((size/35) * cos ((pi_ratio * M_PI)-(M_PI/2))), (height/2) +((size/35) * sin ((pi_ratio * M_PI)-(M_PI/2)))); cairo_close_path (cr); cairo_stroke (cr); if (!priv->show_seconds) return; /* Draw second hand */ gdk_cairo_set_source_color (cr, &widget->style->bg[GTK_STATE_SELECTED]); cairo_set_line_width (cr, MAX (1, size / 120)); pi_ratio = (gdouble)jana_time_get_seconds (priv->time)/30.0; cairo_new_path (cr); cairo_move_to (cr, (width/2) + ((size/2 - thickness/2 - size/8) * cos ((pi_ratio * M_PI)-(M_PI/2))), (height/2) + ((size/2 - thickness/2 - size/8) * sin ((pi_ratio * M_PI)-(M_PI/2)))); cairo_line_to (cr, (width/2) + ((size/35) * cos ((pi_ratio * M_PI)-(M_PI/2))), (height/2) +((size/35) * sin ((pi_ratio * M_PI)-(M_PI/2)))); cairo_close_path (cr); cairo_stroke (cr); } static void draw_analogue_clock (JanaGtkClock *clock, cairo_t *cr) { cairo_pattern_t *pattern; gint width, height, size, thickness, i, shadow_radius; double base_color[3]; double bg_color[3]; double fg_color[3]; GtkWidget *widget = (GtkWidget *)clock; JanaGtkClockPrivate *priv = CLOCK_PRIVATE (clock); /* Draw a Tango-style analogue clock face */ base_color[0] = ((double)widget->style->bg[GTK_STATE_SELECTED].red)/ (double)G_MAXUINT16; base_color[1] = ((double)widget->style->bg[GTK_STATE_SELECTED].green)/ (double)G_MAXUINT16; base_color[2] = ((double)widget->style->bg[GTK_STATE_SELECTED].blue)/ (double)G_MAXUINT16; bg_color[0] = ((double)widget->style->base[GTK_STATE_NORMAL].red)/ (double)G_MAXUINT16; bg_color[1] = ((double)widget->style->base[GTK_STATE_NORMAL].green)/ (double)G_MAXUINT16; bg_color[2] = ((double)widget->style->base[GTK_STATE_NORMAL].blue)/ (double)G_MAXUINT16; fg_color[0] = ((double)widget->style->text[GTK_STATE_NORMAL].red)/ (double)G_MAXUINT16; fg_color[1] = ((double)widget->style->text[GTK_STATE_NORMAL].green)/ (double)G_MAXUINT16; fg_color[2] = ((double)widget->style->text[GTK_STATE_NORMAL].blue)/ (double)G_MAXUINT16; width = widget->allocation.width; height = widget->allocation.height; if (priv->draw_shadow) height -= height/20; size = MIN (width, height); cairo_paint_with_alpha (cr, 0); /* Draw shadow */ shadow_radius = MIN (width, widget->allocation.height) - size; cairo_translate (cr, width/2, (height/2) + (size/2)); cairo_scale (cr, (gdouble)size / (gdouble)(shadow_radius*2), 1.0); cairo_new_path (cr); cairo_arc (cr, 0, 0, shadow_radius, 0, 2 * M_PI); cairo_close_path (cr); pattern = cairo_pattern_create_radial (0, 0, 0, 0, 0, shadow_radius); cairo_pattern_add_color_stop_rgba (pattern, 0, 0, 0, 0, 0.5); cairo_pattern_add_color_stop_rgba (pattern, 1, 0, 0, 0, 0); cairo_set_source (cr, pattern); cairo_fill (cr); cairo_pattern_destroy (pattern); cairo_identity_matrix (cr); /* Draw clock face */ thickness = size / 20; cairo_new_path (cr); cairo_arc (cr, width/2, height/2, size/2 - thickness/2, 0, 2 * M_PI); cairo_close_path (cr); pattern = cairo_pattern_create_radial (width/2, height/3, 0, width/2, height/2, size/2 - thickness/2); cairo_pattern_add_color_stop_rgb (pattern, 0, bg_color[0]*2, bg_color[1]*2, bg_color[2]*2); cairo_pattern_add_color_stop_rgb (pattern, 0.3, bg_color[0], bg_color[1], bg_color[2]); cairo_pattern_add_color_stop_rgb (pattern, 1, bg_color[0]/1.15, bg_color[1]/1.15, bg_color[2]/1.15); cairo_set_source (cr, pattern); cairo_fill (cr); cairo_pattern_destroy (pattern); /* Draw tick marks */ cairo_set_source_rgb (cr, fg_color[0], fg_color[1], fg_color[2]); for (i = 0; i < 4; i++) { cairo_new_path (cr); cairo_arc (cr, (width/2) + ((size/2 - thickness/2 - size/6) * cos (i * M_PI/2)), (height/2) + ((size/2 - thickness/2 - size/6) * sin (i * M_PI/2)), size/40, 0, 2 * M_PI); cairo_close_path (cr); cairo_fill (cr); } /* Draw centre point */ cairo_new_path (cr); cairo_arc (cr, width/2, height/2, size/35, 0, 2 * M_PI); cairo_close_path (cr); cairo_set_line_width (cr, size/60); cairo_stroke (cr); /* Draw internal clock-frame shadow */ thickness = size / 20; cairo_new_path (cr); cairo_arc (cr, width/2, height/2, size/2 - thickness, 0, 2 * M_PI); cairo_close_path (cr); pattern = cairo_pattern_create_radial ((width/2) - (size/4), (height/2) - (size/4), 0, width/2, height/2, size/2 - thickness/2); cairo_pattern_add_color_stop_rgb (pattern, 0, bg_color[0]/2, bg_color[1]/2, bg_color[2]/2); cairo_pattern_add_color_stop_rgb (pattern, 0.5, bg_color[0]/2, bg_color[1]/2, bg_color[2]/2); cairo_pattern_add_color_stop_rgb (pattern, 1, bg_color[0]*2, bg_color[1]*2, bg_color[2]*2); cairo_set_source (cr, pattern); cairo_set_line_width (cr, thickness); cairo_stroke (cr); cairo_pattern_destroy (pattern); /* Draw internal clock-frame */ cairo_new_path (cr); cairo_arc (cr, width/2, height/2, size/2 - thickness/2, 0, 2 * M_PI); cairo_close_path (cr); pattern = cairo_pattern_create_radial ((width/2) - (size/3), (height/2) - (size/3), 0, width/3, height/3, size/2); cairo_pattern_add_color_stop_rgb (pattern, 0, base_color[0]*1.2, base_color[1]*1.2, base_color[2]*1.2); cairo_pattern_add_color_stop_rgb (pattern, 0.7, base_color[0], base_color[1], base_color[2]); cairo_pattern_add_color_stop_rgb (pattern, 1, base_color[0]/1.2, base_color[1]/1.2, base_color[2]/1.2); cairo_set_source (cr, pattern); cairo_stroke (cr); cairo_pattern_destroy (pattern); /* Dark outline frame */ thickness = size / 60; cairo_new_path (cr); cairo_arc (cr, width/2, height/2, size/2 - thickness/2, 0, 2 * M_PI); cairo_close_path (cr); cairo_set_source_rgb (cr, base_color[0]/2, base_color[1]/2, base_color[2]/2); cairo_set_line_width (cr, thickness); cairo_stroke (cr); /* Draw less dark inner outline frame */ thickness = size / 40; cairo_new_path (cr); cairo_arc (cr, width/2, height/2, size/2 - (size/20)/2 - thickness, 0, 2 * M_PI); cairo_close_path (cr); cairo_set_source_rgb (cr, base_color[0]/1.5, base_color[1]/1.5, base_color[2]/1.5); cairo_set_line_width (cr, thickness); cairo_stroke (cr); } static gboolean jana_gtk_clock_expose_event (GtkWidget *widget, GdkEventExpose *event) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (widget); cairo_t *cr = gdk_cairo_create (widget->window); cairo_translate (cr, widget->allocation.x, widget->allocation.y); /* Draw background */ if (priv->buffer) { cairo_set_source_surface (cr, priv->buffer, 0, 0); cairo_paint_with_alpha (cr, 1.0); } /* Draw face */ if (!priv->buffer_time) draw_analogue_face ((JanaGtkClock *)widget, cr); cairo_destroy (cr); return GTK_WIDGET_CLASS (jana_gtk_clock_parent_class)-> expose_event (widget, event); } static void refresh_buffer (JanaGtkClock *self) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (self); cairo_t *cr = cairo_create (priv->buffer); if (priv->digital) { } else { draw_analogue_clock (self, cr); if (priv->buffer_time) draw_analogue_face (self, cr); } cairo_destroy (cr); } static void jana_gtk_clock_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (widget); /* Note: Calling this after the below breaks things, not sure why */ GTK_WIDGET_CLASS (jana_gtk_clock_parent_class)-> size_allocate (widget, allocation); if (!GTK_WIDGET_REALIZED (widget)) gtk_widget_realize (widget); if ((!priv->buffer) || (allocation->width != cairo_image_surface_get_width (priv->buffer)) || (allocation->height != cairo_image_surface_get_height (priv->buffer))) { if (priv->buffer) cairo_surface_destroy (priv->buffer); priv->buffer = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, allocation->width, allocation->height); refresh_buffer (JANA_GTK_CLOCK (widget)); } } static void jana_gtk_clock_style_set (GtkWidget *widget, GtkStyle *previous_style) { GTK_WIDGET_CLASS (jana_gtk_clock_parent_class)-> style_set (widget, previous_style); refresh_buffer (JANA_GTK_CLOCK (widget)); } static void jana_gtk_clock_class_init (JanaGtkClockClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); g_type_class_add_private (klass, sizeof (JanaGtkClockPrivate)); object_class->get_property = jana_gtk_clock_get_property; object_class->set_property = jana_gtk_clock_set_property; object_class->dispose = jana_gtk_clock_dispose; object_class->finalize = jana_gtk_clock_finalize; widget_class->expose_event = jana_gtk_clock_expose_event; widget_class->size_allocate = jana_gtk_clock_size_allocate; widget_class->style_set = jana_gtk_clock_style_set; g_object_class_install_property ( object_class, PROP_TIME, g_param_spec_object ( "time", "JanaTime *", "The JanaTime the clock will show.", G_TYPE_OBJECT, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_DIGITAL, g_param_spec_boolean ( "digital", "gboolean", "Whether to show a digital clock, as opposed to an " "analogue clock.", FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_SHOW_SECONDS, g_param_spec_boolean ( "show_seconds", "gboolean", "Whether to show seconds on the clock face.", FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_BUFFER_TIME, g_param_spec_boolean ( "buffer_time", "gboolean", "Whether to double-buffer the time. Set this to " "%TRUE if you don't expect to change the time " "frequently.", FALSE, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_DRAW_SHADOW, g_param_spec_boolean ( "draw_shadow", "gboolean", "Whether to draw a shadow underneath the clock.", FALSE, G_PARAM_READWRITE)); } static void jana_gtk_clock_init (JanaGtkClock *self) { gtk_widget_set_app_paintable (GTK_WIDGET (self), TRUE); gtk_event_box_set_visible_window (GTK_EVENT_BOX (self), FALSE); } GtkWidget * jana_gtk_clock_new (void) { return GTK_WIDGET (g_object_new (JANA_GTK_TYPE_CLOCK, NULL)); } void jana_gtk_clock_set_time (JanaGtkClock *self, JanaTime *time) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (self); if (priv->time) { g_object_unref (priv->time); priv->time = NULL; } if (time) priv->time = jana_time_duplicate (time); if (priv->buffer_time) refresh_buffer (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } void jana_gtk_clock_set_digital (JanaGtkClock *self, gboolean digital) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (self); if (priv->digital != digital) { priv->digital = digital; refresh_buffer (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } } void jana_gtk_clock_set_show_seconds (JanaGtkClock *self, gboolean show_seconds) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (self); if (priv->show_seconds != show_seconds) { priv->show_seconds = show_seconds; if (priv->buffer_time) refresh_buffer (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } } void jana_gtk_clock_set_buffer_time (JanaGtkClock *self, gboolean buffer_time) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (self); if (priv->buffer_time != buffer_time) { priv->buffer_time = buffer_time; refresh_buffer (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } } void jana_gtk_clock_set_draw_shadow (JanaGtkClock *self, gboolean draw_shadow) { JanaGtkClockPrivate *priv = CLOCK_PRIVATE (self); if (priv->draw_shadow != draw_shadow) { priv->draw_shadow = draw_shadow; refresh_buffer (self); gtk_widget_queue_draw (GTK_WIDGET (self)); } }