1 /*
2  * Copyright (C) 2008-2009 Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 /**
20  * SECTION:gtk-champlain-embed
21  * @short_description: A Gtk+ Widget that embeds a #ChamplainView
22  *
23  * Since #ChamplainView is a #ClutterActor, you cannot embed it directly
24  * into a Gtk+ application.  This widget solves this problem.  It creates
25  * the #ChamplainView for you, you can get it with
26  * #gtk_champlain_embed_get_view.
27  */
28 #include "config.h"
29 
30 #include <champlain/champlain.h>
31 
32 #include <gtk/gtk.h>
33 #include <clutter/clutter.h>
34 #include <clutter-gtk/clutter-gtk.h>
35 
36 #include "gtk-champlain-embed.h"
37 
38 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION <= 12)
39 #define gtk_widget_get_window(widget) ((widget)->window)
40 #endif
41 
42 enum
43 {
44   /* normal signals */
45   LAST_SIGNAL
46 };
47 
48 enum
49 {
50   PROP_0,
51   PROP_VIEW
52 };
53 
54 /* static guint gtk_champlain_embed_embed_signals[LAST_SIGNAL] = { 0, }; */
55 
56 struct _GtkChamplainEmbedPrivate
57 {
58   GtkWidget *clutter_embed;
59   ChamplainView *view;
60 
61   GdkCursor *cursor_hand_open;
62   GdkCursor *cursor_hand_closed;
63 
64   guint width;
65   guint height;
66 };
67 
68 
69 static void gtk_champlain_embed_get_property (GObject *object,
70     guint prop_id,
71     GValue *value,
72     GParamSpec *pspec);
73 static void gtk_champlain_embed_set_property (GObject *object,
74     guint prop_id,
75     const GValue *value,
76     GParamSpec *pspec);
77 static void gtk_champlain_embed_finalize (GObject *object);
78 static void gtk_champlain_embed_dispose (GObject *object);
79 static void gtk_champlain_embed_class_init (GtkChamplainEmbedClass *klass);
80 static void gtk_champlain_embed_init (GtkChamplainEmbed *view);
81 static void view_size_allocated_cb (GtkWidget *widget,
82     GtkAllocation *allocation,
83     GtkChamplainEmbed *view);
84 static gboolean mouse_button_cb (GtkWidget *widget,
85     GdkEventButton *event,
86     GtkChamplainEmbed *view);
87 static void view_size_allocated_cb (GtkWidget *widget,
88     GtkAllocation *allocation,
89     GtkChamplainEmbed *view);
90 static void view_realize_cb (GtkWidget *widget,
91     GtkChamplainEmbed *view);
92 static gboolean embed_focus_cb (GtkChamplainEmbed *embed,
93     GdkEvent *event);
94 static gboolean stage_key_press_cb (ClutterActor *actor,
95     ClutterEvent *event,
96     GtkChamplainEmbed *embed);
97 
G_DEFINE_TYPE_WITH_PRIVATE(GtkChamplainEmbed,gtk_champlain_embed,GTK_TYPE_ALIGNMENT)98 G_DEFINE_TYPE_WITH_PRIVATE (GtkChamplainEmbed, gtk_champlain_embed, GTK_TYPE_ALIGNMENT)
99 
100 static void
101 gtk_champlain_embed_get_property (GObject *object,
102     guint prop_id,
103     GValue *value,
104     GParamSpec *pspec)
105 {
106   GtkChamplainEmbed *embed = GTK_CHAMPLAIN_EMBED (object);
107   GtkChamplainEmbedPrivate *priv = embed->priv;
108 
109   switch (prop_id)
110     {
111     case PROP_VIEW:
112       g_value_set_object (value, priv->view);
113       break;
114 
115     default:
116       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
117     }
118 }
119 
120 
121 static void
gtk_champlain_embed_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)122 gtk_champlain_embed_set_property (GObject *object,
123     guint prop_id,
124     const GValue *value,
125     GParamSpec *pspec)
126 {
127   switch (prop_id)
128     {
129     default:
130       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
131     }
132 }
133 
134 
135 static void
gtk_champlain_embed_dispose(GObject * object)136 gtk_champlain_embed_dispose (GObject *object)
137 {
138   GtkChamplainEmbed *embed = GTK_CHAMPLAIN_EMBED (object);
139   GtkChamplainEmbedPrivate *priv = embed->priv;
140 
141   if (priv->cursor_hand_open != NULL)
142     {
143 #if (GTK_MAJOR_VERSION == 2)
144       gdk_cursor_unref (priv->cursor_hand_open);
145 #else
146       g_object_unref (priv->cursor_hand_open);
147 #endif
148       priv->cursor_hand_open = NULL;
149     }
150 
151   if (priv->cursor_hand_closed != NULL)
152     {
153 #if (GTK_MAJOR_VERSION == 2)
154       gdk_cursor_unref (priv->cursor_hand_closed);
155 #else
156       g_object_unref (priv->cursor_hand_closed);
157 #endif
158       priv->cursor_hand_closed = NULL;
159     }
160 
161   G_OBJECT_CLASS (gtk_champlain_embed_parent_class)->dispose (object);
162 }
163 
164 
165 static void
gtk_champlain_embed_finalize(GObject * object)166 gtk_champlain_embed_finalize (GObject *object)
167 {
168   G_OBJECT_CLASS (gtk_champlain_embed_parent_class)->finalize (object);
169 }
170 
171 
172 static void
gtk_champlain_embed_class_init(GtkChamplainEmbedClass * klass)173 gtk_champlain_embed_class_init (GtkChamplainEmbedClass *klass)
174 {
175   GObjectClass *object_class = G_OBJECT_CLASS (klass);
176   object_class->finalize = gtk_champlain_embed_finalize;
177   object_class->dispose = gtk_champlain_embed_dispose;
178   object_class->get_property = gtk_champlain_embed_get_property;
179   object_class->set_property = gtk_champlain_embed_set_property;
180 
181   /**
182    * GtkChamplainEmbed:champlain-view:
183    *
184    * The #ChamplainView to embed in the Gtk+ widget.
185    *
186    * Since: 0.4
187    */
188   g_object_class_install_property (object_class,
189       PROP_VIEW,
190       g_param_spec_object ("champlain-view",
191           "Champlain view",
192           "The ChamplainView to embed into the Gtk+ widget",
193           CHAMPLAIN_TYPE_VIEW,
194           G_PARAM_READABLE));
195 }
196 
197 
198 static void
set_view(GtkChamplainEmbed * embed,ChamplainView * view)199 set_view (GtkChamplainEmbed *embed,
200     ChamplainView *view)
201 {
202   GtkChamplainEmbedPrivate *priv = embed->priv;
203   ClutterActor *stage;
204 
205   stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->clutter_embed));
206 
207   if (priv->view != NULL)
208     clutter_actor_remove_child (stage, CLUTTER_ACTOR (priv->view));
209 
210   priv->view = view;
211   clutter_actor_set_size (CLUTTER_ACTOR (priv->view), priv->width, priv->height);
212 
213   clutter_actor_add_child (stage, CLUTTER_ACTOR (priv->view));
214 }
215 
216 
217 static void
gtk_champlain_embed_init(GtkChamplainEmbed * embed)218 gtk_champlain_embed_init (GtkChamplainEmbed *embed)
219 {
220   GtkChamplainEmbedPrivate *priv = gtk_champlain_embed_get_instance_private (embed);
221   ClutterActor *stage;
222   GdkDisplay *display;
223 
224   embed->priv = priv;
225 
226   priv->clutter_embed = gtk_clutter_embed_new ();
227 
228   g_signal_connect (priv->clutter_embed,
229       "size-allocate",
230       G_CALLBACK (view_size_allocated_cb),
231       embed);
232   g_signal_connect (priv->clutter_embed,
233       "realize",
234       G_CALLBACK (view_realize_cb),
235       embed);
236   g_signal_connect (priv->clutter_embed,
237       "button-press-event",
238       G_CALLBACK (mouse_button_cb),
239       embed);
240   g_signal_connect (priv->clutter_embed,
241       "button-release-event",
242       G_CALLBACK (mouse_button_cb),
243       embed);
244   /* Setup cursors */
245   display = gdk_display_get_default ();
246   priv->cursor_hand_open = gdk_cursor_new_for_display (display, GDK_HAND1);
247   priv->cursor_hand_closed = gdk_cursor_new_for_display (display, GDK_FLEUR);
248 
249   priv->view = NULL;
250   set_view (embed, CHAMPLAIN_VIEW (champlain_view_new ()));
251 
252   /* Setup focus/key-press events */
253   g_signal_connect (embed, "focus-in-event",
254                     G_CALLBACK (embed_focus_cb),
255                     NULL);
256   stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->clutter_embed));
257   g_signal_connect (stage, "key-press-event",
258                     G_CALLBACK (stage_key_press_cb),
259                     embed);
260   gtk_widget_set_can_focus (GTK_WIDGET (embed), TRUE);
261 
262   gtk_container_add (GTK_CONTAINER (embed), priv->clutter_embed);
263 }
264 
265 
266 #if (GTK_MAJOR_VERSION == 2)
267 static void
gdk_to_clutter_color(GdkColor * gdk_color,ClutterColor * color)268 gdk_to_clutter_color (GdkColor *gdk_color,
269     ClutterColor *color)
270 {
271   color->red = CLAMP ((gdk_color->red / 65535.0) * 255, 0, 255);
272   color->green = CLAMP ((gdk_color->green / 65535.0) * 255, 0, 255);
273   color->blue = CLAMP ((gdk_color->blue / 65535.0) * 255, 0, 255);
274   color->alpha = 255;
275 }
276 #else
277 static void
gdk_rgba_to_clutter_color(GdkRGBA * gdk_rgba_color,ClutterColor * color)278 gdk_rgba_to_clutter_color (GdkRGBA *gdk_rgba_color,
279     ClutterColor *color)
280 {
281   color->red = CLAMP (gdk_rgba_color->red * 255, 0, 255);
282   color->green = CLAMP (gdk_rgba_color->green * 255, 0, 255);
283   color->blue = CLAMP (gdk_rgba_color->blue * 255, 0, 255);
284   color->alpha = CLAMP (gdk_rgba_color->alpha * 255, 0, 255);
285 }
286 
287 static void
get_background_color(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)288 get_background_color (GtkStyleContext *context,
289     GtkStateFlags state,
290     GdkRGBA *color)
291 {
292   GdkRGBA *c;
293 
294   gtk_style_context_get (context, state, "background-color", &c, NULL);
295   *color = *c;
296   gdk_rgba_free (c);
297 }
298 #endif
299 
300 
301 static void
view_realize_cb(GtkWidget * widget,GtkChamplainEmbed * view)302 view_realize_cb (GtkWidget *widget,
303     GtkChamplainEmbed *view)
304 {
305   ClutterColor color = { 0, 0, 0, };
306   GtkChamplainEmbedPrivate *priv = view->priv;
307 
308 #if (GTK_MAJOR_VERSION == 2)
309   GtkStyle *style;
310 
311   /* Set selection color */
312   style = gtk_widget_get_style (widget);
313 
314   gdk_to_clutter_color (&style->text[GTK_STATE_SELECTED], &color);
315   if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0)
316     {
317       color.red = 255;
318       color.green = 255;
319       color.blue = 255;
320     }
321   champlain_marker_set_selection_text_color (&color);
322 
323   gdk_to_clutter_color (&style->bg[GTK_STATE_SELECTED], &color);
324   if (color.alpha == 0)
325     color.alpha = 255;
326   if (color.red == 0 && color.green == 0 && color.blue == 0)
327     {
328       color.red = 75;
329       color.green = 105;
330       color.blue = 131;
331     }
332   champlain_marker_set_selection_color (&color);
333 #else
334   GtkStyleContext *style;
335   GdkRGBA gdk_rgba_color;
336 
337   /* Set selection color */
338   style = gtk_widget_get_style_context (widget);
339   gtk_style_context_save (style);
340   gtk_style_context_set_state (style, GTK_STATE_FLAG_SELECTED);
341 
342   gtk_style_context_get_color (style, gtk_style_context_get_state (style),
343                                &gdk_rgba_color);
344   gdk_rgba_to_clutter_color (&gdk_rgba_color, &color);
345   if (color.alpha == 0 && color.red == 0 && color.green == 0 && color.blue == 0)
346     {
347       color.red = 255;
348       color.green = 255;
349       color.blue = 255;
350     }
351   champlain_marker_set_selection_text_color (&color);
352 
353   get_background_color (style, gtk_style_context_get_state (style),
354                         &gdk_rgba_color);
355   gdk_rgba_to_clutter_color (&gdk_rgba_color, &color);
356   if (color.alpha == 0)
357     color.alpha = 255;
358   if (color.red == 0 && color.green == 0 && color.blue == 0)
359     {
360       color.red = 75;
361       color.green = 105;
362       color.blue = 131;
363     }
364   champlain_marker_set_selection_color (&color);
365 
366   gtk_style_context_restore (style);
367 #endif
368 
369   /* Setup mouse cursor to a hand */
370   gdk_window_set_cursor (gtk_widget_get_window (priv->clutter_embed), priv->cursor_hand_open);
371 }
372 
373 
374 static void
view_size_allocated_cb(GtkWidget * widget,GtkAllocation * allocation,GtkChamplainEmbed * view)375 view_size_allocated_cb (GtkWidget *widget,
376     GtkAllocation *allocation,
377     GtkChamplainEmbed *view)
378 {
379   GtkChamplainEmbedPrivate *priv = view->priv;
380 
381   if (priv->view != NULL)
382     clutter_actor_set_size (CLUTTER_ACTOR (priv->view), allocation->width, allocation->height);
383 
384   priv->width = allocation->width;
385   priv->height = allocation->height;
386 }
387 
388 
389 static gboolean
mouse_button_cb(GtkWidget * widget,GdkEventButton * event,GtkChamplainEmbed * view)390 mouse_button_cb (GtkWidget *widget,
391     GdkEventButton *event,
392     GtkChamplainEmbed *view)
393 {
394   GtkChamplainEmbedPrivate *priv = view->priv;
395 
396   if (event->type == GDK_BUTTON_PRESS)
397     gdk_window_set_cursor (gtk_widget_get_window (priv->clutter_embed),
398         priv->cursor_hand_closed);
399   else
400     gdk_window_set_cursor (gtk_widget_get_window (priv->clutter_embed),
401         priv->cursor_hand_open);
402 
403   return FALSE;
404 }
405 
406 static gboolean
embed_focus_cb(GtkChamplainEmbed * embed,GdkEvent * event)407 embed_focus_cb (GtkChamplainEmbed *embed,
408     GdkEvent *event)
409 {
410   GtkChamplainEmbedPrivate *priv = embed->priv;
411 
412   gtk_widget_grab_focus (priv->clutter_embed);
413   return TRUE;
414 }
415 
416 static gboolean
stage_key_press_cb(ClutterActor * actor,ClutterEvent * event,GtkChamplainEmbed * embed)417 stage_key_press_cb (ClutterActor *actor,
418     ClutterEvent *event,
419     GtkChamplainEmbed *embed)
420 {
421   ChamplainView *view = gtk_champlain_embed_get_view (embed);
422 
423   clutter_actor_event (CLUTTER_ACTOR (view), event, FALSE);
424   return TRUE;
425 }
426 
427 /**
428  * gtk_champlain_embed_new:
429  *
430  * Creates an instance of #GtkChamplainEmbed.
431  *
432  * Return value: a new #GtkChamplainEmbed ready to be used as a #GtkWidget.
433  *
434  * Since: 0.4
435  */
436 GtkWidget *
gtk_champlain_embed_new()437 gtk_champlain_embed_new ()
438 {
439   return g_object_new (GTK_CHAMPLAIN_TYPE_EMBED, NULL);
440 }
441 
442 
443 /**
444  * gtk_champlain_embed_get_view:
445  * @embed: a #ChamplainView, the map view to embed
446  *
447  * Gets a #ChamplainView from the #GtkChamplainEmbed object.
448  *
449  * Return value: (transfer none): a #ChamplainView ready to be used
450  *
451  * Since: 0.4
452  */
453 ChamplainView *
gtk_champlain_embed_get_view(GtkChamplainEmbed * embed)454 gtk_champlain_embed_get_view (GtkChamplainEmbed *embed)
455 {
456   g_return_val_if_fail (GTK_CHAMPLAIN_IS_EMBED (embed), NULL);
457 
458   GtkChamplainEmbedPrivate *priv = embed->priv;
459   return priv->view;
460 }
461