libyui-gtk  2.42.2
 All Classes
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  style_ctx = gtk_widget_get_style_context(widget);
114 // if (label->text && *label->text)
115  {
116  PangoContext *context;
117  PangoFontMetrics *metrics;
118  gint ascent, descent;
119  context = pango_layout_get_context (label->layout);
120  metrics = pango_context_get_metrics (context, gtk_style_context_get_font(style_ctx, GTK_STATE_FLAG_NORMAL),
121  pango_context_get_language (context));
122  ascent = pango_font_metrics_get_ascent (metrics);
123  descent = pango_font_metrics_get_descent (metrics);
124  pango_font_metrics_unref (metrics);
125  requisition->height = PANGO_PIXELS (ascent + descent);
126  }
127 }
128 
129 static void
130 ygtk_link_label_get_preferred_width (GtkWidget *widget,
131  gint *minimal_width,
132  gint *natural_width)
133 {
134  GtkRequisition requisition;
135  ygtk_link_label_size_request (widget, &requisition);
136  *minimal_width = *natural_width = requisition.width;
137 }
138 
139 static void
140 ygtk_link_label_get_preferred_height (GtkWidget *widget,
141  gint *minimal_height,
142  gint *natural_height)
143 {
144  GtkRequisition requisition;
145  ygtk_link_label_size_request (widget, &requisition);
146  *minimal_height = *natural_height = requisition.height;
147 }
148 
149 #define SPACING 4
150 
151 static void ygtk_link_label_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
152 {
153  GTK_WIDGET_CLASS (ygtk_link_label_parent_class)->size_allocate (widget, allocation);
154  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
155 
156  gint width = allocation->width * PANGO_SCALE;
157  PangoRectangle logical;
158  pango_layout_set_width (label->layout, -1);
159  pango_layout_get_extents (label->layout, NULL, &logical);
160  if (label->link_window) {
161  if (*label->text && (logical.width > width || label->link_always_visible)) {
162  PangoRectangle link_logical;
163  pango_layout_get_extents (label->link_layout, NULL, &link_logical);
164  gint link_width = link_logical.width / PANGO_SCALE;
165  gint x;
166  width = width - link_logical.width - SPACING*PANGO_SCALE;
167  if (logical.width < width && label->link_always_visible)
168  x = allocation->x + logical.width/PANGO_SCALE + SPACING;
169  else
170  x = allocation->x + (allocation->width - link_width);
171  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
172  x = (2*allocation->x + allocation->width) - (x + link_width);
173  gdk_window_move_resize (label->link_window, x,
174  allocation->y, link_width, allocation->height);
175  if (logical.width > width)
176  pango_layout_set_width (label->layout, width);
177  gdk_window_show (label->link_window);
178  }
179  else
180  gdk_window_hide (label->link_window);
181  }
182 }
183 
184 static gboolean ygtk_link_label_on_draw (GtkWidget *widget, cairo_t *cr)
185 {
186  YGtkLinkLabel *label = YGTK_LINK_LABEL (widget);
187  ygtk_link_label_ensure_layout (label);
188 
189  GtkStyleContext *style = gtk_widget_get_style_context(widget);
190  if (gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget))) {
191  gint x = 0;
192  gint width = gtk_widget_get_allocated_width (widget);
193  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) {
194  PangoRectangle extent;
195  pango_layout_get_extents (label->layout, NULL, &extent);
196  x = width - extent.width/PANGO_SCALE;
197  }
198  gtk_render_layout (style, cr, x, 0, label->layout);
199  }
200 
201  if (gtk_cairo_should_draw_window(cr, label->link_window)) {
202  gtk_cairo_transform_to_window (cr, widget, label->link_window);
203  gtk_render_layout (style, cr, 0, 0, label->link_layout);
204  }
205  return FALSE;
206 }
207 
208 static gboolean ygtk_link_label_button_press_event (GtkWidget *widget, GdkEventButton *event)
209 {
210  g_signal_emit (widget, link_clicked_signal, 0, NULL);
211  return TRUE;
212 }
213 
214 void ygtk_link_label_set_text (YGtkLinkLabel *label,
215  const gchar *text, const gchar *link, gboolean link_always_visible)
216 {
217  g_free (label->text);
218  label->text = g_strdup (text);
219  if (link) {
220  g_free (label->link);
221  label->link = g_strdup (link);
222  }
223  label->link_always_visible = link_always_visible;
224  ygtk_link_label_clear_layout (label);
225  gtk_widget_queue_resize (GTK_WIDGET (label));
226 }
227 
228 GtkWidget *ygtk_link_label_new (const gchar *text, const gchar *link)
229 {
230  YGtkLinkLabel *label = g_object_new (YGTK_TYPE_LINK_LABEL, NULL);
231  ygtk_link_label_set_text (label, text, link, TRUE);
232  return (GtkWidget *) label;
233 }
234 
235 static void ygtk_link_label_class_init (YGtkLinkLabelClass *klass)
236 {
237  ygtk_link_label_parent_class = g_type_class_peek_parent (klass);
238 
239  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
240  widget_class->realize = ygtk_link_label_realize;
241  widget_class->unrealize = ygtk_link_label_unrealize;
242  widget_class->map = ygtk_link_label_map;
243 
244  widget_class->get_preferred_height = ygtk_link_label_get_preferred_height;
245  widget_class->get_preferred_width = ygtk_link_label_get_preferred_width;
246 
247  widget_class->size_allocate = ygtk_link_label_size_allocate;
248  widget_class->draw = ygtk_link_label_on_draw;
249  widget_class->button_press_event = ygtk_link_label_button_press_event;
250 
251  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
252  gobject_class->finalize = ygtk_link_label_finalize;
253 
254  link_clicked_signal = g_signal_new ("link-clicked",
255  G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST,
256  G_STRUCT_OFFSET (YGtkLinkLabelClass, link_clicked), NULL, NULL,
257  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
258 }
259