]> Pileus Git - ~andy/gtk/blob - gtk/gtklinkbutton.c
More of the same
[~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     }
269 }
270
271 static void
272 gtk_link_button_add (GtkContainer *container,
273                      GtkWidget    *widget)
274 {
275   GTK_CONTAINER_CLASS (gtk_link_button_parent_class)->add (container, widget);
276
277   set_link_underline (GTK_LINK_BUTTON (container));
278 }
279
280 static void
281 gtk_link_button_style_set (GtkWidget *widget,
282                            GtkStyle  *old_style)
283 {
284   GtkLinkButton *link_button = GTK_LINK_BUTTON (widget);
285
286   set_link_color (link_button);
287 }
288
289 static void
290 set_hand_cursor (GtkWidget *widget,
291                  gboolean   show_hand)
292 {
293   GdkDisplay *display;
294   GdkCursor *cursor;
295
296   display = gtk_widget_get_display (widget);
297
298   cursor = NULL;
299   if (show_hand)
300     cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
301
302   gdk_window_set_cursor (widget->window, cursor);
303   gdk_display_flush (display);
304
305   if (cursor)
306     gdk_cursor_unref (cursor);
307 }
308
309 static void
310 popup_menu_detach (GtkWidget *attach_widget,
311                    GtkMenu   *menu)
312 {
313   GtkLinkButton *link_button = GTK_LINK_BUTTON (attach_widget);
314
315   link_button->priv->popup_menu = NULL;
316 }
317
318 static void
319 popup_position_func (GtkMenu  *menu,
320                      gint     *x,
321                      gint     *y,
322                      gboolean *push_in,
323                      gpointer  user_data)
324 {
325   GtkLinkButton *link_button = GTK_LINK_BUTTON (user_data);
326   GtkLinkButtonPrivate *priv = link_button->priv;
327   GtkWidget *widget = GTK_WIDGET (link_button);
328   GdkScreen *screen = gtk_widget_get_screen (widget);
329   GtkRequisition req;
330   gint monitor_num;
331   GdkRectangle monitor;
332   
333   g_return_if_fail (GTK_WIDGET_REALIZED (link_button));
334
335   gdk_window_get_origin (widget->window, x, y);
336
337   gtk_widget_size_request (priv->popup_menu, &req);
338
339   *x += widget->allocation.width / 2;
340   *y += widget->allocation.height;
341
342   monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
343   gtk_menu_set_monitor (menu, monitor_num);
344   gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
345
346   *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width));
347   *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height));
348
349   *push_in = FALSE;
350 }
351
352 static void
353 copy_activate_cb (GtkWidget     *widget,
354                   GtkLinkButton *link_button)
355 {
356   GtkLinkButtonPrivate *priv = link_button->priv;
357   
358   gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (link_button),
359                                                     GDK_SELECTION_CLIPBOARD),
360                           priv->uri, -1);
361 }
362
363 static void
364 gtk_link_button_do_popup (GtkLinkButton  *link_button,
365                           GdkEventButton *event)
366 {
367   GtkLinkButtonPrivate *priv = link_button->priv;
368   gint button;
369   guint time;
370   
371   if (event)
372     {
373       button = event->button;
374       time = event->time;
375     }
376   else
377     {
378       button = 0;
379       time = gtk_get_current_event_time ();
380     }
381
382   if (GTK_WIDGET_REALIZED (link_button))
383     {
384       GtkWidget *menu_item;
385       
386       if (priv->popup_menu)
387         gtk_widget_destroy (priv->popup_menu);
388
389       priv->popup_menu = gtk_menu_new ();
390       
391       gtk_menu_attach_to_widget (GTK_MENU (priv->popup_menu),
392                                  GTK_WIDGET (link_button),
393                                  popup_menu_detach);
394
395       menu_item = gtk_image_menu_item_new_with_mnemonic (_("Copy URL"));
396       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item),
397                                      gtk_image_new_from_stock (GTK_STOCK_COPY,
398                                                                GTK_ICON_SIZE_MENU));
399       g_signal_connect (menu_item, "activate",
400                         G_CALLBACK (copy_activate_cb), link_button);
401       gtk_widget_show (menu_item);
402       gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), menu_item);
403       
404       if (button)
405         gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
406                         NULL, NULL,
407                         button, time);
408       else
409         {
410           gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
411                           popup_position_func, link_button,
412                           button, time);
413           gtk_menu_shell_select_first (GTK_MENU_SHELL (priv->popup_menu), FALSE);
414         }
415     }
416 }
417
418 static gboolean
419 gtk_link_button_button_press (GtkWidget      *widget,
420                               GdkEventButton *event)
421 {
422   if (!GTK_WIDGET_HAS_FOCUS (widget))
423     gtk_widget_grab_focus (widget);
424
425   if ((event->button == 3) && (event->type == GDK_BUTTON_PRESS))
426     {
427       gtk_link_button_do_popup (GTK_LINK_BUTTON (widget), event);
428       
429       return TRUE;
430     }
431
432   if (GTK_WIDGET_CLASS (gtk_link_button_parent_class)->button_press_event)
433     return (* GTK_WIDGET_CLASS (gtk_link_button_parent_class)->button_press_event) (widget, event);
434   
435   return FALSE;
436 }
437
438 static void
439 gtk_link_button_clicked (GtkButton *button)
440 {
441   GtkLinkButton *link_button = GTK_LINK_BUTTON (button);
442
443   if (uri_func)
444     (* uri_func) (button, link_button->priv->uri, uri_func_data);
445
446   link_button->priv->visited = TRUE;
447
448   set_link_color (link_button);
449 }
450
451 static gboolean
452 gtk_link_button_popup_menu (GtkWidget *widget)
453 {
454   gtk_link_button_do_popup (GTK_LINK_BUTTON (widget), NULL);
455
456   return TRUE; 
457 }
458
459 static gboolean
460 gtk_link_button_enter_cb (GtkWidget        *widget,
461                           GdkEventCrossing *crossing,
462                           gpointer          user_data)
463 {
464   set_hand_cursor (widget, TRUE);
465   
466   return FALSE;
467 }
468
469 static gboolean
470 gtk_link_button_leave_cb (GtkWidget        *widget,
471                           GdkEventCrossing *crossing,
472                           gpointer          user_data)
473 {
474   set_hand_cursor (widget, FALSE);
475   
476   return FALSE;
477 }
478
479 static void
480 gtk_link_button_drag_data_get_cb (GtkWidget        *widget,
481                                   GdkDragContext   *context,
482                                   GtkSelectionData *selection,
483                                   guint             _info,
484                                   guint             _time,
485                                   gpointer          user_data)
486 {
487   GtkLinkButton *link_button = GTK_LINK_BUTTON (widget);
488   gchar *uri;
489   
490   uri = g_strdup_printf ("%s\r\n", link_button->priv->uri);
491   gtk_selection_data_set (selection,
492                           selection->target,
493                           8,
494                           (guchar *) uri,
495                           strlen (uri));
496   
497   g_free (uri);
498 }
499
500 /**
501  * gtk_link_button_new:
502  * @uri: a valid URI
503  *
504  * Creates a new #GtkLinkButton with the URI as its text.
505  *
506  * Return value: a new link button widget.
507  *
508  * Since: 2.10
509  */
510 GtkWidget *
511 gtk_link_button_new (const gchar *uri)
512 {
513   gchar *utf8_uri = NULL;
514   GtkWidget *retval;
515   
516   g_return_val_if_fail (uri != NULL, NULL);
517   
518   if (g_utf8_validate (uri, -1, NULL))
519     {
520       utf8_uri = g_strdup (uri);
521     }
522   else
523     {
524       GError *conv_err = NULL;
525     
526       utf8_uri = g_locale_to_utf8 (uri, -1, NULL, NULL, &conv_err);
527       if (conv_err)
528         {
529           g_warning ("Attempting to convert URI `%s' to UTF-8, but failed "
530                      "with error: %s\n",
531                      uri,
532                      conv_err->message);
533           g_error_free (conv_err);
534         
535           utf8_uri = g_strdup (_("Invalid URI"));
536         }
537     }
538   
539   retval = g_object_new (GTK_TYPE_LINK_BUTTON,
540                          "uri", uri,
541                          "label", utf8_uri,
542                          NULL);
543   
544   g_free (utf8_uri);
545   
546   return retval;
547 }
548
549 /**
550  * gtk_link_button_new_with_label:
551  * @uri: a valid URI
552  * @label: the text of the button
553  *
554  * Creates a new #GtkLinkButton containing a label.
555  *
556  * Return value: a new link button widget.
557  *
558  * Since: 2.10
559  */
560 GtkWidget *
561 gtk_link_button_new_with_label (const gchar *uri,
562                                 const gchar *label)
563 {
564   GtkWidget *retval;
565   
566   g_return_val_if_fail (uri != NULL, NULL);
567   
568   if (!label)
569     return gtk_link_button_new (uri);
570
571   retval = g_object_new (GTK_TYPE_LINK_BUTTON,
572                          "label", label,
573                          "uri", uri,
574                          NULL);
575
576   return retval;
577 }
578
579 /**
580  * gtk_link_button_set_uri:
581  * @link_button: a #GtkLinkButton
582  * @uri: a valid URI
583  *
584  * Sets @uri as the URI where the #GtkLinkButton points.
585  *
586  * Since: 2.10
587  */
588 void
589 gtk_link_button_set_uri (GtkLinkButton *link_button,
590                          const gchar   *uri)
591 {
592   gchar *tmp;
593
594   g_return_if_fail (GTK_IS_LINK_BUTTON (link_button));
595   g_return_if_fail (uri != NULL);
596   
597   tmp = link_button->priv->uri;
598   link_button->priv->uri = g_strdup (uri);
599   g_free (tmp);
600
601   link_button->priv->visited = FALSE;
602   
603   g_object_notify (G_OBJECT (link_button), "uri");
604 }
605
606 /**
607  * gtk_link_button_get_uri:
608  * @link_button: a #GtkLinkButton
609  *
610  * Retrieves the URI set using gtk_link_button_set_uri().
611  *
612  * Return value: a valid URI.  The returned string is owned by the link button
613  *   and should not be modified or freed.
614  *
615  * Since: 2.10
616  */
617 G_CONST_RETURN gchar *
618 gtk_link_button_get_uri (GtkLinkButton *link_button)
619 {
620   g_return_val_if_fail (GTK_IS_LINK_BUTTON (link_button), NULL);
621   
622   return link_button->priv->uri;
623 }
624
625 /**
626  * gtk_link_button_set_uri_hook:
627  * @func: a function called each time a #GtkLinkButton is clicked, or %NULL
628  * @data: user data to be passed to @func, or %NULL
629  * @destroy: a #GDestroyNotify that gets called when @data is no longer needed, or %NULL
630  *
631  * Sets @func as the function that should be invoked every time a user clicks
632  * a #GtkLinkButton. This function is called before every callback registered
633  * for the "clicked" signal.
634  *
635  * Return value: the previously set hook function.
636  *
637  * Since: 2.10
638  */
639 GtkLinkButtonUriFunc
640 gtk_link_button_set_uri_hook (GtkLinkButtonUriFunc func,
641                               gpointer             data,
642                               GDestroyNotify       destroy)
643 {
644   GtkLinkButtonUriFunc old_uri_func;
645
646   if (uri_func_destroy)
647     (* uri_func_destroy) (uri_func_data);
648
649   old_uri_func = uri_func;
650
651   uri_func = func;
652   uri_func_data = data;
653   uri_func_destroy = destroy;
654
655   return old_uri_func;
656 }
657
658 #define __GTK_LINK_BUTTON_C__
659 #include "gtkaliasdef.c"