X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextutil.c;h=a2811f2d3cb8ed0e5ade1a51fa41e103d33eb381;hb=fa4878979e0a72890ca577a210ccd7cf6291dbf0;hp=7552a8cb8c84ed3f82fc23918eb291d3df110656;hpb=94eec042676a8e18cebc9af8f27cd251355f4ba4;p=~andy%2Fgtk diff --git a/gtk/gtktextutil.c b/gtk/gtktextutil.c index 7552a8cb8..a2811f2d3 100644 --- a/gtk/gtktextutil.c +++ b/gtk/gtktextutil.c @@ -12,9 +12,7 @@ * 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, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -24,14 +22,21 @@ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ -#include +#include "config.h" + +#include "gtktextview.h" #include "gtktextutil.h" -#include "gtkintl.h" + +#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API + +#include "gtktextdisplay.h" +#include "gtktextbuffer.h" #include "gtkmenuitem.h" -#include "gtkalias.h" +#include "gtkintl.h" #define DRAG_ICON_MAX_WIDTH 250 -#define DRAG_ICON_LAYOUT_BORDER 2 +#define DRAG_ICON_MAX_HEIGHT 250 +#define DRAG_ICON_LAYOUT_BORDER 5 #define DRAG_ICON_MAX_LINES 7 #define ELLIPSIS_CHARACTER "\xe2\x80\xa6" @@ -62,6 +67,26 @@ static const GtkUnicodeMenuEntry bidi_menu_entries[] = { { N_("ZWNJ Zero width _non-joiner"), 0x200C } }; +static GtkTextUtilCallbackInfo * +callback_info_new (GtkTextUtilCharChosenFunc func, + gpointer data) +{ + GtkTextUtilCallbackInfo *info; + + info = g_slice_new (GtkTextUtilCallbackInfo); + + info->func = func; + info->data = data; + + return info; +} + +static void +callback_info_free (GtkTextUtilCallbackInfo *info) +{ + g_slice_free (GtkTextUtilCallbackInfo, info); +} + static void activate_cb (GtkWidget *menu_item, gpointer data) @@ -77,7 +102,7 @@ activate_cb (GtkWidget *menu_item, (* info->func) (buf, info->data); } -/** +/* * _gtk_text_util_append_special_char_menuitems * @menushell: a #GtkMenuShell * @callback: call this when an item is chosen @@ -91,32 +116,29 @@ activate_cb (GtkWidget *menu_item, * become public sometime, but it probably needs more thought first. * e.g. maybe there should be a way to just get the list of items, * instead of requiring the menu items to be created. - **/ + */ void _gtk_text_util_append_special_char_menuitems (GtkMenuShell *menushell, GtkTextUtilCharChosenFunc func, gpointer data) { int i; - + for (i = 0; i < G_N_ELEMENTS (bidi_menu_entries); i++) { GtkWidget *menuitem; GtkTextUtilCallbackInfo *info; - /* wasteful to have a bunch of copies, but simplifies mem management */ - info = g_new (GtkTextUtilCallbackInfo, 1); - info->func = func; - info->data = data; - + info = callback_info_new (func, data); + menuitem = gtk_menu_item_new_with_mnemonic (_(bidi_menu_entries[i].label)); g_object_set_data (G_OBJECT (menuitem), I_("gtk-unicode-menu-entry"), (gpointer)&bidi_menu_entries[i]); - + g_signal_connect_data (menuitem, "activate", G_CALLBACK (activate_cb), - info, (GClosureNotify) g_free, 0); - + info, (GClosureNotify) callback_info_free, 0); + gtk_widget_show (menuitem); gtk_menu_shell_append (menushell, menuitem); } @@ -150,7 +172,7 @@ limit_layout_lines (PangoLayout *layout) { text = pango_layout_get_text (layout); str = g_string_new (NULL); - lines = pango_layout_get_lines (layout); + lines = pango_layout_get_lines_readonly (layout); /* get first lines */ elem = lines; @@ -169,25 +191,30 @@ limit_layout_lines (PangoLayout *layout) } } -/** +/* * _gtk_text_util_create_drag_icon * @widget: #GtkWidget to extract the pango context * @text: a #gchar to render the icon * @len: length of @text, or -1 for NUL-terminated text * * Creates a drag and drop icon from @text. - **/ -GdkPixmap* + * + * Returns: a #cairo_surface_t to use as DND icon + */ +cairo_surface_t * _gtk_text_util_create_drag_icon (GtkWidget *widget, gchar *text, gsize len) { - GdkDrawable *drawable = NULL; + GtkStyleContext *style_context; + GtkStateFlags state; + cairo_surface_t *surface; PangoContext *context; PangoLayout *layout; + cairo_t *cr; gint pixmap_height, pixmap_width; gint layout_width, layout_height; - gint n_lines; + GdkRGBA color; g_return_val_if_fail (widget != NULL, NULL); g_return_val_if_fail (text != NULL, NULL); @@ -201,7 +228,6 @@ _gtk_text_util_create_drag_icon (GtkWidget *widget, layout_width = MIN (layout_width, DRAG_ICON_MAX_WIDTH * PANGO_SCALE); pango_layout_set_width (layout, layout_width); - n_lines = pango_layout_get_line_count (layout); limit_layout_lines (layout); @@ -211,32 +237,325 @@ _gtk_text_util_create_drag_icon (GtkWidget *widget, pixmap_width = layout_width / PANGO_SCALE + DRAG_ICON_LAYOUT_BORDER * 2; pixmap_height = layout_height / PANGO_SCALE + DRAG_ICON_LAYOUT_BORDER * 2; - drawable = gdk_pixmap_new (widget->window, - pixmap_width + 2, - pixmap_height + 2, - -1); - - gdk_draw_rectangle (drawable, - widget->style->base_gc [GTK_WIDGET_STATE (widget)], - TRUE, - 0, 0, - pixmap_width + 1, - pixmap_height + 1); - - gdk_draw_layout (drawable, - widget->style->text_gc [GTK_WIDGET_STATE (widget)], - 1 + DRAG_ICON_LAYOUT_BORDER, - 1 + DRAG_ICON_LAYOUT_BORDER, - layout); - - gdk_draw_rectangle (drawable, - widget->style->black_gc, - FALSE, - 0, 0, - pixmap_width + 1, - pixmap_height + 1); + style_context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR, + pixmap_width + 2, + pixmap_height + 2); + cr = cairo_create (surface); + + gtk_style_context_save (style_context); + gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW); + + gtk_style_context_get_background_color (style_context, state, &color); + gdk_cairo_set_source_rgba (cr, &color); + cairo_paint (cr); + gtk_style_context_get_color (style_context, state, &color); + gdk_cairo_set_source_rgba (cr, &color); + cairo_move_to (cr, 1 + DRAG_ICON_LAYOUT_BORDER, 1 + DRAG_ICON_LAYOUT_BORDER); + pango_cairo_show_layout (cr, layout); + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_rectangle (cr, 0.5, 0.5, pixmap_width + 1, pixmap_height + 1); + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + + cairo_destroy (cr); g_object_unref (layout); - return drawable; + cairo_surface_set_device_offset (surface, 2, 2); + + gtk_style_context_restore (style_context); + + return surface; +} + +static void +gtk_text_view_set_attributes_from_style (GtkTextView *text_view, + GtkTextAttributes *values) +{ + GtkStyleContext *context; + GdkRGBA bg_color, fg_color; + GtkStateFlags state; + + context = gtk_widget_get_style_context (GTK_WIDGET (text_view)); + state = gtk_widget_get_state_flags (GTK_WIDGET (text_view)); + + gtk_style_context_get_background_color (context, state, &bg_color); + gtk_style_context_get_color (context, state, &fg_color); + + values->appearance.bg_color.red = CLAMP (bg_color.red * 65535. + 0.5, 0, 65535); + values->appearance.bg_color.green = CLAMP (bg_color.green * 65535. + 0.5, 0, 65535); + values->appearance.bg_color.blue = CLAMP (bg_color.blue * 65535. + 0.5, 0, 65535); + + values->appearance.fg_color.red = CLAMP (fg_color.red * 65535. + 0.5, 0, 65535); + values->appearance.fg_color.green = CLAMP (fg_color.green * 65535. + 0.5, 0, 65535); + values->appearance.fg_color.blue = CLAMP (fg_color.blue * 65535. + 0.5, 0, 65535); + + if (values->font) + pango_font_description_free (values->font); + + gtk_style_context_get (context, state, "font", &values->font, NULL); +} + +cairo_surface_t * +_gtk_text_util_create_rich_drag_icon (GtkWidget *widget, + GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GtkAllocation allocation; + cairo_surface_t *surface; + gint pixmap_height, pixmap_width; + gint layout_width, layout_height; + GtkStyleContext *context; + GtkStateFlags state; + GdkRGBA color; + GtkTextBuffer *new_buffer; + GtkTextLayout *layout; + GtkTextAttributes *style; + PangoContext *ltr_context, *rtl_context; + GtkTextIter iter; + cairo_t *cr; + + 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); + + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + 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; + + if (GTK_IS_TEXT_VIEW (widget)) + { + gtk_text_view_set_attributes_from_style (GTK_TEXT_VIEW (widget), style); + + layout_width = layout_width + - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_LEFT) + - gtk_text_view_get_border_window_size (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_RIGHT); + } + + style->direction = gtk_widget_get_direction (widget); + style->wrap_mode = PANGO_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); + + pixmap_width = layout_width + DRAG_ICON_LAYOUT_BORDER * 2; + pixmap_height = layout_height + DRAG_ICON_LAYOUT_BORDER * 2; + + surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR, + pixmap_width + 2, + pixmap_height + 2); + + cr = cairo_create (surface); + + gtk_style_context_save (context); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW); + + gtk_style_context_get_background_color (context, state, &color); + gdk_cairo_set_source_rgba (cr, &color); + cairo_paint (cr); + + cairo_save (cr); + + cairo_translate (cr, 1 + DRAG_ICON_LAYOUT_BORDER, 1 + DRAG_ICON_LAYOUT_BORDER); + gtk_text_layout_draw (layout, widget, cr, NULL); + + cairo_restore (cr); + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_rectangle (cr, 0.5, 0.5, pixmap_width + 1, pixmap_height + 1); + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + + cairo_destroy (cr); + g_object_unref (layout); + g_object_unref (new_buffer); + + cairo_surface_set_device_offset (surface, 2, 2); + + gtk_style_context_restore (context); + + return surface; +} + + +static gint +layout_get_char_width (PangoLayout *layout) +{ + gint 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, + gint index, + PangoRectangle *pos, + gboolean *at_line_end) +{ + PangoRectangle strong_pos, weak_pos; + PangoLayoutLine *layout_line; + gboolean rtl; + gint line_no; + const gchar *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; + gint i; + gint left, right; + const gchar *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; }