1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gtktextview.h"
28#include "gtktextutil.h"
29
30#include "gtkcsscolorvalueprivate.h"
31#include "gtkstylecontextprivate.h"
32#include "gtktextbuffer.h"
33#include "gtktextlayoutprivate.h"
34#include "gtkintl.h"
35#include "gtkwidgetprivate.h"
36#include "gtkcssstyleprivate.h"
37#include "gtkcsscolorvalueprivate.h"
38
39#define DRAG_ICON_MAX_WIDTH 250
40#define DRAG_ICON_MAX_HEIGHT 250
41#define DRAG_ICON_MAX_LINES 7
42#define ELLIPSIS_CHARACTER "\xe2\x80\xa6"
43
44static void
45append_n_lines (GString *str, const char *text, GSList *lines, int n_lines)
46{
47 PangoLayoutLine *line;
48 int i;
49
50 for (i = 0; i < n_lines; i++)
51 {
52 line = lines->data;
53 g_string_append_len (string: str,
54 val: &text[pango_layout_line_get_start_index (line)],
55 len: pango_layout_line_get_length (line));
56 lines = lines->next;
57 }
58}
59
60static void
61limit_layout_lines (PangoLayout *layout)
62{
63 const char *text;
64 GString *str;
65 GSList *lines, *elem;
66 int n_lines;
67
68 n_lines = pango_layout_get_line_count (layout);
69
70 if (n_lines >= DRAG_ICON_MAX_LINES)
71 {
72 text = pango_layout_get_text (layout);
73 str = g_string_new (NULL);
74 lines = pango_layout_get_lines_readonly (layout);
75
76 /* get first lines */
77 elem = lines;
78 append_n_lines (str, text, lines: elem,
79 DRAG_ICON_MAX_LINES / 2);
80
81 g_string_append (string: str, val: "\n" ELLIPSIS_CHARACTER "\n");
82
83 /* get last lines */
84 elem = g_slist_nth (list: lines, n: n_lines - DRAG_ICON_MAX_LINES / 2);
85 append_n_lines (str, text, lines: elem,
86 DRAG_ICON_MAX_LINES / 2);
87
88 pango_layout_set_text (layout, text: str->str, length: -1);
89 g_string_free (string: str, TRUE);
90 }
91}
92
93/**
94 * gtk_text_util_create_drag_icon:
95 * @widget: `GtkWidget` to extract the pango context
96 * @text: a #char to render the icon
97 * @len: length of @text, or -1 for NUL-terminated text
98 *
99 * Creates a drag and drop icon from @text.
100 *
101 * Returns: (transfer full): a `GdkPaintable` to use as DND icon
102 */
103GdkPaintable *
104gtk_text_util_create_drag_icon (GtkWidget *widget,
105 char *text,
106 gssize len)
107{
108 GtkCssStyle *style;
109 GtkSnapshot *snapshot;
110 PangoContext *context;
111 PangoLayout *layout;
112 GdkPaintable *paintable;
113 int layout_width;
114 int layout_height;
115 const GdkRGBA *color;
116 GdkDisplay *display;
117
118 g_return_val_if_fail (widget != NULL, NULL);
119 g_return_val_if_fail (text != NULL, NULL);
120
121 context = gtk_widget_get_pango_context (widget);
122 layout = pango_layout_new (context);
123
124 pango_layout_set_text (layout, text, length: len);
125 pango_layout_set_wrap (layout, wrap: PANGO_WRAP_WORD_CHAR);
126 pango_layout_get_size (layout, width: &layout_width, NULL);
127
128 layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH * PANGO_SCALE);
129 pango_layout_set_width (layout, width: layout_width);
130
131 limit_layout_lines (layout);
132
133 snapshot = gtk_snapshot_new ();
134
135 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
136 color = gtk_css_color_value_get_rgba (color: style->core->color);
137
138 display = gtk_widget_get_display (widget);
139
140 if (!gdk_display_is_rgba (display) ||
141 !gdk_display_is_composited (display))
142 {
143 GtkWidget *bg_widget;
144
145 if (GTK_IS_TEXT (widget))
146 bg_widget = gtk_widget_get_parent (widget);
147 else
148 bg_widget = widget;
149 pango_layout_get_size (layout, width: &layout_width, height: &layout_height);
150 gtk_snapshot_render_background (snapshot,
151 context: gtk_widget_get_style_context (widget: bg_widget),
152 x: 0, y: 0, width: layout_width / PANGO_SCALE,
153 height: layout_height / PANGO_SCALE);
154 }
155
156 gtk_snapshot_append_layout (snapshot, layout, color);
157
158 paintable = gtk_snapshot_free_to_paintable (snapshot, NULL);
159 g_object_unref (object: layout);
160
161 return paintable;
162}
163
164static void
165set_attributes_from_style (GtkWidget *widget,
166 GtkTextAttributes *values)
167{
168 GtkCssStyle *style;
169 const GdkRGBA black = { 0, };
170
171 if (!values->appearance.bg_rgba)
172 values->appearance.bg_rgba = gdk_rgba_copy (rgba: &black);
173 if (!values->appearance.fg_rgba)
174 values->appearance.fg_rgba = gdk_rgba_copy (rgba: &black);
175
176 style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
177
178 *values->appearance.bg_rgba = *gtk_css_color_value_get_rgba (color: style->background->background_color);
179 *values->appearance.fg_rgba = *gtk_css_color_value_get_rgba (color: style->core->color);
180
181 if (values->font)
182 pango_font_description_free (desc: values->font);
183
184 values->font = gtk_css_style_get_pango_font (style);
185}
186
187static int
188get_border_window_size (GtkTextView *text_view,
189 GtkTextWindowType window_type)
190{
191 GtkWidget *gutter;
192
193 gutter = gtk_text_view_get_gutter (text_view, win: window_type);
194 if (gutter != NULL)
195 return gtk_widget_get_width (widget: gutter);
196
197 return 0;
198}
199
200GdkPaintable *
201gtk_text_util_create_rich_drag_icon (GtkWidget *widget,
202 GtkTextBuffer *buffer,
203 GtkTextIter *start,
204 GtkTextIter *end)
205{
206 GtkAllocation allocation;
207 GdkPaintable *paintable;
208 GtkSnapshot *snapshot;
209 int layout_width, layout_height;
210 GtkTextBuffer *new_buffer;
211 GtkTextLayout *layout;
212 GtkTextAttributes *style;
213 PangoContext *ltr_context, *rtl_context;
214 GtkTextIter iter;
215 GdkDisplay *display;
216
217 g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
218 g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
219 g_return_val_if_fail (start != NULL, NULL);
220 g_return_val_if_fail (end != NULL, NULL);
221
222 new_buffer = gtk_text_buffer_new (table: gtk_text_buffer_get_tag_table (buffer));
223 gtk_text_buffer_get_start_iter (buffer: new_buffer, iter: &iter);
224
225 gtk_text_buffer_insert_range (buffer: new_buffer, iter: &iter, start, end);
226
227 gtk_text_buffer_get_start_iter (buffer: new_buffer, iter: &iter);
228
229 layout = gtk_text_layout_new ();
230
231 ltr_context = gtk_widget_create_pango_context (widget);
232 pango_context_set_base_dir (context: ltr_context, direction: PANGO_DIRECTION_LTR);
233 rtl_context = gtk_widget_create_pango_context (widget);
234 pango_context_set_base_dir (context: rtl_context, direction: PANGO_DIRECTION_RTL);
235
236 gtk_text_layout_set_contexts (layout, ltr_context, rtl_context);
237
238 g_object_unref (object: ltr_context);
239 g_object_unref (object: rtl_context);
240
241 style = gtk_text_attributes_new ();
242
243 gtk_widget_get_allocation (widget, allocation: &allocation);
244 layout_width = allocation.width;
245
246 set_attributes_from_style (widget, values: style);
247
248 if (GTK_IS_TEXT_VIEW (widget))
249 {
250 layout_width = layout_width
251 - get_border_window_size (GTK_TEXT_VIEW (widget), window_type: GTK_TEXT_WINDOW_LEFT)
252 - get_border_window_size (GTK_TEXT_VIEW (widget), window_type: GTK_TEXT_WINDOW_RIGHT);
253 }
254
255 style->direction = gtk_widget_get_direction (widget);
256 style->wrap_mode = GTK_WRAP_WORD_CHAR;
257
258 gtk_text_layout_set_default_style (layout, values: style);
259 gtk_text_attributes_unref (values: style);
260
261 gtk_text_layout_set_buffer (layout, buffer: new_buffer);
262 gtk_text_layout_set_cursor_visible (layout, FALSE);
263 gtk_text_layout_set_screen_width (layout, width: layout_width);
264
265 gtk_text_layout_validate (layout, DRAG_ICON_MAX_HEIGHT);
266 gtk_text_layout_get_size (layout, width: &layout_width, height: &layout_height);
267
268 layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH);
269 layout_height = MIN (layout_height, DRAG_ICON_MAX_HEIGHT);
270
271 snapshot = gtk_snapshot_new ();
272
273 display = gtk_widget_get_display (widget);
274
275 if (!gdk_display_is_rgba (display) ||
276 !gdk_display_is_composited (display))
277 {
278 gtk_snapshot_render_background (snapshot,
279 context: gtk_widget_get_style_context (widget),
280 x: 0, y: 0, width: layout_width, height: layout_height);
281 }
282
283 gtk_text_layout_snapshot (layout, widget, snapshot, clip: &(GdkRectangle) { 0, 0, layout_width, layout_height }, cursor_alpha: 1.0);
284
285 g_object_unref (object: layout);
286 g_object_unref (object: new_buffer);
287
288 paintable = gtk_snapshot_free_to_paintable (snapshot, size: &(graphene_size_t) { layout_width, layout_height });
289
290 return paintable;
291}
292
293static int
294layout_get_char_width (PangoLayout *layout)
295{
296 int width;
297 PangoFontMetrics *metrics;
298 const PangoFontDescription *font_desc;
299 PangoContext *context = pango_layout_get_context (layout);
300
301 font_desc = pango_layout_get_font_description (layout);
302 if (!font_desc)
303 font_desc = pango_context_get_font_description (context);
304
305 metrics = pango_context_get_metrics (context, desc: font_desc, NULL);
306 width = pango_font_metrics_get_approximate_char_width (metrics);
307 pango_font_metrics_unref (metrics);
308
309 return width;
310}
311
312/*
313 * _gtk_text_util_get_block_cursor_location
314 * @layout: a `PangoLayout`
315 * @index: index at which cursor is located
316 * @pos: cursor location
317 * @at_line_end: whether cursor is drawn at line end, not over some
318 * character
319 *
320 * Returns: whether cursor should actually be drawn as a rectangle.
321 * It may not be the case if character at index is invisible.
322 */
323gboolean
324_gtk_text_util_get_block_cursor_location (PangoLayout *layout,
325 int index,
326 PangoRectangle *pos,
327 gboolean *at_line_end)
328{
329 PangoRectangle strong_pos, weak_pos;
330 PangoLayoutLine *layout_line;
331 gboolean rtl;
332 int line_no;
333 const char *text;
334
335 g_return_val_if_fail (layout != NULL, FALSE);
336 g_return_val_if_fail (index >= 0, FALSE);
337 g_return_val_if_fail (pos != NULL, FALSE);
338
339 pango_layout_index_to_pos (layout, index_: index, pos);
340
341 if (pos->width != 0)
342 {
343 /* cursor is at some visible character, good */
344 if (at_line_end)
345 *at_line_end = FALSE;
346 if (pos->width < 0)
347 {
348 pos->x += pos->width;
349 pos->width = -pos->width;
350 }
351 return TRUE;
352 }
353
354 pango_layout_index_to_line_x (layout, index_: index, FALSE, line: &line_no, NULL);
355 layout_line = pango_layout_get_line_readonly (layout, line: line_no);
356 g_return_val_if_fail (layout_line != NULL, FALSE);
357
358 text = pango_layout_get_text (layout);
359
360 if (index < pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line))
361 {
362 /* this may be a zero-width character in the middle of the line,
363 * or it could be a character where line is wrapped, we do want
364 * block cursor in latter case */
365 if (g_utf8_next_char (text + index) - text !=
366 pango_layout_line_get_start_index (line: layout_line) + pango_layout_line_get_length (line: layout_line))
367 {
368 /* zero-width character in the middle of the line, do not
369 * bother with block cursor */
370 return FALSE;
371 }
372 }
373
374 /* Cursor is at the line end. It may be an empty line, or it could
375 * be on the left or on the right depending on text direction, or it
376 * even could be in the middle of visual layout in bidi text. */
377
378 pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &strong_pos, weak_pos: &weak_pos);
379
380 if (strong_pos.x != weak_pos.x)
381 {
382 /* do not show block cursor in this case, since the character typed
383 * in may or may not appear at the cursor position */
384 return FALSE;
385 }
386
387 /* In case when index points to the end of line, pos->x is always most right
388 * pixel of the layout line, so we need to correct it for RTL text. */
389 if (pango_layout_line_get_length (line: layout_line))
390 {
391 if (pango_layout_line_get_resolved_direction (line: layout_line) == PANGO_DIRECTION_RTL)
392 {
393 PangoLayoutIter *iter;
394 PangoRectangle line_rect;
395 int i;
396 int left, right;
397 const char *p;
398
399 p = g_utf8_prev_char (p: text + index);
400
401 pango_layout_line_index_to_x (line: layout_line, index_: p - text, FALSE, x_pos: &left);
402 pango_layout_line_index_to_x (line: layout_line, index_: p - text, TRUE, x_pos: &right);
403 pos->x = MIN (left, right);
404
405 iter = pango_layout_get_iter (layout);
406 for (i = 0; i < line_no; i++)
407 pango_layout_iter_next_line (iter);
408 pango_layout_iter_get_line_extents (iter, NULL, logical_rect: &line_rect);
409 pango_layout_iter_free (iter);
410
411 rtl = TRUE;
412 pos->x += line_rect.x;
413 }
414 else
415 rtl = FALSE;
416 }
417 else
418 {
419 PangoContext *context = pango_layout_get_context (layout);
420 rtl = pango_context_get_base_dir (context) == PANGO_DIRECTION_RTL;
421 }
422
423 pos->width = layout_get_char_width (layout);
424
425 if (rtl)
426 pos->x -= pos->width - 1;
427
428 if (at_line_end)
429 *at_line_end = TRUE;
430
431 return pos->width != 0;
432}
433

source code of gtk/gtk/gtktextutil.c