]> Pileus Git - ~andy/gtk/blob - gtk/gtklinkbutton.c
Fix some compiler warnings. (#433642, Kjartan Maraas)
[~andy/gtk] / gtk / gtklinkbutton.c
1 /* GTK - The GIMP Toolkit
2  * gtklinkbutton.c - an hyperlink-enabled button
3  * 
4  * Copyright (C) 2006 Emmanuele Bassi <ebassi@gmail.com>
5  * All rights reserved.
6  *
7  * Based on gnome-href code by:
8  *      James Henstridge <james@daa.com.au>
9  * 
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Cambridge, MA 02139, USA.
23  */
24
25 #include "config.h"
26
27 #include <string.h>
28
29 #include <gdk/gdkcolor.h>
30 #include <gdk/gdkcursor.h>
31 #include <gdk/gdkdisplay.h>
32
33 #include "gtkclipboard.h"
34 #include "gtkdnd.h"
35 #include "gtkimagemenuitem.h"
36 #include "gtklabel.h"
37 #include "gtkmain.h"
38 #include "gtkmenu.h"
39 #include "gtkmenuitem.h"
40 #include "gtkstock.h"
41
42 #include "gtklinkbutton.h"
43
44 #include "gtkintl.h"
45 #include "gtkalias.h"
46
47
48 struct _GtkLinkButtonPrivate
49 {
50   gchar *uri;
51
52   gboolean visited;
53
54   GtkWidget *popup_menu;
55 };
56
57 enum
58 {
59   PROP_0,
60   
61   PROP_URI,
62 };
63
64 #define GTK_LINK_BUTTON_GET_PRIVATE(obj)        (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_LINK_BUTTON, GtkLinkButtonPrivate))
65
66 static void     gtk_link_button_finalize     (GObject          *object);
67 static void     gtk_link_button_get_property (GObject          *object,
68                                               guint             prop_id,
69                                               GValue           *value,
70                                               GParamSpec       *pspec);
71 static void     gtk_link_button_set_property (GObject          *object,
72                                               guint             prop_id,
73                                               const GValue     *value,
74                                               GParamSpec       *pspec);
75 static void     gtk_link_button_add          (GtkContainer     *container,
76                                               GtkWidget        *widget);
77 static gboolean gtk_link_button_button_press (GtkWidget        *widget,
78                                               GdkEventButton   *event);
79 static void     gtk_link_button_clicked      (GtkButton        *button);
80 static gboolean gtk_link_button_popup_menu   (GtkWidget        *widget);
81 static void     gtk_link_button_style_set    (GtkWidget        *widget,
82                                               GtkStyle         *old_style);
83 static gboolean gtk_link_button_enter_cb     (GtkWidget        *widget,
84                                               GdkEventCrossing *event,
85                                               gpointer          user_data);
86 static gboolean gtk_link_button_leave_cb     (GtkWidget        *widget,
87                                               GdkEventCrossing *event,
88                                               gpointer          user_data);
89 static void gtk_link_button_drag_data_get_cb (GtkWidget        *widget,
90                                               GdkDragContext   *context,
91                                               GtkSelectionData *selection,
92                                               guint             _info,
93                                               guint             _time,
94                                               gpointer          user_data);
95
96
97 static const GtkTargetEntry link_drop_types[] = {
98   { "text/uri-list", 0, 0 },
99   { "_NETSCAPE_URL", 0, 0 }
100 };
101
102 static const GdkColor default_link_color = { 0, 0, 0, 0xeeee };
103 static const GdkColor default_visited_link_color = { 0, 0x5555, 0x1a1a, 0x8b8b };
104
105 static GtkLinkButtonUriFunc uri_func = NULL;
106 static gpointer uri_func_data = NULL;
107 static GDestroyNotify uri_func_destroy = NULL;
108
109 G_DEFINE_TYPE (GtkLinkButton, gtk_link_button, GTK_TYPE_BUTTON)
110
111 static void
112 gtk_link_button_class_init (GtkLinkButtonClass *klass)
113 {
114   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
115   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
116   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
117   GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
118   
119   gobject_class->set_property = gtk_link_button_set_property;
120   gobject_class->get_property = gtk_link_button_get_property;
121   gobject_class->finalize = gtk_link_button_finalize;
122   
123   widget_class->button_press_event = gtk_link_button_button_press;
124   widget_class->popup_menu = gtk_link_button_popup_menu;
125   widget_class->style_set = gtk_link_button_style_set;
126   
127   container_class->add = gtk_link_button_add;
128
129   button_class->clicked = gtk_link_button_clicked;
130
131   /**
132    * GtkLinkButton:uri
133    * 
134    * The URI bound to this button.
135    *
136    * Since: 2.10
137    */
138   g_object_class_install_property (gobject_class,
139                                    PROP_URI,
140                                    g_param_spec_string ("uri",
141                                                         _("URI"),
142                                                         _("The URI bound to this button"),
143                                                         "http://www.gtk.org",
144                                                         G_PARAM_READWRITE));
145   
146   g_type_class_add_private (gobject_class, sizeof (GtkLinkButtonPrivate));
147 }
148
149 static void
150 gtk_link_button_init (GtkLinkButton *link_button)
151 {
152   link_button->priv = GTK_LINK_BUTTON_GET_PRIVATE (link_button),
153   
154   gtk_button_set_relief (GTK_BUTTON (link_button), GTK_RELIEF_NONE);
155   
156   g_signal_connect (link_button, "enter_notify_event",
157                     G_CALLBACK (gtk_link_button_enter_cb), NULL);
158   g_signal_connect (link_button, "leave_notify_event",
159                     G_CALLBACK (gtk_link_button_leave_cb), NULL);
160   g_signal_connect (link_button, "drag_data_get",
161                     G_CALLBACK (gtk_link_button_drag_data_get_cb), NULL);
162   
163   /* enable drag source */
164   gtk_drag_source_set (GTK_WIDGET (link_button),
165                        GDK_BUTTON1_MASK,
166                        link_drop_types, G_N_ELEMENTS (link_drop_types),
167                        GDK_ACTION_COPY);
168 }
169
170 static void
171 gtk_link_button_finalize (GObject *object)
172 {
173   GtkLinkButton *link_button = GTK_LINK_BUTTON (object);
174   
175   g_free (link_button->priv->uri);
176   
177   G_OBJECT_CLASS (gtk_link_button_parent_class)->finalize (object);
178 }
179
180 static void
181 gtk_link_button_get_property (GObject    *object,
182                               guint       prop_id,
183                               GValue     *value,
184                               GParamSpec *pspec)
185 {
186   GtkLinkButton *link_button = GTK_LINK_BUTTON (object);
187   
188   switch (prop_id)
189     {
190     case PROP_URI:
191       g_value_set_string (value, link_button->priv->uri);
192       break;
193     default:
194       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
195       break;
196     }
197 }
198
199 static void
200 gtk_link_button_set_property (GObject      *object,
201                               guint         prop_id,
202                               const GValue *value,
203                               GParamSpec   *pspec)
204 {
205   GtkLinkButton *link_button = GTK_LINK_BUTTON (object);
206   
207   switch (prop_id)
208     {
209     case PROP_URI:
210       gtk_link_button_set_uri (link_button, g_value_get_string (value));
211       break;
212     default:
213       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214       break;
215     }
216 }
217
218 static void
219 set_link_color (GtkLinkButton *link_button)
220 {
221   GdkColor *link_color = NULL;
222   GtkWidget *label;
223
224   label = gtk_bin_get_child (GTK_BIN (link_button));
225
226   if (link_button->priv->visited)
227     {
228       gtk_widget_style_get (GTK_WIDGET (link_button), 
229                             "visited-link-color", &link_color, NULL);
230       if (!link_color)
231         link_color = &default_visited_link_color;
232     }
233   else
234     {
235       gtk_widget_style_get (GTK_WIDGET (link_button), 
236                             "link-color", &link_color, NULL);
237       if (!link_color)
238         link_color = &default_link_color;
239     }
240   
241   gtk_widget_modify_fg (label, GTK_STATE_NORMAL, link_color);
242   gtk_widget_modify_fg (label, GTK_STATE_ACTIVE, link_color);
243   gtk_widget_modify_fg (label, GTK_STATE_PRELIGHT, link_color);
244   gtk_widget_modify_fg (label, GTK_STATE_SELECTED, link_color);
245
246   if (link_color != &default_link_color &&
247       link_color != &default_visited_link_color)
248     gdk_color_free (link_color);
249 }
250
251 static void
252 set_link_underline (GtkLinkButton *link_button)
253 {
254   GtkWidget *label;
255   
256   label = gtk_bin_get_child (GTK_BIN (link_button));
257   if (GTK_IS_LABEL (label))
258     {
259       PangoAttrList *attributes;
260       PangoAttribute *uline;
261
262       uline = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
263       uline->start_index = 0;
264       uline->end_index = G_MAXUINT;
265       attributes = pango_attr_list_new ();
266       pango_attr_list_insert (attributes, uline); 
267       gtk_label_set_attributes (GTK_LABEL (label), attributes);
268       pango_attr_list_unref (attributes);
269     }
270 }
271
272 static void
273 gtk_link_button_add (GtkContainer *container,
274                      GtkWidget    *widget)
275 {
276   GTK_CONTAINER_CLASS (gtk_link_button_parent_class)->add (container, widget);
277
278   set_link_underline (GTK_LINK_BUTTON (container));
279 }
280
281 static void
282 gtk_link_button_style_set (GtkWidget *widget,
283                            GtkStyle  *old_style)
284 {
285   GtkLinkButton *link_button = GTK_LINK_BUTTON (widget);
286
287   set_link_color (link_button);
288 }
289
290 static void
291 set_hand_cursor (GtkWidget *widget,
292                  gboolean   show_hand)
293 {
294   GdkDisplay *display;
295   GdkCursor *cursor;
296
297   display = gtk_widget_get_display (widget);
298
299   cursor = NULL;
300   if (show_hand)
301     cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
302
303   gdk_window_set_cursor (widget->window, cursor);
304   gdk_display_flush (display);
305
306   if (cursor)
307     gdk_cursor_unref (cursor);
308 }
309
310 static void
311 popup_menu_detach (GtkWidget *attach_widget,
312                    GtkMenu   *menu)
313 {
314   GtkLinkButton *link_button = GTK_LINK_BUTTON (attach_widget);
315
316   link_button->priv->popup_menu = NULL;
317 }
318
319 static void
320 popup_position_func (GtkMenu  *menu,
321                      gint     *x,
322                      gint     *y,
323                      gboolean *push_in,
324                      gpointer  user_data)
325 {
326   GtkLinkButton *link_button = GTK_LINK_BUTTON (user_data);
327   GtkLinkButtonPrivate *priv = link_button->priv;
328   GtkWidget *widget = GTK_WIDGET (link_button);
329   GdkScreen *screen = gtk_widget_get_screen (widget);
330   GtkRequisition req;
331   gint monitor_num;
332   GdkRectangle monitor;
333   
334   g_return_if_fail (GTK_WIDGET_REALIZED (link_button));
335
336   gdk_window_get_origin (widget->window, x, y);
337
338   gtk_widget_size_request (priv->popup_menu, &req);
339
340   *x += widget->allocation.width / 2;
341   *y += widget->allocation.height;
342
343   monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
344   gtk_menu_set_monitor (menu, monitor_num);
345   gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
346
347   *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width));
348   *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height));
349
350   *push_in = FALSE;
351 }
352
353 static void
354 copy_activate_cb (GtkWidget     *widget,
355                   GtkLinkButton *link_button)
356 {
357   GtkLinkButtonPrivate *priv = link_button->priv;
358   
359   gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (link_button),
360                                                     GDK_SELECTION_CLIPBOARD),
361                           priv->uri, -1);
362 }
363
364 static void
365 gtk_link_button_do_popup (GtkLinkButton  *link_button,
366                           GdkEventButton *event)
367 {
368   GtkLinkButtonPrivate *priv = link_button->priv;
369   gint button;
370   guint time;
371   
372   if (event)
373     {
374       button = event->button;
375       time = event->time;
376     }
377   else
378     {
379       button = 0;
380       time = gtk_get_current_event_time ();
381     }
382
383   if (GTK_WIDGET_REALIZED (link_button))
384     {
385       GtkWidget *menu_item;
386       
387       if (priv->popup_menu)
388         gtk_widget_destroy (priv->popup_menu);
389
390       priv->popup_menu = gtk_menu_new ();
391       
392       gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
393                                  GTK_WIDGET (link_button),
394                                  popup_menu_detach);
395
396       menu_item = gtk_image_menu_item_new_with_mnemonic (_("Copy URL"));
397       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
398                                      gtk_image_new_from_stock (GTK_STOCK_COPY,
399                                                                GTK_ICON_SIZE_MENU));
400       g_signal_connect (menu_item, "activate",
401                         G_CALLBACK (copy_activate_cb), link_button);
402       gtk_widget_show (menu_item);
403       gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), menu_item);
404       
405       if (button)
406         gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
407                         NULL, NULL,
408                         button, time);
409       else
410         {
411           gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
412                           popup_position_func, link_button,
413                           button, time);
414           gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->popup_menu), FALSE);
415         }
416     }
417 }
418
419 static gboolean
420 gtk_link_button_button_press (GtkWidget      *widget,
421                               GdkEventButton *event)
422 {
423   if (!GTK_WIDGET_HAS_FOCUS (widget))
424     gtk_widget_grab_focus (widget);
425
426   if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS))
427     {
428       gtk_link_button_do_popup (GTK_LINK_BUTTON (widget), event);
429       
430       return TRUE;
431     }
432
433   if (GTK_WIDGET_CLASS (gtk_link_button_parent_class)->button_press_event)
434     return (* GTK_WIDGET_CLASS (gtk_link_button_parent_class)->button_press_event) (widget, event);
435   
436   return FALSE;
437 }
438
439 static void
440 gtk_link_button_clicked (GtkButton *button)
441 {
442   GtkLinkButton *link_button = GTK_LINK_BUTTON (button);
443
444   if (uri_func)
445     (* uri_func) (link_button, link_button->priv->uri, uri_func_data);
446
447   link_button->priv->visited = TRUE;
448
449   set_link_color (link_button);
450 }
451
452 static gboolean
453 gtk_link_button_popup_menu (GtkWidget *widget)
454 {
455   gtk_link_button_do_popup (GTK_LINK_BUTTON (widget), NULL);
456
457   return TRUE; 
458 }
459
460 static gboolean
461 gtk_link_button_enter_cb (GtkWidget        *widget,
462                           GdkEventCrossing *crossing,
463                           gpointer          user_data)
464 {
465   set_hand_cursor (widget, TRUE);
466   
467   return FALSE;
468 }
469
470 static gboolean
471 gtk_link_button_leave_cb (GtkWidget        *widget,
472                           GdkEventCrossing *crossing,
473                           gpointer          user_data)
474 {
475   set_hand_cursor (widget, FALSE);
476   
477   return FALSE;
478 }
479
480 static void
481 gtk_link_button_drag_data_get_cb (GtkWidget        *widget,
482                                   GdkDragContext   *context,
483                                   GtkSelectionData *selection,
484                                   guint             _info,
485                                   guint             _time,
486                                   gpointer          user_data)
487 {
488   GtkLinkButton *link_button = GTK_LINK_BUTTON (widget);
489   gchar *uri;
490   
491   uri = g_strdup_printf ("%s\r\n", link_button->priv->uri);
492   gtk_selection_data_set (selection,
493                           selection->target,
494                           8,
495                           (guchar *) uri,
496                           strlen (uri));
497   
498   g_free (uri);
499 }
500
501 /**
502  * gtk_link_button_new:
503  * @uri: a valid URI
504  *
505  * Creates a new #GtkLinkButton with the URI as its text.
506  *
507  * Return value: a new link button widget.
508  *
509  * Since: 2.10
510  */
511 GtkWidget *
512 gtk_link_button_new (const gchar *uri)
513 {
514   gchar *utf8_uri = NULL;
515   GtkWidget *retval;
516   
517   g_return_val_if_fail (uri != NULL, NULL);
518   
519   if (g_utf8_validate (uri, -1, NULL))
520     {
521       utf8_uri = g_strdup (uri);
522     }
523   else
524     {
525       GError *conv_err = NULL;
526     
527       utf8_uri = g_locale_to_utf8 (uri, -1, NULL, NULL, &conv_err);
528       if (conv_err)
529         {
530           g_warning ("Attempting to convert URI `%s' to UTF-8, but failed "
531                      "with error: %s\n",
532                      uri,
533                      conv_err->message);
534           g_error_free (conv_err);
535         
536           utf8_uri = g_strdup (_("Invalid URI"));
537         }
538     }
539   
540   retval = g_object_new (GTK_TYPE_LINK_BUTTON,
541                          "uri", uri,
542                          "label", utf8_uri,
543                          NULL);
544   
545   g_free (utf8_uri);
546   
547   return retval;
548 }
549
550 /**
551  * gtk_link_button_new_with_label:
552  * @uri: a valid URI
553  * @label: the text of the button
554  *
555  * Creates a new #GtkLinkButton containing a label.
556  *
557  * Return value: a new link button widget.
558  *
559  * Since: 2.10
560  */
561 GtkWidget *
562 gtk_link_button_new_with_label (const gchar *uri,
563                                 const gchar *label)
564 {
565   GtkWidget *retval;
566   
567   g_return_val_if_fail (uri != NULL, NULL);
568   
569   if (!label)
570     return gtk_link_button_new (uri);
571
572   retval = g_object_new (GTK_TYPE_LINK_BUTTON,
573                          "label", label,
574                          "uri", uri,
575                          NULL);
576
577   return retval;
578 }
579
580 /**
581  * gtk_link_button_set_uri:
582  * @link_button: a #GtkLinkButton
583  * @uri: a valid URI
584  *
585  * Sets @uri as the URI where the #GtkLinkButton points.
586  *
587  * Since: 2.10
588  */
589 void
590 gtk_link_button_set_uri (GtkLinkButton *link_button,
591                          const gchar   *uri)
592 {
593   gchar *tmp;
594
595   g_return_if_fail (GTK_IS_LINK_BUTTON (link_button));
596   g_return_if_fail (uri != NULL);
597   
598   tmp = link_button->priv->uri;
599   link_button->priv->uri = g_strdup (uri);
600   g_free (tmp);
601
602   link_button->priv->visited = FALSE;
603   
604   g_object_notify (G_OBJECT (link_button), "uri");
605 }
606
607 /**
608  * gtk_link_button_get_uri:
609  * @link_button: a #GtkLinkButton
610  *
611  * Retrieves the URI set using gtk_link_button_set_uri().
612  *
613  * Return value: a valid URI.  The returned string is owned by the link button
614  *   and should not be modified or freed.
615  *
616  * Since: 2.10
617  */
618 G_CONST_RETURN gchar *
619 gtk_link_button_get_uri (GtkLinkButton *link_button)
620 {
621   g_return_val_if_fail (GTK_IS_LINK_BUTTON (link_button), NULL);
622   
623   return link_button->priv->uri;
624 }
625
626 /**
627  * gtk_link_button_set_uri_hook:
628  * @func: a function called each time a #GtkLinkButton is clicked, or %NULL
629  * @data: user data to be passed to @func, or %NULL
630  * @destroy: a #GDestroyNotify that gets called when @data is no longer needed, or %NULL
631  *
632  * Sets @func as the function that should be invoked every time a user clicks
633  * a #GtkLinkButton. This function is called before every callback registered
634  * for the "clicked" signal.
635  *
636  * Return value: the previously set hook function.
637  *
638  * Since: 2.10
639  */
640 GtkLinkButtonUriFunc
641 gtk_link_button_set_uri_hook (GtkLinkButtonUriFunc func,
642                               gpointer             data,
643                               GDestroyNotify       destroy)
644 {
645   GtkLinkButtonUriFunc old_uri_func;
646
647   if (uri_func_destroy)
648     (* uri_func_destroy) (uri_func_data);
649
650   old_uri_func = uri_func;
651
652   uri_func = func;
653   uri_func_data = data;
654   uri_func_destroy = destroy;
655
656   return old_uri_func;
657 }
658
659 #define __GTK_LINK_BUTTON_C__
660 #include "gtkaliasdef.c"