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