/* * e-contact-marker.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 * * Copyright (C) 2008 Pierre-Luc Beaudoin * Copyright (C) 2011 Jiri Techet * Copyright (C) 2011 Dan Vratil * */ #ifdef HAVE_CONFIG_H #include #endif #ifdef WITH_CONTACT_MAPS #include "e-contact-marker.h" #include #include #include #include #include #include #include #include #include #define E_CONTACT_MARKER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_CONTACT_MARKER, EContactMarkerPrivate)) G_DEFINE_TYPE (EContactMarker, e_contact_marker, CHAMPLAIN_TYPE_LABEL); struct _EContactMarkerPrivate { gchar *contact_uid; ClutterActor *image; ClutterActor *text_actor; ClutterActor *shadow; ClutterActor *background; guint total_width; guint total_height; ClutterGroup *content_group; guint redraw_id; }; enum { DOUBLE_CLICKED, LAST_SIGNAL }; static gint signals[LAST_SIGNAL] = {0}; #define DEFAULT_FONT_NAME "Serif 9" static ClutterColor DEFAULT_COLOR = { 0x33, 0x33, 0x33, 0xff }; #define RADIUS 10 #define PADDING (RADIUS / 2) static gboolean contact_marker_clicked_cb (ClutterActor *actor, ClutterEvent *event, gpointer user_data) { gint click_count = clutter_event_get_click_count (event); if (click_count == 2) g_signal_emit (E_CONTACT_MARKER (actor), signals[DOUBLE_CLICKED], 0); return TRUE; } static ClutterActor * texture_new_from_pixbuf (GdkPixbuf *pixbuf, GError **error) { ClutterActor *texture = NULL; const guchar *data; gboolean has_alpha, success; gint width, height, rowstride; ClutterTextureFlags flags = 0; data = gdk_pixbuf_get_pixels (pixbuf); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); texture = clutter_texture_new (); success = clutter_texture_set_from_rgb_data ( CLUTTER_TEXTURE (texture), data, has_alpha, width, height, rowstride, (has_alpha ? 4: 3), flags, NULL); if (!success) { clutter_actor_destroy (CLUTTER_ACTOR (texture)); texture = NULL; } return texture; } static ClutterActor * contact_photo_to_texture (EContactPhoto *photo) { GdkPixbuf *pixbuf; if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) { GError *error = NULL; GdkPixbufLoader *loader = gdk_pixbuf_loader_new (); gdk_pixbuf_loader_write ( loader, photo->data.inlined.data, photo->data.inlined.length, NULL); gdk_pixbuf_loader_close (loader, NULL); pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (pixbuf) g_object_ref (pixbuf); g_object_unref (loader); if (error) { g_error_free (error); return NULL; } } else if (photo->type == E_CONTACT_PHOTO_TYPE_URI) { GError *error = NULL; pixbuf = gdk_pixbuf_new_from_file (photo->data.uri, &error); if (error) { g_error_free (error); return NULL; } } else return NULL; if (pixbuf) { ClutterActor *texture; GError *error = NULL; texture = texture_new_from_pixbuf (pixbuf, &error); if (error) { g_error_free (error); g_object_unref (pixbuf); return NULL; } g_object_unref (pixbuf); return texture; } return NULL; } static void draw_box (cairo_t *cr, gint width, gint height, gint point) { cairo_move_to (cr, RADIUS, 0); cairo_line_to (cr, width - RADIUS, 0); cairo_arc (cr, width - RADIUS, RADIUS, RADIUS - 1, 3 * M_PI / 2.0, 0); cairo_line_to (cr, width, height - RADIUS); cairo_arc (cr, width - RADIUS, height - RADIUS, RADIUS - 1, 0, M_PI / 2.0); cairo_line_to (cr, point, height); cairo_line_to (cr, 0, height + point); cairo_arc (cr, RADIUS, RADIUS, RADIUS - 1, M_PI, 3 * M_PI / 2.0); cairo_close_path (cr); } static void draw_shadow (EContactMarker *marker, gint width, gint height, gint point) { EContactMarkerPrivate *priv = marker->priv; ClutterActor *shadow = NULL; cairo_t *cr; gdouble slope; gdouble scaling; gint x; cairo_matrix_t matrix; slope = -0.3; scaling = 0.65; x = -40 * slope; shadow = clutter_cairo_texture_new (width + x, (height + point)); cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (shadow)); cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_matrix_init (&matrix, 1, 0, slope, scaling, x, 0); cairo_set_matrix (cr, &matrix); draw_box (cr, width, height, point); cairo_set_source_rgba (cr, 0, 0, 0, 0.15); cairo_fill (cr); cairo_destroy (cr); clutter_actor_set_position (shadow, 0, height / 2.0); clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), shadow); if (priv->shadow != NULL) { clutter_container_remove_actor ( CLUTTER_CONTAINER (priv->content_group), priv->shadow); } priv->shadow = shadow; } static void draw_background (EContactMarker *marker, gint width, gint height, gint point) { EContactMarkerPrivate *priv = marker->priv; ClutterActor *bg = NULL; const ClutterColor *color; ClutterColor darker_color; cairo_t *cr; bg = clutter_cairo_texture_new (width, height + point); cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (bg)); cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); /* If selected, add the selection color to the marker's color */ if (champlain_marker_get_selected (CHAMPLAIN_MARKER (marker))) color = champlain_marker_get_selection_color (); else color = &DEFAULT_COLOR; draw_box (cr, width, height, point); clutter_color_darken (color, &darker_color); cairo_set_source_rgba ( cr, color->red / 255.0, color->green / 255.0, color->blue / 255.0, color->alpha / 255.0); cairo_fill_preserve (cr); cairo_set_line_width (cr, 1.0); cairo_set_source_rgba ( cr, darker_color.red / 255.0, darker_color.green / 255.0, darker_color.blue / 255.0, darker_color.alpha / 255.0); cairo_stroke (cr); cairo_destroy (cr); clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), bg); if (priv->background != NULL) { clutter_container_remove_actor ( CLUTTER_CONTAINER (priv->content_group), priv->background); } priv->background = bg; } static void draw_marker (EContactMarker *marker) { EContactMarkerPrivate *priv = marker->priv; ChamplainLabel *label = CHAMPLAIN_LABEL (marker); guint height = 0, point = 0; guint total_width = 0, total_height = 0; ClutterText *text; if (priv->image) { clutter_actor_set_position (priv->image, 2 *PADDING, 2 *PADDING); if (clutter_actor_get_parent (priv->image) == NULL) clutter_container_add_actor ( CLUTTER_CONTAINER (priv->content_group), priv->image); } if (priv->text_actor == NULL) { priv->text_actor = clutter_text_new_with_text ( "Serif 8", champlain_label_get_text (label)); champlain_label_set_font_name (label, "Serif 8"); } text = CLUTTER_TEXT (priv->text_actor); clutter_text_set_text ( text, champlain_label_get_text (label)); clutter_text_set_font_name ( text, champlain_label_get_font_name (label)); clutter_text_set_line_alignment (text, PANGO_ALIGN_CENTER); clutter_text_set_line_wrap (text, TRUE); clutter_text_set_line_wrap_mode (text, PANGO_WRAP_WORD); clutter_text_set_ellipsize ( text, champlain_label_get_ellipsize (label)); clutter_text_set_attributes ( text, champlain_label_get_attributes (label)); clutter_text_set_use_markup ( text, champlain_label_get_use_markup (label)); if (priv->image) { clutter_actor_set_width ( priv->text_actor, clutter_actor_get_width (priv->image)); total_height = clutter_actor_get_height (priv->image) + 2 *PADDING + clutter_actor_get_height (priv->text_actor) + 2 *PADDING; total_width = clutter_actor_get_width (priv->image) + 4 *PADDING; clutter_actor_set_position ( priv->text_actor, PADDING, clutter_actor_get_height (priv->image) + 2 *PADDING + 3); } else { total_height = clutter_actor_get_height (priv->text_actor) + 2 *PADDING; total_width = clutter_actor_get_width (priv->text_actor) + 4 *PADDING; clutter_actor_set_position (priv->text_actor, 2 * PADDING, PADDING); } height += 2 * PADDING; if (height > total_height) total_height = height; clutter_text_set_color ( CLUTTER_TEXT (priv->text_actor), (champlain_marker_get_selected (CHAMPLAIN_MARKER (marker)) ? champlain_marker_get_selection_text_color () : champlain_label_get_text_color (CHAMPLAIN_LABEL (marker)))); if (clutter_actor_get_parent (priv->text_actor) == NULL) clutter_container_add_actor ( CLUTTER_CONTAINER (priv->content_group), priv->text_actor); if (priv->text_actor == NULL && priv->image == NULL) { total_width = 6 * PADDING; total_height = 6 * PADDING; } point = (total_height + 2 * PADDING) / 4.0; priv->total_width = total_width; priv->total_height = total_height; draw_shadow (marker, total_width, total_height, point); draw_background (marker, total_width, total_height, point); if (priv->text_actor != NULL && priv->background != NULL) clutter_actor_raise (priv->text_actor, priv->background); if (priv->image != NULL && priv->background != NULL) clutter_actor_raise (priv->image, priv->background); clutter_actor_set_anchor_point (CLUTTER_ACTOR (marker), 0, total_height + point); } static gboolean redraw_on_idle (gpointer gobject) { EContactMarker *marker = E_CONTACT_MARKER (gobject); draw_marker (marker); marker->priv->redraw_id = 0; return FALSE; } static void queue_redraw (EContactMarker *marker) { EContactMarkerPrivate *priv = marker->priv; if (!priv->redraw_id) { priv->redraw_id = g_idle_add_full ( G_PRIORITY_DEFAULT, (GSourceFunc) redraw_on_idle, g_object_ref (marker), (GDestroyNotify) g_object_unref); } } static void allocate (ClutterActor *self, const ClutterActorBox *box, ClutterAllocationFlags flags) { ClutterActorBox child_box; EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv; CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->allocate (self, box, flags); child_box.x1 = 0; child_box.x2 = box->x2 - box->x1; child_box.y1 = 0; child_box.y2 = box->y2 - box->y1; clutter_actor_allocate (CLUTTER_ACTOR (priv->content_group), &child_box, flags); } static void paint (ClutterActor *self) { EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv; clutter_actor_paint (CLUTTER_ACTOR (priv->content_group)); } static void map (ClutterActor *self) { EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv; CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->map (self); clutter_actor_map (CLUTTER_ACTOR (priv->content_group)); } static void unmap (ClutterActor *self) { EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv; CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->unmap (self); clutter_actor_unmap (CLUTTER_ACTOR (priv->content_group)); } static void pick (ClutterActor *self, const ClutterColor *color) { EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv; gfloat width, height; if (!clutter_actor_should_pick_paint (self)) return; width = priv->total_width; height = priv->total_height; cogl_path_new (); cogl_set_source_color4ub ( color->red, color->green, color->blue, color->alpha); cogl_path_move_to (RADIUS, 0); cogl_path_line_to (width - RADIUS, 0); cogl_path_arc (width - RADIUS, RADIUS, RADIUS, RADIUS, -90, 0); cogl_path_line_to (width, height - RADIUS); cogl_path_arc (width - RADIUS, height - RADIUS, RADIUS, RADIUS, 0, 90); cogl_path_line_to (RADIUS, height); cogl_path_arc (RADIUS, height - RADIUS, RADIUS, RADIUS, 90, 180); cogl_path_line_to (0, RADIUS); cogl_path_arc (RADIUS, RADIUS, RADIUS, RADIUS, 180, 270); cogl_path_close (); cogl_path_fill (); } static void notify_selected (GObject *gobject, G_GNUC_UNUSED GParamSpec *pspec, G_GNUC_UNUSED gpointer user_data) { queue_redraw (E_CONTACT_MARKER (gobject)); } static void e_contact_marker_finalize (GObject *object) { EContactMarkerPrivate *priv = E_CONTACT_MARKER (object)->priv; if (priv->contact_uid) { g_free (priv->contact_uid); priv->contact_uid = NULL; } if (priv->redraw_id) { g_source_remove (priv->redraw_id); priv->redraw_id = 0; } G_OBJECT_CLASS (e_contact_marker_parent_class)->finalize (object); } static void e_contact_marker_dispose (GObject *object) { EContactMarkerPrivate *priv = E_CONTACT_MARKER (object)->priv; priv->background = NULL; priv->shadow = NULL; priv->text_actor = NULL; if (priv->content_group) { clutter_actor_unparent (CLUTTER_ACTOR (priv->content_group)); priv->content_group = NULL; } G_OBJECT_CLASS (e_contact_marker_parent_class)->dispose (object); } static void e_contact_marker_class_init (EContactMarkerClass *class) { ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class); GObjectClass *object_class = G_OBJECT_CLASS (class); g_type_class_add_private (class, sizeof (EContactMarkerPrivate)); object_class->dispose = e_contact_marker_dispose; object_class->finalize = e_contact_marker_finalize; actor_class->paint = paint; actor_class->allocate = allocate; actor_class->map = map; actor_class->unmap = unmap; actor_class->pick = pick; signals[DOUBLE_CLICKED] = g_signal_new ( "double-clicked", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EContactMarkerClass, double_clicked), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void e_contact_marker_init (EContactMarker *marker) { EContactMarkerPrivate *priv; priv = E_CONTACT_MARKER_GET_PRIVATE (marker); marker->priv = priv; priv->contact_uid = NULL; priv->image = NULL; priv->background = NULL; priv->shadow = NULL; priv->text_actor = NULL; priv->content_group = CLUTTER_GROUP (clutter_group_new ()); priv->redraw_id = 0; clutter_actor_set_parent ( CLUTTER_ACTOR (priv->content_group), CLUTTER_ACTOR (marker)); clutter_actor_queue_relayout (CLUTTER_ACTOR (marker)); priv->total_width = 0; priv->total_height = 0; g_signal_connect ( marker, "notify::selected", G_CALLBACK (notify_selected), NULL); g_signal_connect ( marker, "button-release-event", G_CALLBACK (contact_marker_clicked_cb), NULL); } ClutterActor * e_contact_marker_new (const gchar *name, const gchar *contact_uid, EContactPhoto *photo) { ClutterActor *marker = CLUTTER_ACTOR (g_object_new (E_TYPE_CONTACT_MARKER, NULL)); EContactMarkerPrivate *priv = E_CONTACT_MARKER (marker)->priv; g_return_val_if_fail (name && *name, NULL); g_return_val_if_fail (contact_uid && *contact_uid, NULL); champlain_label_set_text (CHAMPLAIN_LABEL (marker), name); priv->contact_uid = g_strdup (contact_uid); if (photo) priv->image = contact_photo_to_texture (photo); queue_redraw (E_CONTACT_MARKER (marker)); return marker; } const gchar * e_contact_marker_get_contact_uid (EContactMarker *marker) { g_return_val_if_fail (marker && E_IS_CONTACT_MARKER (marker), NULL); return marker->priv->contact_uid; } #endif /* WITH_CONTACT_MAPS */