/*
* 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
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;
int 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 = G_TYPE_INSTANCE_GET_PRIVATE (
marker, E_TYPE_CONTACT_MARKER, EContactMarkerPrivate);
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 (CLUTTER_ACTOR (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 */