| /* GTK - The GIMP Toolkit |
| * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* |
| * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS |
| * file for a list of people on the GTK+ Team. See the ChangeLog |
| * files for a list of changes. These files are distributed with |
| * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
| */ |
| |
| #include "config.h" |
| |
| #include "gtktextview.h" |
| #include "gtktextutil.h" |
| |
| #include "gtkcsscolorvalueprivate.h" |
| #include "gtkstylecontextprivate.h" |
| #include "gtktextbuffer.h" |
| #include "gtktextlayoutprivate.h" |
| #include "gtkintl.h" |
| #include "gtkwidgetprivate.h" |
| #include "gtkcssstyleprivate.h" |
| #include "gtkcsscolorvalueprivate.h" |
| |
| #define DRAG_ICON_MAX_WIDTH 250 |
| #define DRAG_ICON_MAX_HEIGHT 250 |
| #define DRAG_ICON_MAX_LINES 7 |
| #define ELLIPSIS_CHARACTER "\xe2\x80\xa6" |
| |
| static void |
| append_n_lines (GString *str, const char *text, GSList *lines, int n_lines) |
| { |
| PangoLayoutLine *line; |
| int i; |
| |
| for (i = 0; i < n_lines; i++) |
| { |
| line = lines->data; |
| g_string_append_len (str, &text[line->start_index], line->length); |
| lines = lines->next; |
| } |
| } |
| |
| static void |
| limit_layout_lines (PangoLayout *layout) |
| { |
| const char *text; |
| GString *str; |
| GSList *lines, *elem; |
| int n_lines; |
| |
| n_lines = pango_layout_get_line_count (layout); |
| |
| if (n_lines >= DRAG_ICON_MAX_LINES) |
| { |
| text = pango_layout_get_text (layout); |
| str = g_string_new (NULL); |
| lines = pango_layout_get_lines_readonly (layout); |
| |
| /* get first lines */ |
| elem = lines; |
| append_n_lines (str, text, elem, |
| DRAG_ICON_MAX_LINES / 2); |
| |
| g_string_append (str, "\n" ELLIPSIS_CHARACTER "\n"); |
| |
| /* get last lines */ |
| elem = g_slist_nth (lines, n_lines - DRAG_ICON_MAX_LINES / 2); |
| append_n_lines (str, text, elem, |
| DRAG_ICON_MAX_LINES / 2); |
| |
| pango_layout_set_text (layout, str->str, -1); |
| g_string_free (str, TRUE); |
| } |
| } |
| |
| /** |
| * gtk_text_util_create_drag_icon: |
| * @widget: #GtkWidget to extract the pango context |
| * @text: a #char to render the icon |
| * @len: length of @text, or -1 for NUL-terminated text |
| * |
| * Creates a drag and drop icon from @text. |
| * |
| * Returns: (transfer full): a #GdkPaintable to use as DND icon |
| */ |
| GdkPaintable * |
| gtk_text_util_create_drag_icon (GtkWidget *widget, |
| char *text, |
| gssize len) |
| { |
| GtkCssStyle *style; |
| GtkSnapshot *snapshot; |
| PangoContext *context; |
| PangoLayout *layout; |
| GdkPaintable *paintable; |
| int layout_width; |
| int layout_height; |
| const GdkRGBA *color; |
| GdkDisplay *display; |
| |
| g_return_val_if_fail (widget != NULL, NULL); |
| g_return_val_if_fail (text != NULL, NULL); |
| |
| context = gtk_widget_get_pango_context (widget); |
| layout = pango_layout_new (context); |
| |
| pango_layout_set_text (layout, text, len); |
| pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR); |
| pango_layout_get_size (layout, &layout_width, NULL); |
| |
| layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH * PANGO_SCALE); |
| pango_layout_set_width (layout, layout_width); |
| |
| limit_layout_lines (layout); |
| |
| snapshot = gtk_snapshot_new (); |
| |
| style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); |
| color = gtk_css_color_value_get_rgba (style->core->color); |
| |
| display = gtk_widget_get_display (widget); |
| |
| if (!gdk_display_is_rgba (display) || |
| !gdk_display_is_composited (display)) |
| { |
| GtkWidget *bg_widget; |
| |
| if (GTK_IS_TEXT (widget)) |
| bg_widget = gtk_widget_get_parent (widget); |
| else |
| bg_widget = widget; |
| pango_layout_get_size (layout, &layout_width, &layout_height); |
| gtk_snapshot_render_background (snapshot, |
| gtk_widget_get_style_context (bg_widget), |
| 0, 0, layout_width / PANGO_SCALE, |
| layout_height / PANGO_SCALE); |
| } |
| |
| gtk_snapshot_append_layout (snapshot, layout, color); |
| |
| paintable = gtk_snapshot_free_to_paintable (snapshot, NULL); |
| g_object_unref (layout); |
| |
| return paintable; |
| } |
| |
| static void |
| set_attributes_from_style (GtkWidget *widget, |
| GtkTextAttributes *values) |
| { |
| GtkCssStyle *style; |
| const GdkRGBA black = { 0, }; |
| |
| if (!values->appearance.bg_rgba) |
| values->appearance.bg_rgba = gdk_rgba_copy (&black); |
| if (!values->appearance.fg_rgba) |
| values->appearance.fg_rgba = gdk_rgba_copy (&black); |
| |
| style = gtk_css_node_get_style (gtk_widget_get_css_node (widget)); |
| |
| *values->appearance.bg_rgba = *gtk_css_color_value_get_rgba (style->background->background_color); |
| *values->appearance.fg_rgba = *gtk_css_color_value_get_rgba (style->core->color); |
| |
| if (values->font) |
| pango_font_description_free (values->font); |
| |
| values->font = gtk_css_style_get_pango_font (style); |
| } |
| |
| static int |
| get_border_window_size (GtkTextView *text_view, |
| GtkTextWindowType window_type) |
| { |
| GtkWidget *gutter; |
| |
| gutter = gtk_text_view_get_gutter (text_view, window_type); |
| if (gutter != NULL) |
| return gtk_widget_get_width (gutter); |
| |
| return 0; |
| } |
| |
| GdkPaintable * |
| gtk_text_util_create_rich_drag_icon (GtkWidget *widget, |
| GtkTextBuffer *buffer, |
| GtkTextIter *start, |
| GtkTextIter *end) |
| { |
| GtkAllocation allocation; |
| GdkPaintable *paintable; |
| GtkSnapshot *snapshot; |
| int layout_width, layout_height; |
| GtkTextBuffer *new_buffer; |
| GtkTextLayout *layout; |
| GtkTextAttributes *style; |
| PangoContext *ltr_context, *rtl_context; |
| GtkTextIter iter; |
| GdkDisplay *display; |
| |
| g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); |
| g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL); |
| g_return_val_if_fail (start != NULL, NULL); |
| g_return_val_if_fail (end != NULL, NULL); |
| |
| new_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (buffer)); |
| gtk_text_buffer_get_start_iter (new_buffer, &iter); |
| |
| gtk_text_buffer_insert_range (new_buffer, &iter, start, end); |
| |
| gtk_text_buffer_get_start_iter (new_buffer, &iter); |
| |
| layout = gtk_text_layout_new (); |
| |
| ltr_context = gtk_widget_create_pango_context (widget); |
| pango_context_set_base_dir (ltr_context, PANGO_DIRECTION_LTR); |
| rtl_context = gtk_widget_create_pango_context (widget); |
| pango_context_set_base_dir (rtl_context, PANGO_DIRECTION_RTL); |
| |
| gtk_text_layout_set_contexts (layout, ltr_context, rtl_context); |
| |
| g_object_unref (ltr_context); |
| g_object_unref (rtl_context); |
| |
| style = gtk_text_attributes_new (); |
| |
| gtk_widget_get_allocation (widget, &allocation); |
| layout_width = allocation.width; |
| |
| set_attributes_from_style (widget, style); |
| |
| if (GTK_IS_TEXT_VIEW (widget)) |
| { |
| layout_width = layout_width |
| - get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_LEFT) |
| - get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_RIGHT); |
| } |
| |
| style->direction = gtk_widget_get_direction (widget); |
| style->wrap_mode = GTK_WRAP_WORD_CHAR; |
| |
| gtk_text_layout_set_default_style (layout, style); |
| gtk_text_attributes_unref (style); |
| |
| gtk_text_layout_set_buffer (layout, new_buffer); |
| gtk_text_layout_set_cursor_visible (layout, FALSE); |
| gtk_text_layout_set_screen_width (layout, layout_width); |
| |
| gtk_text_layout_validate (layout, DRAG_ICON_MAX_HEIGHT); |
| gtk_text_layout_get_size (layout, &layout_width, &layout_height); |
| |
| layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH); |
| layout_height = MIN (layout_height, DRAG_ICON_MAX_HEIGHT); |
| |
| snapshot = gtk_snapshot_new (); |
| |
| display = gtk_widget_get_display (widget); |
| |
| if (!gdk_display_is_rgba (display) || |
| !gdk_display_is_composited (display)) |
| { |
| gtk_snapshot_render_background (snapshot, |
| gtk_widget_get_style_context (widget), |
| 0, 0, layout_width, layout_height); |
| } |
| |
| gtk_text_layout_snapshot (layout, widget, snapshot, &(GdkRectangle) { 0, 0, layout_width, layout_height }, 1.0); |
| |
| g_object_unref (layout); |
| g_object_unref (new_buffer); |
| |
| paintable = gtk_snapshot_free_to_paintable (snapshot, &(graphene_size_t) { layout_width, layout_height }); |
| |
| return paintable; |
| } |
| |
| static int |
| layout_get_char_width (PangoLayout *layout) |
| { |
| int width; |
| PangoFontMetrics *metrics; |
| const PangoFontDescription *font_desc; |
| PangoContext *context = pango_layout_get_context (layout); |
| |
| font_desc = pango_layout_get_font_description (layout); |
| if (!font_desc) |
| font_desc = pango_context_get_font_description (context); |
| |
| metrics = pango_context_get_metrics (context, font_desc, NULL); |
| width = pango_font_metrics_get_approximate_char_width (metrics); |
| pango_font_metrics_unref (metrics); |
| |
| return width; |
| } |
| |
| /* |
| * _gtk_text_util_get_block_cursor_location |
| * @layout: a #PangoLayout |
| * @index: index at which cursor is located |
| * @pos: cursor location |
| * @at_line_end: whether cursor is drawn at line end, not over some |
| * character |
| * |
| * Returns: whether cursor should actually be drawn as a rectangle. |
| * It may not be the case if character at index is invisible. |
| */ |
| gboolean |
| _gtk_text_util_get_block_cursor_location (PangoLayout *layout, |
| int index, |
| PangoRectangle *pos, |
| gboolean *at_line_end) |
| { |
| PangoRectangle strong_pos, weak_pos; |
| PangoLayoutLine *layout_line; |
| gboolean rtl; |
| int line_no; |
| const char *text; |
| |
| g_return_val_if_fail (layout != NULL, FALSE); |
| g_return_val_if_fail (index >= 0, FALSE); |
| g_return_val_if_fail (pos != NULL, FALSE); |
| |
| pango_layout_index_to_pos (layout, index, pos); |
| |
| if (pos->width != 0) |
| { |
| /* cursor is at some visible character, good */ |
| if (at_line_end) |
| *at_line_end = FALSE; |
| if (pos->width < 0) |
| { |
| pos->x += pos->width; |
| pos->width = -pos->width; |
| } |
| return TRUE; |
| } |
| |
| pango_layout_index_to_line_x (layout, index, FALSE, &line_no, NULL); |
| layout_line = pango_layout_get_line_readonly (layout, line_no); |
| g_return_val_if_fail (layout_line != NULL, FALSE); |
| |
| text = pango_layout_get_text (layout); |
| |
| if (index < layout_line->start_index + layout_line->length) |
| { |
| /* this may be a zero-width character in the middle of the line, |
| * or it could be a character where line is wrapped, we do want |
| * block cursor in latter case */ |
| if (g_utf8_next_char (text + index) - text != |
| layout_line->start_index + layout_line->length) |
| { |
| /* zero-width character in the middle of the line, do not |
| * bother with block cursor */ |
| return FALSE; |
| } |
| } |
| |
| /* Cursor is at the line end. It may be an empty line, or it could |
| * be on the left or on the right depending on text direction, or it |
| * even could be in the middle of visual layout in bidi text. */ |
| |
| pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos); |
| |
| if (strong_pos.x != weak_pos.x) |
| { |
| /* do not show block cursor in this case, since the character typed |
| * in may or may not appear at the cursor position */ |
| return FALSE; |
| } |
| |
| /* In case when index points to the end of line, pos->x is always most right |
| * pixel of the layout line, so we need to correct it for RTL text. */ |
| if (layout_line->length) |
| { |
| if (layout_line->resolved_dir == PANGO_DIRECTION_RTL) |
| { |
| PangoLayoutIter *iter; |
| PangoRectangle line_rect; |
| int i; |
| int left, right; |
| const char *p; |
| |
| p = g_utf8_prev_char (text + index); |
| |
| pango_layout_line_index_to_x (layout_line, p - text, FALSE, &left); |
| pango_layout_line_index_to_x (layout_line, p - text, TRUE, &right); |
| pos->x = MIN (left, right); |
| |
| iter = pango_layout_get_iter (layout); |
| for (i = 0; i < line_no; i++) |
| pango_layout_iter_next_line (iter); |
| pango_layout_iter_get_line_extents (iter, NULL, &line_rect); |
| pango_layout_iter_free (iter); |
| |
| rtl = TRUE; |
| pos->x += line_rect.x; |
| } |
| else |
| rtl = FALSE; |
| } |
| else |
| { |
| PangoContext *context = pango_layout_get_context (layout); |
| rtl = pango_context_get_base_dir (context) == PANGO_DIRECTION_RTL; |
| } |
| |
| pos->width = layout_get_char_width (layout); |
| |
| if (rtl) |
| pos->x -= pos->width - 1; |
| |
| if (at_line_end) |
| *at_line_end = TRUE; |
| |
| return pos->width != 0; |
| } |