libyui-gtk  2.44.9
ygtklinklabel.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /* YGtkLinkLabel container */
6 // check the header file for information about this container
7 
8 #include <yui/Libyui_config.h>
9 #include <math.h>
10 #include <gtk/gtk.h>
11 #include "ygtklinklabel.h"
12 
13 static guint link_clicked_signal;
14 
15 G_DEFINE_TYPE (YGtkLinkLabel, ygtk_link_label, GTK_TYPE_WIDGET)
16 
17 static void ygtk_link_label_init (YGtkLinkLabel *label)
18 {
19  gtk_widget_set_has_window(GTK_WIDGET(label), FALSE);
20 }
21 
22 static void ygtk_link_label_realize (GtkWidget *widget)
23 {
24  GTK_WIDGET_CLASS (ygtk_link_label_parent_class)->realize (widget);
25  GdkWindowAttr attributes;
26  GtkAllocation alloc;
27  gtk_widget_get_allocation(widget, &alloc);
28  attributes.x = alloc.x;
29  attributes.y = alloc.y;
30  attributes.width = alloc.width;
31  attributes.height = alloc.height;
32  attributes.window_type = GDK_WINDOW_CHILD;
33  attributes.wclass = GDK_INPUT_OUTPUT;
34  attributes.event_mask = gtk_widget_get_events (widget) |
35  GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK;
36  gint attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_CURSOR;
37  attributes.cursor = gdk_cursor_new_for_display (
38  gtk_widget_get_display (widget), GDK_HAND2);
39 
40  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
41  label->link_window = gdk_window_new (gtk_widget_get_window(widget), &attributes, attributes_mask);
42  gdk_window_set_user_data (label->link_window, widget);
43  GdkRGBA white = { 1, 1, 1, 1 };
44  gdk_window_set_background_rgba(label->link_window, &white);
45  g_object_unref (G_OBJECT(attributes.cursor));
46 }
47 
48 static void ygtk_link_label_unrealize (GtkWidget *widget)
49 {
50  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
51  if (label->link_window) {
52  gdk_window_set_user_data (label->link_window, NULL);
53  gdk_window_destroy (label->link_window);
54  label->link_window = NULL;
55  }
56  GTK_WIDGET_CLASS (ygtk_link_label_parent_class)->unrealize (widget);
57 }
58 
59 static void ygtk_link_label_map (GtkWidget *widget)
60 {
61  // "more" hides on unmap and for some reason showing it clears the
62  // text...
63  gtk_widget_queue_resize (widget);
64  GTK_WIDGET_CLASS (ygtk_link_label_parent_class)->map (widget);
65 }
66 
67 static void ygtk_link_label_clear_layout (YGtkLinkLabel *label)
68 {
69  if (label->layout) {
70  g_object_unref (label->layout);
71  label->layout = NULL;
72  }
73  if (label->link_layout) {
74  g_object_unref (label->link_layout);
75  label->link_layout = NULL;
76  }
77 }
78 
79 static void ygtk_link_label_ensure_layout (YGtkLinkLabel *label)
80 {
81  GtkWidget *widget = GTK_WIDGET (label);
82  if (!label->layout) {
83  label->layout = gtk_widget_create_pango_layout (widget, label->text);
84  pango_layout_set_single_paragraph_mode (label->layout, TRUE);
85  pango_layout_set_ellipsize (label->layout, PANGO_ELLIPSIZE_END);
86  }
87  if (!label->link_layout) {
88  label->link_layout = gtk_widget_create_pango_layout (widget, label->link);
89  PangoAttrList *attrbs = pango_attr_list_new();
90  pango_attr_list_insert (attrbs, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
91  pango_attr_list_insert (attrbs, pango_attr_foreground_new (0, 0, 0xffff));
92  pango_layout_set_attributes (label->link_layout, attrbs);
93  pango_attr_list_unref (attrbs);
94  }
95 }
96 
97 static void ygtk_link_label_finalize (GObject *object)
98 {
99  YGtkLinkLabel *label = YGTK_LINK_LABEL (object);
100  g_free (label->text);
101  g_free (label->link);
102  ygtk_link_label_clear_layout (label);
103  G_OBJECT_CLASS (ygtk_link_label_parent_class)->finalize (object);
104 }
105 
106 static void ygtk_link_label_size_request (GtkWidget *widget,
107  GtkRequisition *requisition)
108 {
109  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
110  ygtk_link_label_ensure_layout (label);
111  requisition->width = requisition->height = 0;
112  GtkStyleContext *style_ctx;
113  PangoFontDescription *font_desc;
114  style_ctx = gtk_widget_get_style_context(widget);
115 // if (label->text && *label->text)
116  {
117  PangoContext *context;
118  PangoFontMetrics *metrics;
119  gint ascent, descent;
120  context = pango_layout_get_context (label->layout);
121  gtk_style_context_get (style_ctx, GTK_STATE_FLAG_NORMAL, "font", &font_desc, NULL);
122  metrics = pango_context_get_metrics (context, font_desc,
123  pango_context_get_language (context));
124  ascent = pango_font_metrics_get_ascent (metrics);
125  descent = pango_font_metrics_get_descent (metrics);
126  pango_font_metrics_unref (metrics);
127  requisition->height = PANGO_PIXELS (ascent + descent);
128  }
129 }
130 
131 static void
132 ygtk_link_label_get_preferred_width (GtkWidget *widget,
133  gint *minimal_width,
134  gint *natural_width)
135 {
136  GtkRequisition requisition;
137  ygtk_link_label_size_request (widget, &requisition);
138  *minimal_width = *natural_width = requisition.width;
139 }
140 
141 static void
142 ygtk_link_label_get_preferred_height (GtkWidget *widget,
143  gint *minimal_height,
144  gint *natural_height)
145 {
146  GtkRequisition requisition;
147  ygtk_link_label_size_request (widget, &requisition);
148  *minimal_height = *natural_height = requisition.height;
149 }
150 
151 #define SPACING 4
152 
153 static void ygtk_link_label_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
154 {
155  GTK_WIDGET_CLASS (ygtk_link_label_parent_class)->size_allocate (widget, allocation);
156  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
157 
158  gint width = allocation->width * PANGO_SCALE;
159  PangoRectangle logical;
160  pango_layout_set_width (label->layout, -1);
161  pango_layout_get_extents (label->layout, NULL, &logical);
162  if (label->link_window) {
163  if (*label->text && (logical.width > width || label->link_always_visible)) {
164  PangoRectangle link_logical;
165  pango_layout_get_extents (label->link_layout, NULL, &link_logical);
166  gint link_width = link_logical.width / PANGO_SCALE;
167  gint x;
168  width = width - link_logical.width - SPACING*PANGO_SCALE;
169  if (logical.width < width && label->link_always_visible)
170  x = allocation->x + logical.width/PANGO_SCALE + SPACING;
171  else
172  x = allocation->x + (allocation->width - link_width);
173  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
174  x = (2*allocation->x + allocation->width) - (x + link_width);
175  gdk_window_move_resize (label->link_window, x,
176  allocation->y, link_width, allocation->height);
177  if (logical.width > width)
178  pango_layout_set_width (label->layout, width);
179  gdk_window_show (label->link_window);
180  }
181  else
182  gdk_window_hide (label->link_window);
183  }
184 }
185 
186 static gboolean ygtk_link_label_on_draw (GtkWidget *widget, cairo_t *cr)
187 {
188  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
189  ygtk_link_label_ensure_layout (label);
190 
191  GtkStyleContext *style = gtk_widget_get_style_context(widget);
192  if (gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget))) {
193  gint x = 0;
194  gint width = gtk_widget_get_allocated_width (widget);
195  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) {
196  PangoRectangle extent;
197  pango_layout_get_extents (label->layout, NULL, &extent);
198  x = width - extent.width/PANGO_SCALE;
199  }
200  gtk_render_layout (style, cr, x, 0, label->layout);
201  }
202 
203  if (gtk_cairo_should_draw_window(cr, label->link_window)) {
204  gtk_cairo_transform_to_window (cr, widget, label->link_window);
205  gtk_render_layout (style, cr, 0, 0, label->link_layout);
206  }
207  return FALSE;
208 }
209 
210 static gboolean ygtk_link_label_button_press_event (GtkWidget *widget, GdkEventButton *event)
211 {
212  g_signal_emit (widget, link_clicked_signal, 0, NULL);
213  return TRUE;
214 }
215 
216 void ygtk_link_label_set_text (YGtkLinkLabel *label,
217  const gchar *text, const gchar *link, gboolean link_always_visible)
218 {
219  g_free (label->text);
220  label->text = g_strdup (text);
221  if (link) {
222  g_free (label->link);
223  label->link = g_strdup (link);
224  }
225  label->link_always_visible = link_always_visible;
226  ygtk_link_label_clear_layout (label);
227  gtk_widget_queue_resize (GTK_WIDGET (label));
228 }
229 
230 GtkWidget *ygtk_link_label_new (const gchar *text, const gchar *link)
231 {
232  YGtkLinkLabel *label = g_object_new (YGTK_TYPE_LINK_LABEL, NULL);
233  ygtk_link_label_set_text (label, text, link, TRUE);
234  return (GtkWidget *) label;
235 }
236 
237 static void ygtk_link_label_class_init (YGtkLinkLabelClass *klass)
238 {
239  ygtk_link_label_parent_class = g_type_class_peek_parent (klass);
240 
241  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
242  widget_class->realize = ygtk_link_label_realize;
243  widget_class->unrealize = ygtk_link_label_unrealize;
244  widget_class->map = ygtk_link_label_map;
245 
246  widget_class->get_preferred_height = ygtk_link_label_get_preferred_height;
247  widget_class->get_preferred_width = ygtk_link_label_get_preferred_width;
248 
249  widget_class->size_allocate = ygtk_link_label_size_allocate;
250  widget_class->draw = ygtk_link_label_on_draw;
251  widget_class->button_press_event = ygtk_link_label_button_press_event;
252 
253  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
254  gobject_class->finalize = ygtk_link_label_finalize;
255 
256  link_clicked_signal = g_signal_new ("link-clicked",
257  G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST,
258  G_STRUCT_OFFSET (YGtkLinkLabelClass, link_clicked), NULL, NULL,
259  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
260 }
261