/* example_clock_desktop_widget.c */ #ifdef HAVE_CONFIG_H #include #endif #include "example_clock_desktop_widget.h" #include #include #include #include /* For M_PI. */ #include /* Declares _(). */ /** This example desktop widget draws an analogue clock which updates itself every * second. * * It is a shared librarary that implements a derived HDHomePluginItem (a * desktop widget), with the help of the HD_DEFINE_PLUGIN_MODULE() macro, which * also implements some standard functions that all desktop widget libraries * should export. * * Note that the API contains the name "home plugin", but the correct name * is now "desktop widget". * * As with other custom GTK+ widgets, drawing happens only in a handler for the * expose-event signal. This is triggered every second by a timeout handler * that invalidates the desktop widget's area. * * TODO: Show hd_home_plugin_item_set_resize_type() if that appears in a * future version of the SDK. It is mentioned in the hello-world-home.c example. * */ /** This is like G_DEFINE_TYPE(), but it also * implements standard functions such as hd_plugin_module_load(), * which hildon-desktop expects to be able to call on this library. */ HD_DEFINE_PLUGIN_MODULE (ExampleClockDesktopWidget, example_clock_desktop_widget, HD_TYPE_HOME_PLUGIN_ITEM) /* This use of cairo is based on Dave Madeley's example code from * http://gnomejournal.org/article/36/writing-a-widget-using-cairo-and-gtk28-part-2 */ static void draw (ExampleClockDesktopWidget *self, cairo_t *cr) { double x, y = 0; double radius = 0; int i = 0; int hours, minutes, seconds = 0; GtkWidget *widget = GTK_WIDGET(self); /** Make the background transparent: * Note that this works because we used gtk_widget_set_colormap() in * our realize() implementation. */ cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); x = widget->allocation.width / 2; y = widget->allocation.height / 2; radius = MIN (widget->allocation.width / 2, widget->allocation.height / 2) - 5; /* The clock's back surface: */ cairo_arc (cr, x, y, radius, 0, 2 * M_PI); /* TODO: Clutter seems to complain about some colors. * How can we restrict the choice of colors? */ cairo_set_source_rgb (cr, self->color.red/65535., self->color.green/65535., self->color.blue/65535.); cairo_fill_preserve (cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_stroke (cr); /* The clock's tick marks: */ for (i = 0; i < 12; i++) { int inset = 0; cairo_save (cr); /* stack-pen-size */ if (i % 3 == 0) { inset = 0.2 * radius; } else { inset = 0.1 * radius; cairo_set_line_width (cr, 0.5 * cairo_get_line_width (cr)); } cairo_move_to (cr, x + (radius - inset) * cos (i * M_PI / 6), y + (radius - inset) * sin (i * M_PI / 6)); cairo_line_to (cr, x + radius * cos (i * M_PI / 6), y + radius * sin (i * M_PI / 6)); cairo_stroke (cr); cairo_restore (cr); /* stack-pen-size */ } /* The clock hands: */ hours = self->time.tm_hour; minutes = self->time.tm_min + 0; /* TODO: clock->minute_offset; */ seconds = self->time.tm_sec; /* 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_set_line_width (cr, 2.5 * cairo_get_line_width (cr)); cairo_move_to (cr, x, y); cairo_line_to (cr, x + radius / 2 * sin (M_PI / 6 * hours + M_PI / 360 * minutes), y + radius / 2 * -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 * 0.75 * sin (M_PI / 30 * minutes), y + radius * 0.75 * -cos (M_PI / 30 * minutes)); cairo_stroke (cr); /* Seconds hand: * Operates identically to the minute hand. */ cairo_save (cr); cairo_set_source_rgb (cr, 1, 0, 0); /* red */ cairo_move_to (cr, x, y); cairo_line_to (cr, x + radius * 0.7 * sin (M_PI / 30 * seconds), y + radius * 0.7 * -cos (M_PI / 30 * seconds)); cairo_stroke (cr); cairo_restore (cr); } static gboolean example_clock_desktop_widget_on_timeout (gpointer data) { ExampleClockDesktopWidget *self = EXAMPLE_CLOCK_DESKTOP_WIDGET (data); /* Update the time: */ time_t timet; time (&timet); localtime_r (&timet, &self->time); /* Invalidate the drawing: */ GtkWidget *widget = GTK_WIDGET (data); if (!widget->window) return TRUE; /* Redraw the cairo canvas completely by exposing it: */ GdkRegion *region = gdk_drawable_get_clip_region (widget->window); if (!region) return TRUE; gdk_window_invalidate_region (widget->window, region, TRUE); gdk_window_process_updates (widget->window, TRUE); gdk_region_destroy (region); return TRUE; /* keep running this event */ } static void example_clock_desktop_widget_realize (GtkWidget *widget) { /* Use An RGBA colormap rather than RGB, * so we can use transparency in our expose_event() implementation. */ GdkScreen *screen = gtk_widget_get_screen (widget); gtk_widget_set_colormap (widget, gdk_screen_get_rgba_colormap (screen)); gtk_widget_set_app_paintable (widget, TRUE); /* Call the base class's implementation: */ GTK_WIDGET_CLASS (example_clock_desktop_widget_parent_class)->realize (widget); } static gboolean example_clock_desktop_widget_expose_event (GtkWidget *widget, GdkEventExpose *event) { cairo_t *cr = gdk_cairo_create (widget->window); /* Clip only the exposed area of the cairo context, * to potentially avoid unnecessary drawing: */ cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height); cairo_clip (cr); draw (EXAMPLE_CLOCK_DESKTOP_WIDGET (widget), cr); cairo_destroy (cr); return FALSE; } static gboolean example_clock_desktop_widget_on_button_press_event (GtkWidget *widget, GdkEventButton * event G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { ExampleClockDesktopWidget *self = EXAMPLE_CLOCK_DESKTOP_WIDGET (widget); gchar* text = g_strdup_printf(_("Current Time: %s"), asctime(&(self->time))); hildon_banner_show_information (GTK_WIDGET (self), NULL, /* icon_name - ignored */ text); g_free (text); return TRUE; /* Prevent further handling of this signal. */ } static void example_clock_desktop_widget_on_settings_dialog_response (GtkDialog *dialog, gint response_id, gpointer user_data) { ExampleClockDesktopWidget *self = EXAMPLE_CLOCK_DESKTOP_WIDGET (user_data); g_assert (self->settings_window); g_assert (self->settings_window == GTK_WIDGET (dialog)); /* Get the chosen color from the button: * This will be used by the next timeout redraw. */ gtk_color_button_get_color (GTK_COLOR_BUTTON (self->color_button), &(self->color) ); gchar* debug = gdk_color_to_string (&(self->color)); printf("debug: chosen color = %s\n", debug); g_free (debug); gtk_widget_destroy (self->settings_window); self->settings_window = NULL; self->color_button = NULL; } static void example_clock_desktop_widget_on_show_settings (GtkWidget *widget G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { ExampleClockDesktopWidget *self = EXAMPLE_CLOCK_DESKTOP_WIDGET (widget); if (!self->settings_window) { /* The Hildon HIG says "Dialogs should be used for small tasks" so * we use GtkDialog instead of HildonWindow: */ self->settings_window = gtk_dialog_new_with_buttons( _("Clock Desktop Widget Settings"), NULL /* parent window */, GTK_DIALOG_NO_SEPARATOR /* flags */, GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL); g_signal_connect (self->settings_window, "response", G_CALLBACK (example_clock_desktop_widget_on_settings_dialog_response), self); /* TODO: Should we use HILDON_MARGIN_* here, or something else. * They are undocumented: * https://bugs.maemo.org/show_bug.cgi?id=4462 * https://bugs.maemo.org/show_bug.cgi?id=4463 * https://bugs.maemo.org/show_bug.cgi?id=4464 */ GtkWidget *vbox = GTK_DIALOG(self->settings_window)->vbox; gtk_container_set_border_width(GTK_CONTAINER (vbox), HILDON_MARGIN_DEFAULT); GtkWidget *hbox = gtk_hbox_new (FALSE, HILDON_MARGIN_DEFAULT); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); GtkWidget *label = gtk_label_new (_("Clock Face Color:")); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); self->color_button = gtk_color_button_new (); gtk_box_pack_start (GTK_BOX (hbox), self->color_button, TRUE, TRUE, 0); gtk_widget_show (self->color_button); } gtk_color_button_set_color (GTK_COLOR_BUTTON (self->color_button), &(self->color) ); gtk_widget_show (self->settings_window); } static void example_clock_desktop_widget_dispose (GObject *object) { ExampleClockDesktopWidget *self = EXAMPLE_CLOCK_DESKTOP_WIDGET (object); if (self->settings_window) { gtk_widget_destroy (self->settings_window); self->settings_window = NULL; self->color_button = NULL; } /* Call the base class's implementation: */ G_OBJECT_CLASS (example_clock_desktop_widget_parent_class)->dispose (object); } static void example_clock_desktop_widget_finalize (GObject *object) { ExampleClockDesktopWidget *self = EXAMPLE_CLOCK_DESKTOP_WIDGET (object); /* Remove the timout handler: */ if (self->timeout_handler) { g_source_remove (self->timeout_handler); } /* Call the base class's implementation: */ G_OBJECT_CLASS (example_clock_desktop_widget_parent_class)->finalize (object); } static void example_clock_desktop_widget_class_init (ExampleClockDesktopWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = example_clock_desktop_widget_dispose; object_class->finalize = example_clock_desktop_widget_finalize; widget_class->realize = example_clock_desktop_widget_realize; widget_class->expose_event = example_clock_desktop_widget_expose_event; } static void example_clock_desktop_widget_class_finalize (ExampleClockDesktopWidgetClass *klass G_GNUC_UNUSED) { } static void example_clock_desktop_widget_init (ExampleClockDesktopWidget *self) { /* Update the clock once a second: */ self->timeout_handler = g_timeout_add (1000, example_clock_desktop_widget_on_timeout, self); /* Set the default clock face color: */ gdk_color_parse ("white", &(self->color)); /* Allow this widget to handle button-press events: * Note that gtk_widget_add_events would only work after the widget is realized: */ gint mask = gtk_widget_get_events (GTK_WIDGET (self)) | GDK_BUTTON_PRESS_MASK; gtk_widget_set_events (GTK_WIDGET (self), mask); /* Handle a mouse button press: */ g_signal_connect (self, "button-press-event", G_CALLBACK (example_clock_desktop_widget_on_button_press_event), NULL); /* Specify that a settings button should be shown in layout mode, * and handle a request to configure the settings: */ /* TODO: This is not visible in the Beta SDK, but neither are other things: * See https://bugs.maemo.org/show_bug.cgi?id=4439 */ hd_home_plugin_item_set_settings (HD_HOME_PLUGIN_ITEM (self), TRUE); g_signal_connect (self, "show-settings", G_CALLBACK (example_clock_desktop_widget_on_show_settings), NULL); } ExampleClockDesktopWidget* example_clock_desktop_widget_new (void) { return g_object_new (EXAMPLE_TYPE_CLOCK_DESKTOP_WIDGET, NULL); }