]> Pileus Git - ~andy/gtk/blob - gtk/gtktextutil.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtktextutil.c
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 #define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
31
32 #include "gtktextdisplay.h"
33 #include "gtktextbuffer.h"
34 #include "gtkmenuitem.h"
35 #include "gtkintl.h"
36
37 #define DRAG_ICON_MAX_WIDTH 250
38 #define DRAG_ICON_MAX_HEIGHT 250
39 #define DRAG_ICON_LAYOUT_BORDER 5
40 #define DRAG_ICON_MAX_LINES 7
41 #define ELLIPSIS_CHARACTER "\xe2\x80\xa6"
42
43 typedef struct _GtkUnicodeMenuEntry GtkUnicodeMenuEntry;
44 typedef struct _GtkTextUtilCallbackInfo GtkTextUtilCallbackInfo;
45
46 struct _GtkUnicodeMenuEntry {
47   const char *label;
48   gunichar ch;
49 };
50
51 struct _GtkTextUtilCallbackInfo
52 {
53   GtkTextUtilCharChosenFunc func;
54   gpointer data;
55 };
56
57 static const GtkUnicodeMenuEntry bidi_menu_entries[] = {
58   { N_("LRM _Left-to-right mark"), 0x200E },
59   { N_("RLM _Right-to-left mark"), 0x200F },
60   { N_("LRE Left-to-right _embedding"), 0x202A },
61   { N_("RLE Right-to-left e_mbedding"), 0x202B },
62   { N_("LRO Left-to-right _override"), 0x202D },
63   { N_("RLO Right-to-left o_verride"), 0x202E },
64   { N_("PDF _Pop directional formatting"), 0x202C },
65   { N_("ZWS _Zero width space"), 0x200B },
66   { N_("ZWJ Zero width _joiner"), 0x200D },
67   { N_("ZWNJ Zero width _non-joiner"), 0x200C }
68 };
69
70 static GtkTextUtilCallbackInfo *
71 callback_info_new (GtkTextUtilCharChosenFunc  func,
72                    gpointer                   data)
73 {
74   GtkTextUtilCallbackInfo *info;
75
76   info = g_slice_new (GtkTextUtilCallbackInfo);
77
78   info->func = func;
79   info->data = data;
80
81   return info;
82 }
83
84 static void
85 callback_info_free (GtkTextUtilCallbackInfo *info)
86 {
87   g_slice_free (GtkTextUtilCallbackInfo, info);
88 }
89
90 static void
91 activate_cb (GtkWidget *menu_item,
92              gpointer   data)
93 {
94   GtkUnicodeMenuEntry *entry;
95   GtkTextUtilCallbackInfo *info = data;
96   char buf[7];
97   
98   entry = g_object_get_data (G_OBJECT (menu_item), "gtk-unicode-menu-entry");
99
100   buf[g_unichar_to_utf8 (entry->ch, buf)] = '\0';
101   
102   (* info->func) (buf, info->data);
103 }
104
105 /*
106  * _gtk_text_util_append_special_char_menuitems
107  * @menushell: a #GtkMenuShell
108  * @callback:  call this when an item is chosen
109  * @data: data for callback
110  * 
111  * Add menuitems for various bidi control characters  to a menu;
112  * the menuitems, when selected, will call the given function
113  * with the chosen character.
114  *
115  * This function is private/internal in GTK 2.0, the functionality may
116  * become public sometime, but it probably needs more thought first.
117  * e.g. maybe there should be a way to just get the list of items,
118  * instead of requiring the menu items to be created.
119  */
120 void
121 _gtk_text_util_append_special_char_menuitems (GtkMenuShell              *menushell,
122                                               GtkTextUtilCharChosenFunc  func,
123                                               gpointer                   data)
124 {
125   int i;
126
127   for (i = 0; i < G_N_ELEMENTS (bidi_menu_entries); i++)
128     {
129       GtkWidget *menuitem;
130       GtkTextUtilCallbackInfo *info;
131
132       info = callback_info_new (func, data);
133
134       menuitem = gtk_menu_item_new_with_mnemonic (_(bidi_menu_entries[i].label));
135       g_object_set_data (G_OBJECT (menuitem), I_("gtk-unicode-menu-entry"),
136                          (gpointer)&bidi_menu_entries[i]);
137
138       g_signal_connect_data (menuitem, "activate",
139                              G_CALLBACK (activate_cb),
140                              info, (GClosureNotify) callback_info_free, 0);
141
142       gtk_widget_show (menuitem);
143       gtk_menu_shell_append (menushell, menuitem);
144     }
145 }
146
147 static void
148 append_n_lines (GString *str, const gchar *text, GSList *lines, gint n_lines)
149 {
150   PangoLayoutLine *line;
151   gint i;
152
153   for (i = 0; i < n_lines; i++)
154     {
155       line = lines->data;
156       g_string_append_len (str, &text[line->start_index], line->length);
157       lines = lines->next;
158     }
159 }
160
161 static void
162 limit_layout_lines (PangoLayout *layout)
163 {
164   const gchar *text;
165   GString     *str;
166   GSList      *lines, *elem;
167   gint         n_lines;
168
169   n_lines = pango_layout_get_line_count (layout);
170   
171   if (n_lines >= DRAG_ICON_MAX_LINES)
172     {
173       text  = pango_layout_get_text (layout);
174       str   = g_string_new (NULL);
175       lines = pango_layout_get_lines_readonly (layout);
176
177       /* get first lines */
178       elem = lines;
179       append_n_lines (str, text, elem,
180                       DRAG_ICON_MAX_LINES / 2);
181
182       g_string_append (str, "\n" ELLIPSIS_CHARACTER "\n");
183
184       /* get last lines */
185       elem = g_slist_nth (lines, n_lines - DRAG_ICON_MAX_LINES / 2);
186       append_n_lines (str, text, elem,
187                       DRAG_ICON_MAX_LINES / 2);
188
189       pango_layout_set_text (layout, str->str, -1);
190       g_string_free (str, TRUE);
191     }
192 }
193
194 /*
195  * _gtk_text_util_create_drag_icon
196  * @widget: #GtkWidget to extract the pango context
197  * @text: a #gchar to render the icon
198  * @len: length of @text, or -1 for NUL-terminated text
199  *
200  * Creates a drag and drop icon from @text.
201  *
202  * Returns: a #cairo_surface_t to use as DND icon
203  */
204 cairo_surface_t *
205 _gtk_text_util_create_drag_icon (GtkWidget *widget, 
206                                  gchar     *text,
207                                  gsize      len)
208 {
209   GtkStyleContext *style_context;
210   GtkStateFlags state;
211   cairo_surface_t *surface;
212   PangoContext *context;
213   PangoLayout  *layout;
214   cairo_t      *cr;
215   gint          pixmap_height, pixmap_width;
216   gint          layout_width, layout_height;
217   GdkRGBA       color;
218
219   g_return_val_if_fail (widget != NULL, NULL);
220   g_return_val_if_fail (text != NULL, NULL);
221
222   context = gtk_widget_get_pango_context (widget);
223   layout  = pango_layout_new (context);
224
225   pango_layout_set_text (layout, text, len);
226   pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
227   pango_layout_get_size (layout, &layout_width, &layout_height);
228
229   layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH * PANGO_SCALE);
230   pango_layout_set_width (layout, layout_width);
231
232   limit_layout_lines (layout);
233
234   /* get again layout extents, they may have changed */
235   pango_layout_get_size (layout, &layout_width, &layout_height);
236
237   pixmap_width  = layout_width  / PANGO_SCALE + DRAG_ICON_LAYOUT_BORDER * 2;
238   pixmap_height = layout_height / PANGO_SCALE + DRAG_ICON_LAYOUT_BORDER * 2;
239
240   style_context = gtk_widget_get_style_context (widget);
241   state = gtk_widget_get_state_flags (widget);
242
243   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
244                                                CAIRO_CONTENT_COLOR,
245                                                pixmap_width  + 2,
246                                                pixmap_height + 2);
247   cr = cairo_create (surface);
248
249   gtk_style_context_save (style_context);
250   gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
251
252   gtk_style_context_get_background_color (style_context, state, &color);
253   gdk_cairo_set_source_rgba (cr, &color);
254   cairo_paint (cr);
255
256   gtk_style_context_get_color (style_context, state, &color);
257   gdk_cairo_set_source_rgba (cr, &color);
258   cairo_move_to (cr, 1 + DRAG_ICON_LAYOUT_BORDER, 1 + DRAG_ICON_LAYOUT_BORDER);
259   pango_cairo_show_layout (cr, layout);
260
261   cairo_set_source_rgb (cr, 0, 0, 0);
262   cairo_rectangle (cr, 0.5, 0.5, pixmap_width + 1, pixmap_height + 1);
263   cairo_set_line_width (cr, 1.0);
264   cairo_stroke (cr);
265
266   cairo_destroy (cr);
267   g_object_unref (layout);
268
269   cairo_surface_set_device_offset (surface, 2, 2);
270
271   gtk_style_context_restore (style_context);
272
273   return surface;
274 }
275
276 static void
277 gtk_text_view_set_attributes_from_style (GtkTextView        *text_view,
278                                          GtkTextAttributes  *values)
279 {
280   GtkStyleContext *context;
281   GdkRGBA bg_color, fg_color;
282   GtkStateFlags state;
283
284   context = gtk_widget_get_style_context (GTK_WIDGET (text_view));
285   state = gtk_widget_get_state_flags (GTK_WIDGET (text_view));
286
287   gtk_style_context_get_background_color (context, state, &bg_color);
288   gtk_style_context_get_color (context, state, &fg_color);
289
290   values->appearance.bg_color.red = CLAMP (bg_color.red * 65535. + 0.5, 0, 65535);
291   values->appearance.bg_color.green = CLAMP (bg_color.green * 65535. + 0.5, 0, 65535);
292   values->appearance.bg_color.blue = CLAMP (bg_color.blue * 65535. + 0.5, 0, 65535);
293
294   values->appearance.fg_color.red = CLAMP (fg_color.red * 65535. + 0.5, 0, 65535);
295   values->appearance.fg_color.green = CLAMP (fg_color.green * 65535. + 0.5, 0, 65535);
296   values->appearance.fg_color.blue = CLAMP (fg_color.blue * 65535. + 0.5, 0, 65535);
297
298   if (values->font)
299     pango_font_description_free (values->font);
300
301   gtk_style_context_get (context, state, "font", &values->font, NULL);
302 }
303
304 cairo_surface_t *
305 _gtk_text_util_create_rich_drag_icon (GtkWidget     *widget,
306                                       GtkTextBuffer *buffer,
307                                       GtkTextIter   *start,
308                                       GtkTextIter   *end)
309 {
310   GtkAllocation      allocation;
311   cairo_surface_t   *surface;
312   gint               pixmap_height, pixmap_width;
313   gint               layout_width, layout_height;
314   GtkStyleContext   *context;
315   GtkStateFlags      state;
316   GdkRGBA            color;
317   GtkTextBuffer     *new_buffer;
318   GtkTextLayout     *layout;
319   GtkTextAttributes *style;
320   PangoContext      *ltr_context, *rtl_context;
321   GtkTextIter        iter;
322   cairo_t           *cr;
323
324    g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
325    g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
326    g_return_val_if_fail (start != NULL, NULL);
327    g_return_val_if_fail (end != NULL, NULL);
328
329    context = gtk_widget_get_style_context (widget);
330    state = gtk_widget_get_state_flags (widget);
331
332    new_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (buffer));
333    gtk_text_buffer_get_start_iter (new_buffer, &iter);
334
335    gtk_text_buffer_insert_range (new_buffer, &iter, start, end);
336
337    gtk_text_buffer_get_start_iter (new_buffer, &iter);
338
339    layout = gtk_text_layout_new ();
340
341    ltr_context = gtk_widget_create_pango_context (widget);
342    pango_context_set_base_dir (ltr_context, PANGO_DIRECTION_LTR);
343    rtl_context = gtk_widget_create_pango_context (widget);
344    pango_context_set_base_dir (rtl_context, PANGO_DIRECTION_RTL);
345
346    gtk_text_layout_set_contexts (layout, ltr_context, rtl_context);
347
348    g_object_unref (ltr_context);
349    g_object_unref (rtl_context);
350
351    style = gtk_text_attributes_new ();
352
353    gtk_widget_get_allocation (widget, &allocation);
354    layout_width = allocation.width;
355
356    if (GTK_IS_TEXT_VIEW (widget))
357      {
358        gtk_text_view_set_attributes_from_style (GTK_TEXT_VIEW (widget), style);
359
360        layout_width = layout_width
361          - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_LEFT)
362          - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_RIGHT);
363      }
364
365    style->direction = gtk_widget_get_direction (widget);
366    style->wrap_mode = PANGO_WRAP_WORD_CHAR;
367
368    gtk_text_layout_set_default_style (layout, style);
369    gtk_text_attributes_unref (style);
370
371    gtk_text_layout_set_buffer (layout, new_buffer);
372    gtk_text_layout_set_cursor_visible (layout, FALSE);
373    gtk_text_layout_set_screen_width (layout, layout_width);
374
375    gtk_text_layout_validate (layout, DRAG_ICON_MAX_HEIGHT);
376    gtk_text_layout_get_size (layout, &layout_width, &layout_height);
377
378    layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH);
379    layout_height = MIN (layout_height, DRAG_ICON_MAX_HEIGHT);
380
381    pixmap_width  = layout_width + DRAG_ICON_LAYOUT_BORDER * 2;
382    pixmap_height = layout_height + DRAG_ICON_LAYOUT_BORDER * 2;
383
384    surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
385                                                 CAIRO_CONTENT_COLOR,
386                                                 pixmap_width  + 2,
387                                                 pixmap_height + 2);
388
389    cr = cairo_create (surface);
390
391    gtk_style_context_save (context);
392    gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
393
394    gtk_style_context_get_background_color (context, state, &color);
395    gdk_cairo_set_source_rgba (cr, &color);
396    cairo_paint (cr);
397
398    cairo_save (cr);
399
400    cairo_translate (cr, 1 + DRAG_ICON_LAYOUT_BORDER, 1 + DRAG_ICON_LAYOUT_BORDER);
401    gtk_text_layout_draw (layout, widget, cr, NULL);
402
403    cairo_restore (cr);
404
405    cairo_set_source_rgb (cr, 0, 0, 0);
406    cairo_rectangle (cr, 0.5, 0.5, pixmap_width + 1, pixmap_height + 1);
407    cairo_set_line_width (cr, 1.0);
408    cairo_stroke (cr);
409
410    cairo_destroy (cr);
411    g_object_unref (layout);
412    g_object_unref (new_buffer);
413
414    cairo_surface_set_device_offset (surface, 2, 2);
415
416    gtk_style_context_restore (context);
417
418    return surface;
419 }
420
421
422 static gint
423 layout_get_char_width (PangoLayout *layout)
424 {
425   gint width;
426   PangoFontMetrics *metrics;
427   const PangoFontDescription *font_desc;
428   PangoContext *context = pango_layout_get_context (layout);
429
430   font_desc = pango_layout_get_font_description (layout);
431   if (!font_desc)
432     font_desc = pango_context_get_font_description (context);
433
434   metrics = pango_context_get_metrics (context, font_desc, NULL);
435   width = pango_font_metrics_get_approximate_char_width (metrics);
436   pango_font_metrics_unref (metrics);
437
438   return width;
439 }
440
441 /*
442  * _gtk_text_util_get_block_cursor_location
443  * @layout: a #PangoLayout
444  * @index: index at which cursor is located
445  * @pos: cursor location
446  * @at_line_end: whether cursor is drawn at line end, not over some
447  * character
448  *
449  * Returns: whether cursor should actually be drawn as a rectangle.
450  *     It may not be the case if character at index is invisible.
451  */
452 gboolean
453 _gtk_text_util_get_block_cursor_location (PangoLayout    *layout,
454                                           gint            index,
455                                           PangoRectangle *pos,
456                                           gboolean       *at_line_end)
457 {
458   PangoRectangle strong_pos, weak_pos;
459   PangoLayoutLine *layout_line;
460   gboolean rtl;
461   gint line_no;
462   const gchar *text;
463
464   g_return_val_if_fail (layout != NULL, FALSE);
465   g_return_val_if_fail (index >= 0, FALSE);
466   g_return_val_if_fail (pos != NULL, FALSE);
467
468   pango_layout_index_to_pos (layout, index, pos);
469
470   if (pos->width != 0)
471     {
472       /* cursor is at some visible character, good */
473       if (at_line_end)
474         *at_line_end = FALSE;
475       if (pos->width < 0)
476         {
477           pos->x += pos->width;
478           pos->width = -pos->width;
479         }
480       return TRUE;
481     }
482
483   pango_layout_index_to_line_x (layout, index, FALSE, &line_no, NULL);
484   layout_line = pango_layout_get_line_readonly (layout, line_no);
485   g_return_val_if_fail (layout_line != NULL, FALSE);
486
487   text = pango_layout_get_text (layout);
488
489   if (index < layout_line->start_index + layout_line->length)
490     {
491       /* this may be a zero-width character in the middle of the line,
492        * or it could be a character where line is wrapped, we do want
493        * block cursor in latter case */
494       if (g_utf8_next_char (text + index) - text !=
495           layout_line->start_index + layout_line->length)
496         {
497           /* zero-width character in the middle of the line, do not
498            * bother with block cursor */
499           return FALSE;
500         }
501     }
502
503   /* Cursor is at the line end. It may be an empty line, or it could
504    * be on the left or on the right depending on text direction, or it
505    * even could be in the middle of visual layout in bidi text. */
506
507   pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos);
508
509   if (strong_pos.x != weak_pos.x)
510     {
511       /* do not show block cursor in this case, since the character typed
512        * in may or may not appear at the cursor position */
513       return FALSE;
514     }
515
516   /* In case when index points to the end of line, pos->x is always most right
517    * pixel of the layout line, so we need to correct it for RTL text. */
518   if (layout_line->length)
519     {
520       if (layout_line->resolved_dir == PANGO_DIRECTION_RTL)
521         {
522           PangoLayoutIter *iter;
523           PangoRectangle line_rect;
524           gint i;
525           gint left, right;
526           const gchar *p;
527
528           p = g_utf8_prev_char (text + index);
529
530           pango_layout_line_index_to_x (layout_line, p - text, FALSE, &left);
531           pango_layout_line_index_to_x (layout_line, p - text, TRUE, &right);
532           pos->x = MIN (left, right);
533
534           iter = pango_layout_get_iter (layout);
535           for (i = 0; i < line_no; i++)
536             pango_layout_iter_next_line (iter);
537           pango_layout_iter_get_line_extents (iter, NULL, &line_rect);
538           pango_layout_iter_free (iter);
539
540           rtl = TRUE;
541           pos->x += line_rect.x;
542         }
543       else
544         rtl = FALSE;
545     }
546   else
547     {
548       PangoContext *context = pango_layout_get_context (layout);
549       rtl = pango_context_get_base_dir (context) == PANGO_DIRECTION_RTL;
550     }
551
552   pos->width = layout_get_char_width (layout);
553
554   if (rtl)
555     pos->x -= pos->width - 1;
556
557   if (at_line_end)
558     *at_line_end = TRUE;
559
560   return pos->width != 0;
561 }