/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* EText - Text item for evolution. * Copyright (C) 2000 Helix Code, Inc. * * Author: Chris Lahey * * A majority of code taken from: * * Text item type for GnomeCanvas widget * * GnomeCanvas is basically a port of the Tk toolkit's most excellent * canvas widget. Tk is copyrighted by the Regents of the University * of California, Sun Microsystems, and other parties. * * Copyright (C) 1998 The Free Software Foundation * * Author: Federico Mena */ #include #include #include #include "e-text.h" #include /* for BlackPixel */ #include #include #include #include #include "e-text-event-processor-emacs-like.h" enum { E_TEXT_RESIZE, E_TEXT_CHANGE, E_TEXT_LAST_SIGNAL }; static guint e_text_signals[E_TEXT_LAST_SIGNAL] = { 0 }; /* This defines a line of text */ struct line { char *text; /* Line's text, it is a pointer into the text->text string */ int length; /* Line's length in characters */ int width; /* Line's width in pixels */ int ellipsis_length; /* Length before adding ellipsis */ }; /* Object argument IDs */ enum { ARG_0, ARG_TEXT, ARG_X, ARG_Y, ARG_FONT, ARG_FONTSET, ARG_FONT_GDK, ARG_ANCHOR, ARG_JUSTIFICATION, ARG_CLIP_WIDTH, ARG_CLIP_HEIGHT, ARG_CLIP, ARG_X_OFFSET, ARG_Y_OFFSET, ARG_FILL_COLOR, ARG_FILL_COLOR_GDK, ARG_FILL_COLOR_RGBA, ARG_FILL_STIPPLE, ARG_TEXT_WIDTH, ARG_TEXT_HEIGHT, ARG_EDITABLE, ARG_USE_ELLIPSIS, ARG_ELLIPSIS, ARG_LINE_WRAP, ARG_MAX_LINES }; enum { E_SELECTION_PRIMARY, E_SELECTION_CLIPBOARD }; enum { TARGET_STRING, TARGET_TEXT, TARGET_COMPOUND_TEXT }; static void e_text_class_init (ETextClass *class); static void e_text_init (EText *text); static void e_text_destroy (GtkObject *object); static void e_text_set_arg (GtkObject *object, GtkArg *arg, guint arg_id); static void e_text_get_arg (GtkObject *object, GtkArg *arg, guint arg_id); static void e_text_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); static void e_text_realize (GnomeCanvasItem *item); static void e_text_unrealize (GnomeCanvasItem *item); static void e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height); static double e_text_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item); static void e_text_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2); static void e_text_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); static gint e_text_event (GnomeCanvasItem *item, GdkEvent *event); static void e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data); static guint32 e_text_get_event_time (EText *text); static void e_text_get_selection(EText *text, GdkAtom selection, guint32 time); static void e_text_supply_selection (EText *text, guint time, GdkAtom selection, guchar *data, gint length); static GtkWidget *e_text_get_invisible(EText *text); static void _selection_clear_event (GtkInvisible *invisible, GdkEventSelection *event, EText *text); static void _selection_get (GtkInvisible *invisible, GtkSelectionData *selection_data, guint info, guint time_stamp, EText *text); static void _selection_received (GtkInvisible *invisible, GtkSelectionData *selection_data, guint time, EText *text); static ETextSuckFont *e_suck_font (GdkFont *font); static void e_suck_font_free (ETextSuckFont *suckfont); static GnomeCanvasItemClass *parent_class; static GdkAtom clipboard_atom = GDK_NONE; /** * e_text_get_type: * @void: * * Registers the &EText class if necessary, and returns the type ID * associated to it. * * Return value: The type ID of the &EText class. **/ GtkType e_text_get_type (void) { static GtkType text_type = 0; if (!text_type) { GtkTypeInfo text_info = { "EText", sizeof (EText), sizeof (ETextClass), (GtkClassInitFunc) e_text_class_init, (GtkObjectInitFunc) e_text_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; text_type = gtk_type_unique (gnome_canvas_item_get_type (), &text_info); } return text_type; } /* Class initialization function for the text item */ static void e_text_class_init (ETextClass *klass) { GtkObjectClass *object_class; GnomeCanvasItemClass *item_class; object_class = (GtkObjectClass *) klass; item_class = (GnomeCanvasItemClass *) klass; parent_class = gtk_type_class (gnome_canvas_item_get_type ()); e_text_signals[E_TEXT_RESIZE] = gtk_signal_new ("resize", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (ETextClass, resize), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); e_text_signals[E_TEXT_CHANGE] = gtk_signal_new ("change", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (ETextClass, change), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, e_text_signals, E_TEXT_LAST_SIGNAL); gtk_object_add_arg_type ("EText::text", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_TEXT); gtk_object_add_arg_type ("EText::x", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X); gtk_object_add_arg_type ("EText::y", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y); gtk_object_add_arg_type ("EText::font", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_FONT); gtk_object_add_arg_type ("EText::fontset", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_FONTSET); gtk_object_add_arg_type ("EText::font_gdk", GTK_TYPE_GDK_FONT, GTK_ARG_READWRITE, ARG_FONT_GDK); gtk_object_add_arg_type ("EText::anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_ANCHOR); gtk_object_add_arg_type ("EText::justification", GTK_TYPE_JUSTIFICATION, GTK_ARG_READWRITE, ARG_JUSTIFICATION); gtk_object_add_arg_type ("EText::clip_width", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_CLIP_WIDTH); gtk_object_add_arg_type ("EText::clip_height", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_CLIP_HEIGHT); gtk_object_add_arg_type ("EText::clip", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_CLIP); gtk_object_add_arg_type ("EText::x_offset", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X_OFFSET); gtk_object_add_arg_type ("EText::y_offset", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y_OFFSET); gtk_object_add_arg_type ("EText::fill_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_FILL_COLOR); gtk_object_add_arg_type ("EText::fill_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_FILL_COLOR_GDK); gtk_object_add_arg_type ("EText::fill_color_rgba", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_FILL_COLOR_RGBA); gtk_object_add_arg_type ("EText::fill_stipple", GTK_TYPE_GDK_WINDOW, GTK_ARG_READWRITE, ARG_FILL_STIPPLE); gtk_object_add_arg_type ("EText::text_width", GTK_TYPE_DOUBLE, GTK_ARG_READABLE, ARG_TEXT_WIDTH); gtk_object_add_arg_type ("EText::text_height", GTK_TYPE_DOUBLE, GTK_ARG_READABLE, ARG_TEXT_HEIGHT); gtk_object_add_arg_type ("EText::editable", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_EDITABLE); gtk_object_add_arg_type ("EText::use_ellipsis", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_USE_ELLIPSIS); gtk_object_add_arg_type ("EText::ellipsis", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_ELLIPSIS); gtk_object_add_arg_type ("EText::line_wrap", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_LINE_WRAP); gtk_object_add_arg_type ("EText::max_lines", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_MAX_LINES); if (!clipboard_atom) clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE); klass->resize = NULL; klass->change = NULL; object_class->destroy = e_text_destroy; object_class->set_arg = e_text_set_arg; object_class->get_arg = e_text_get_arg; item_class->update = e_text_update; item_class->realize = e_text_realize; item_class->unrealize = e_text_unrealize; item_class->draw = e_text_draw; item_class->point = e_text_point; item_class->bounds = e_text_bounds; item_class->render = e_text_render; item_class->event = e_text_event; } /* Object initialization function for the text item */ static void e_text_init (EText *text) { text->x = 0.0; text->y = 0.0; text->anchor = GTK_ANCHOR_CENTER; text->justification = GTK_JUSTIFY_LEFT; text->clip_width = 0.0; text->clip_height = 0.0; text->xofs = 0.0; text->yofs = 0.0; text->ellipsis = NULL; text->use_ellipsis = FALSE; text->ellipsis_width = 0; text->editable = FALSE; text->editing = FALSE; text->xofs_edit = 0; text->selection_start = 0; text->selection_end = 0; text->select_by_word = FALSE; text->timeout_id = 0; text->timer = NULL; text->lastx = 0; text->lasty = 0; text->last_state = 0; text->scroll_start = 0; text->show_cursor = TRUE; text->button_down = FALSE; text->tep = NULL; text->has_selection = FALSE; text->invisible = NULL; text->primary_selection = NULL; text->primary_length = 0; text->clipboard_selection = NULL; text->clipboard_length = 0; text->pointer_in = FALSE; text->default_cursor_shown = TRUE; text->line_wrap = FALSE; text->max_lines = -1; } /* Destroy handler for the text item */ static void e_text_destroy (GtkObject *object) { EText *text; g_return_if_fail (object != NULL); g_return_if_fail (GNOME_IS_CANVAS_TEXT (object)); text = E_TEXT (object); if (text->text) g_free (text->text); if (text->tep) gtk_object_unref (GTK_OBJECT(text->tep)); if (text->invisible) gtk_object_unref (GTK_OBJECT(text->invisible)); if (text->lines) g_free (text->lines); if (text->font) gdk_font_unref (text->font); if (text->suckfont) e_suck_font_free (text->suckfont); if (text->stipple) gdk_bitmap_unref (text->stipple); if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void get_bounds_item_relative (EText *text, double *px1, double *py1, double *px2, double *py2) { GnomeCanvasItem *item; double x, y; double clip_x, clip_y; int old_height; item = GNOME_CANVAS_ITEM (text); x = text->x; y = text->y; clip_x = x; clip_y = y; /* Calculate text dimensions */ old_height = text->height; if (text->text && text->font) text->height = (text->font->ascent + text->font->descent) * text->num_lines; else text->height = 0; if (old_height != text->height) gtk_signal_emit_by_name (GTK_OBJECT (text), "resize"); /* Anchor text */ switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_W: case GTK_ANCHOR_SW: break; case GTK_ANCHOR_N: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_S: x -= text->max_width / 2; clip_x -= text->clip_width / 2; break; case GTK_ANCHOR_NE: case GTK_ANCHOR_E: case GTK_ANCHOR_SE: x -= text->max_width; clip_x -= text->clip_width; break; } switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_N: case GTK_ANCHOR_NE: break; case GTK_ANCHOR_W: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_E: y -= text->height / 2; clip_y -= text->clip_height / 2; break; case GTK_ANCHOR_SW: case GTK_ANCHOR_S: case GTK_ANCHOR_SE: y -= text->height; clip_y -= text->clip_height; break; } /* Bounds */ if (text->clip) { /* maybe do bbox intersection here? */ *px1 = clip_x; *py1 = clip_y; *px2 = clip_x + text->clip_width; *py2 = clip_y + text->clip_height; } else { *px1 = x; *py1 = y; *px2 = x + text->max_width; *py2 = y + text->height; } } static void get_bounds (EText *text, double *px1, double *py1, double *px2, double *py2) { GnomeCanvasItem *item; double wx, wy; int old_height; item = GNOME_CANVAS_ITEM (text); /* Get canvas pixel coordinates for text position */ wx = text->x; wy = text->y; gnome_canvas_item_i2w (item, &wx, &wy); gnome_canvas_w2c (item->canvas, wx + text->xofs, wy + text->yofs, &text->cx, &text->cy); /* Get canvas pixel coordinates for clip rectangle position */ gnome_canvas_w2c (item->canvas, wx, wy, &text->clip_cx, &text->clip_cy); text->clip_cwidth = text->clip_width * item->canvas->pixels_per_unit; text->clip_cheight = text->clip_height * item->canvas->pixels_per_unit; /* Calculate text dimensions */ old_height = text->height; if (text->text && text->font) text->height = (text->font->ascent + text->font->descent) * text->num_lines; else text->height = 0; if (old_height != text->height) gtk_signal_emit_by_name (GTK_OBJECT (text), "resize"); /* Anchor text */ switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_W: case GTK_ANCHOR_SW: break; case GTK_ANCHOR_N: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_S: text->cx -= text->max_width / 2; text->clip_cx -= text->clip_cwidth / 2; break; case GTK_ANCHOR_NE: case GTK_ANCHOR_E: case GTK_ANCHOR_SE: text->cx -= text->max_width; text->clip_cx -= text->clip_cwidth; break; } switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_N: case GTK_ANCHOR_NE: break; case GTK_ANCHOR_W: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_E: text->cy -= text->height / 2; text->clip_cy -= text->clip_cheight / 2; break; case GTK_ANCHOR_SW: case GTK_ANCHOR_S: case GTK_ANCHOR_SE: text->cy -= text->height; text->clip_cy -= text->clip_cheight; break; } /* Bounds */ if (text->clip) { *px1 = text->clip_cx; *py1 = text->clip_cy; *px2 = text->clip_cx + text->clip_cwidth; *py2 = text->clip_cy + text->clip_cheight; } else { *px1 = text->cx; *py1 = text->cy; *px2 = text->cx + text->max_width; *py2 = text->cy + text->height; } } /* Recalculates the bounding box of the text item. The bounding box is defined * by the text's extents if the clip rectangle is disabled. If it is enabled, * the bounding box is defined by the clip rectangle itself. */ static void recalc_bounds (EText *text) { GnomeCanvasItem *item; item = GNOME_CANVAS_ITEM (text); get_bounds (text, &item->x1, &item->y1, &item->x2, &item->y2); gnome_canvas_group_child_bounds (GNOME_CANVAS_GROUP (item->parent), item); } static void calc_ellipsis (EText *text) { if (text->font) text->ellipsis_width = gdk_text_width (text->font, text->ellipsis ? text->ellipsis : "...", text->ellipsis ? strlen (text->ellipsis) : 3); } /* Calculates the line widths (in pixels) of the text's splitted lines */ static void calc_line_widths (EText *text) { struct line *lines; int i; int j; lines = text->lines; text->max_width = 0; if (!lines) return; for (i = 0; i < text->num_lines; i++) { if (lines->length != 0) { if (text->font) { lines->width = gdk_text_width (text->font, lines->text, lines->length); lines->ellipsis_length = 0; } else { lines->width = 0; } if (text->clip && text->use_ellipsis && ! text->editing && lines->width > text->clip_width) { if (text->font) { lines->ellipsis_length = 0; for (j = 0; j < lines->length; j++ ) { if (gdk_text_width (text->font, lines->text, j) + text->ellipsis_width <= text->clip_width) lines->ellipsis_length = j; else break; } } else lines->ellipsis_length = 0; lines->width = gdk_text_width (text->font, lines->text, lines->ellipsis_length) + text->ellipsis_width; } else lines->ellipsis_length = lines->length; if (lines->width > text->max_width) text->max_width = lines->width; } lines++; } } /* Splits the text of the text item into lines */ static void split_into_lines (EText *text) { char *p; struct line *lines; int len; int line_num; char *laststart; char *lastend; char *linestart; /* Free old array of lines */ if (text->lines) g_free (text->lines); text->lines = NULL; text->num_lines = 0; if (!text->text) return; /* First, count the number of lines */ lastend = text->text; laststart = text->text; linestart = text->text; for (p = text->text; *p; p++) { if (text->line_wrap && (*p == ' ' || *p == '\n')) { if ( laststart != lastend && gdk_text_width(text->font, linestart, p - linestart) > text->clip_width ) { text->num_lines ++; linestart = laststart; laststart = p + 1; lastend = p; } else if (*p == ' ') { laststart = p + 1; lastend = p; } } if (*p == '\n') { text->num_lines ++; lastend = p + 1; laststart = p + 1; linestart = p + 1; } } if (text->line_wrap) { if ( laststart != lastend && gdk_text_width(text->font, linestart, p - linestart) > text->clip_width ) { text->num_lines ++; } } text->num_lines++; if ( (!text->editing) && text->max_lines != -1 && text->num_lines > text->max_lines ) { text->num_lines = text->max_lines; } /* Allocate array of lines and calculate split positions */ text->lines = lines = g_new0 (struct line, text->num_lines); len = 0; line_num = 1; lastend = text->text; laststart = text->text; for (p = text->text; line_num < text->num_lines && *p; p++) { gboolean handled = FALSE; if (len == 0) lines->text = p; if (text->line_wrap && (*p == ' ' || *p == '\n')) { if ( gdk_text_width(text->font, lines->text, p - lines->text) > text->clip_width && laststart != lastend ) { lines->length = lastend - lines->text; lines++; line_num ++; len = p - laststart; lines->text = laststart; laststart = p + 1; lastend = p; } else if (*p == ' ') { laststart = p + 1; lastend = p; len ++; } handled = TRUE; } if ( line_num >= text->num_lines ) break; if (*p == '\n') { lines->length = p - lines->text; lines++; line_num ++; len = 0; lastend = p + 1; laststart = p + 1; handled = TRUE; } if (!handled) len++; } if ( line_num < text->num_lines && text->line_wrap ) { if ( gdk_text_width(text->font, lines->text, p - lines->text) > text->clip_width && laststart != lastend ) { lines->length = lastend - lines->text; lines++; line_num ++; len = p - laststart; lines->text = laststart; laststart = p + 1; lastend = p; } } if (len == 0) lines->text = p; lines->length = strlen(lines->text); calc_line_widths (text); } /* Convenience function to set the text's GC's foreground color */ static void set_text_gc_foreground (EText *text) { GdkColor c; if (!text->gc) return; c.pixel = text->pixel; gdk_gc_set_foreground (text->gc, &c); } /* Sets the stipple pattern for the text */ static void set_stipple (EText *text, GdkBitmap *stipple, int reconfigure) { if (text->stipple && !reconfigure) gdk_bitmap_unref (text->stipple); text->stipple = stipple; if (stipple && !reconfigure) gdk_bitmap_ref (stipple); if (text->gc) { if (stipple) { gdk_gc_set_stipple (text->gc, stipple); gdk_gc_set_fill (text->gc, GDK_STIPPLED); } else gdk_gc_set_fill (text->gc, GDK_SOLID); } } /* Set_arg handler for the text item */ static void e_text_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) { GnomeCanvasItem *item; EText *text; GdkColor color = { 0, 0, 0, 0, }; GdkColor *pcolor; gboolean color_changed; int have_pixel; item = GNOME_CANVAS_ITEM (object); text = E_TEXT (object); color_changed = FALSE; have_pixel = FALSE; switch (arg_id) { case ARG_TEXT: if (text->text) g_free (text->text); text->text = g_strdup (GTK_VALUE_STRING (*arg)); split_into_lines (text); recalc_bounds (text); break; case ARG_X: text->x = GTK_VALUE_DOUBLE (*arg); recalc_bounds (text); break; case ARG_Y: text->y = GTK_VALUE_DOUBLE (*arg); recalc_bounds (text); break; case ARG_FONT: if (text->font) gdk_font_unref (text->font); text->font = gdk_font_load (GTK_VALUE_STRING (*arg)); if (item->canvas->aa) { if (text->suckfont) e_suck_font_free (text->suckfont); text->suckfont = e_suck_font (text->font); } calc_ellipsis (text); if ( text->line_wrap ) split_into_lines (text); else calc_line_widths (text); recalc_bounds (text); break; case ARG_FONTSET: if (text->font) gdk_font_unref (text->font); text->font = gdk_fontset_load (GTK_VALUE_STRING (*arg)); if (item->canvas->aa) { if (text->suckfont) e_suck_font_free (text->suckfont); text->suckfont = e_suck_font (text->font); } calc_ellipsis (text); if ( text->line_wrap ) split_into_lines (text); else calc_line_widths (text); recalc_bounds (text); break; case ARG_FONT_GDK: if (text->font) gdk_font_unref (text->font); text->font = GTK_VALUE_BOXED (*arg); gdk_font_ref (text->font); if (item->canvas->aa) { if (text->suckfont) e_suck_font_free (text->suckfont); text->suckfont = e_suck_font (text->font); } calc_ellipsis (text); if ( text->line_wrap ) split_into_lines (text); else calc_line_widths (text); recalc_bounds (text); break; case ARG_ANCHOR: text->anchor = GTK_VALUE_ENUM (*arg); recalc_bounds (text); break; case ARG_JUSTIFICATION: text->justification = GTK_VALUE_ENUM (*arg); break; case ARG_CLIP_WIDTH: text->clip_width = fabs (GTK_VALUE_DOUBLE (*arg)); calc_ellipsis (text); if ( text->line_wrap ) split_into_lines (text); else calc_line_widths (text); recalc_bounds (text); break; case ARG_CLIP_HEIGHT: text->clip_height = fabs (GTK_VALUE_DOUBLE (*arg)); recalc_bounds (text); break; case ARG_CLIP: text->clip = GTK_VALUE_BOOL (*arg); calc_ellipsis (text); if ( text->line_wrap ) split_into_lines (text); else calc_line_widths (text); recalc_bounds (text); break; case ARG_X_OFFSET: text->xofs = GTK_VALUE_DOUBLE (*arg); recalc_bounds (text); break; case ARG_Y_OFFSET: text->yofs = GTK_VALUE_DOUBLE (*arg); recalc_bounds (text); break; case ARG_FILL_COLOR: if (GTK_VALUE_STRING (*arg)) gdk_color_parse (GTK_VALUE_STRING (*arg), &color); text->rgba = ((color.red & 0xff00) << 16 | (color.green & 0xff00) << 8 | (color.blue & 0xff00) | 0xff); color_changed = TRUE; break; case ARG_FILL_COLOR_GDK: pcolor = GTK_VALUE_BOXED (*arg); if (pcolor) { color = *pcolor; gdk_color_context_query_color (item->canvas->cc, &color); have_pixel = TRUE; } text->rgba = ((color.red & 0xff00) << 16 | (color.green & 0xff00) << 8 | (color.blue & 0xff00) | 0xff); color_changed = TRUE; break; case ARG_FILL_COLOR_RGBA: text->rgba = GTK_VALUE_UINT (*arg); color_changed = TRUE; break; case ARG_FILL_STIPPLE: set_stipple (text, GTK_VALUE_BOXED (*arg), FALSE); break; case ARG_EDITABLE: text->editable = GTK_VALUE_BOOL (*arg); break; case ARG_USE_ELLIPSIS: text->use_ellipsis = GTK_VALUE_BOOL (*arg); calc_line_widths (text); recalc_bounds (text); break; case ARG_ELLIPSIS: if (text->ellipsis) g_free (text->ellipsis); text->ellipsis = g_strdup (GTK_VALUE_STRING (*arg)); calc_ellipsis (text); calc_line_widths (text); recalc_bounds (text); break; case ARG_LINE_WRAP: text->line_wrap = GTK_VALUE_BOOL (*arg); split_into_lines (text); recalc_bounds (text); break; case ARG_MAX_LINES: text->max_lines = GTK_VALUE_INT (*arg); split_into_lines (text); recalc_bounds (text); break; default: break; } if (color_changed) { if (have_pixel) text->pixel = color.pixel; else text->pixel = gnome_canvas_get_color_pixel (item->canvas, text->rgba); if (!item->canvas->aa) set_text_gc_foreground (text); gnome_canvas_item_request_update (item); } } /* Get_arg handler for the text item */ static void e_text_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) { EText *text; GdkColor *color; text = E_TEXT (object); switch (arg_id) { case ARG_TEXT: GTK_VALUE_STRING (*arg) = g_strdup (text->text); break; case ARG_X: GTK_VALUE_DOUBLE (*arg) = text->x; break; case ARG_Y: GTK_VALUE_DOUBLE (*arg) = text->y; break; case ARG_FONT_GDK: GTK_VALUE_BOXED (*arg) = text->font; break; case ARG_ANCHOR: GTK_VALUE_ENUM (*arg) = text->anchor; break; case ARG_JUSTIFICATION: GTK_VALUE_ENUM (*arg) = text->justification; break; case ARG_CLIP_WIDTH: GTK_VALUE_DOUBLE (*arg) = text->clip_width; break; case ARG_CLIP_HEIGHT: GTK_VALUE_DOUBLE (*arg) = text->clip_height; break; case ARG_CLIP: GTK_VALUE_BOOL (*arg) = text->clip; break; case ARG_X_OFFSET: GTK_VALUE_DOUBLE (*arg) = text->xofs; break; case ARG_Y_OFFSET: GTK_VALUE_DOUBLE (*arg) = text->yofs; break; case ARG_FILL_COLOR_GDK: color = g_new (GdkColor, 1); color->pixel = text->pixel; gdk_color_context_query_color (text->item.canvas->cc, color); GTK_VALUE_BOXED (*arg) = color; break; case ARG_FILL_COLOR_RGBA: GTK_VALUE_UINT (*arg) = text->rgba; break; case ARG_FILL_STIPPLE: GTK_VALUE_BOXED (*arg) = text->stipple; break; case ARG_TEXT_WIDTH: GTK_VALUE_DOUBLE (*arg) = text->max_width / text->item.canvas->pixels_per_unit; break; case ARG_TEXT_HEIGHT: GTK_VALUE_DOUBLE (*arg) = text->height / text->item.canvas->pixels_per_unit; break; case ARG_EDITABLE: GTK_VALUE_BOOL (*arg) = text->editable; break; case ARG_USE_ELLIPSIS: GTK_VALUE_BOOL (*arg) = text->use_ellipsis; break; case ARG_ELLIPSIS: GTK_VALUE_STRING (*arg) = g_strdup (text->ellipsis); break; case ARG_LINE_WRAP: GTK_VALUE_BOOL (*arg) = text->line_wrap; break; case ARG_MAX_LINES: GTK_VALUE_INT (*arg) = text->max_lines; break; default: arg->type = GTK_TYPE_INVALID; break; } } /* Update handler for the text item */ static void e_text_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) { EText *text; double x1, y1, x2, y2; ArtDRect i_bbox, c_bbox; int i; text = E_TEXT (item); if (parent_class->update) (* parent_class->update) (item, affine, clip_path, flags); if (!item->canvas->aa) { set_text_gc_foreground (text); set_stipple (text, text->stipple, TRUE); get_bounds (text, &x1, &y1, &x2, &y2); gnome_canvas_update_bbox (item, x1, y1, x2, y2); } else { /* aa rendering */ for (i = 0; i < 6; i++) text->affine[i] = affine[i]; get_bounds_item_relative (text, &i_bbox.x0, &i_bbox.y0, &i_bbox.x1, &i_bbox.y1); art_drect_affine_transform (&c_bbox, &i_bbox, affine); gnome_canvas_update_bbox (item, c_bbox.x0, c_bbox.y0, c_bbox.x1, c_bbox.y1); } } /* Realize handler for the text item */ static void e_text_realize (GnomeCanvasItem *item) { EText *text; text = E_TEXT (item); if (parent_class->realize) (* parent_class->realize) (item); text->gc = gdk_gc_new (item->canvas->layout.bin_window); text->i_cursor = gdk_cursor_new (GDK_XTERM); text->default_cursor = gdk_cursor_new (GDK_LEFT_PTR); } /* Unrealize handler for the text item */ static void e_text_unrealize (GnomeCanvasItem *item) { EText *text; text = E_TEXT (item); gdk_gc_unref (text->gc); text->gc = NULL; gdk_cursor_destroy (text->i_cursor); gdk_cursor_destroy (text->default_cursor); if (parent_class->unrealize) (* parent_class->unrealize) (item); } /* Calculates the x position of the specified line of text, based on the text's justification */ static double get_line_xpos_item_relative (EText *text, struct line *line) { double x; x = text->x; switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_W: case GTK_ANCHOR_SW: break; case GTK_ANCHOR_N: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_S: x -= text->max_width / 2; break; case GTK_ANCHOR_NE: case GTK_ANCHOR_E: case GTK_ANCHOR_SE: x -= text->max_width; break; } switch (text->justification) { case GTK_JUSTIFY_RIGHT: x += text->max_width - line->width; break; case GTK_JUSTIFY_CENTER: x += (text->max_width - line->width) * 0.5; break; default: /* For GTK_JUSTIFY_LEFT, we don't have to do anything. We do not support * GTK_JUSTIFY_FILL, yet. */ break; } return x; } /* Calculates the y position of the first line of text. */ static double get_line_ypos_item_relative (EText *text) { double y; y = text->y; switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_N: case GTK_ANCHOR_NE: break; case GTK_ANCHOR_W: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_E: y -= text->height / 2; break; case GTK_ANCHOR_SW: case GTK_ANCHOR_S: case GTK_ANCHOR_SE: y -= text->height; break; } return y; } /* Calculates the x position of the specified line of text, based on the text's justification */ static int get_line_xpos (EText *text, struct line *line) { int x; x = text->cx; switch (text->justification) { case GTK_JUSTIFY_RIGHT: x += text->max_width - line->width; break; case GTK_JUSTIFY_CENTER: x += (text->max_width - line->width) / 2; break; default: /* For GTK_JUSTIFY_LEFT, we don't have to do anything. We do not support * GTK_JUSTIFY_FILL, yet. */ break; } return x; } static void _get_tep(EText *text) { if (!text->tep) { text->tep = e_text_event_processor_emacs_like_new(); gtk_object_ref (GTK_OBJECT (text->tep)); gtk_object_sink (GTK_OBJECT (text->tep)); gtk_signal_connect(GTK_OBJECT(text->tep), "command", GTK_SIGNAL_FUNC(e_text_command), (gpointer) text); } } /* Draw handler for the text item */ static void e_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) { EText *text; GdkRectangle rect, *clip_rect; struct line *lines; int i; int xpos, ypos; int start_char, end_char; int sel_start, sel_end; GdkRectangle sel_rect; GdkGC *fg_gc; GnomeCanvas *canvas; text = E_TEXT (item); canvas = GNOME_CANVAS_ITEM(text)->canvas; fg_gc = GTK_WIDGET(canvas)->style->fg_gc[text->has_selection ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE]; if (!text->text || !text->font) return; clip_rect = NULL; if (text->clip) { rect.x = text->clip_cx - x; rect.y = text->clip_cy - y; rect.width = text->clip_cwidth; rect.height = text->clip_cheight; gdk_gc_set_clip_rectangle (text->gc, &rect); gdk_gc_set_clip_rectangle (fg_gc, &rect); clip_rect = ▭ } lines = text->lines; ypos = text->cy + text->font->ascent; if (text->stipple) gnome_canvas_set_stipple_origin (item->canvas, text->gc); for (i = 0; i < text->num_lines; i++) { xpos = get_line_xpos (text, lines); if (text->editing) { xpos -= text->xofs_edit; start_char = lines->text - text->text; end_char = start_char + lines->length; sel_start = text->selection_start; sel_end = text->selection_end; if (sel_start > sel_end ) { sel_start ^= sel_end; sel_end ^= sel_start; sel_start ^= sel_end; } if ( sel_start < start_char ) sel_start = start_char; if ( sel_end > end_char ) sel_end = end_char; if ( sel_start < sel_end ) { sel_rect.x = xpos - x + gdk_text_width (text->font, lines->text, sel_start - start_char); sel_rect.y = ypos - y - text->font->ascent; sel_rect.width = gdk_text_width (text->font, lines->text + sel_start - start_char, sel_end - sel_start); sel_rect.height = text->font->ascent + text->font->descent; gtk_paint_flat_box(GTK_WIDGET(item->canvas)->style, drawable, text->has_selection ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE, GTK_SHADOW_NONE, clip_rect, GTK_WIDGET(item->canvas), "text", sel_rect.x, sel_rect.y, sel_rect.width, sel_rect.height); gdk_draw_text (drawable, text->font, text->gc, xpos - x, ypos - y, lines->text, sel_start - start_char); gdk_draw_text (drawable, text->font, fg_gc, xpos - x + gdk_text_width (text->font, lines->text, sel_start - start_char), ypos - y, lines->text + sel_start - start_char, sel_end - sel_start); gdk_draw_text (drawable, text->font, text->gc, xpos - x + gdk_text_width (text->font, lines->text, sel_end - start_char), ypos - y, lines->text + sel_end - start_char, end_char - sel_end); } else { gdk_draw_text (drawable, text->font, text->gc, xpos - x, ypos - y, lines->text, lines->length); } if (text->selection_start == text->selection_end && text->selection_start >= start_char && text->selection_start <= end_char && text->show_cursor) { gdk_draw_rectangle (drawable, text->gc, TRUE, xpos - x + gdk_text_width (text->font, lines->text, sel_start - start_char), ypos - y - text->font->ascent, 1, text->font->ascent + text->font->descent); } } else { if ( text->clip && text->use_ellipsis && lines->ellipsis_length < lines->length) { gdk_draw_text (drawable, text->font, text->gc, xpos - x, ypos - y, lines->text, lines->ellipsis_length); gdk_draw_text (drawable, text->font, text->gc, xpos - x + lines->width - text->ellipsis_width, ypos - y, text->ellipsis ? text->ellipsis : "...", text->ellipsis ? strlen (text->ellipsis) : 3); } else gdk_draw_text (drawable, text->font, text->gc, xpos - x, ypos - y, lines->text, lines->length); } ypos += text->font->ascent + text->font->descent; lines++; } if (text->clip) { gdk_gc_set_clip_rectangle (text->gc, NULL); gdk_gc_set_clip_rectangle (fg_gc, NULL); } } /* Render handler for the text item */ static void e_text_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) { EText *text; guint32 fg_color; double xpos, ypos; struct line *lines; int i, j; double affine[6]; ETextSuckFont *suckfont; int dx, dy; ArtPoint start_i, start_c; text = E_TEXT (item); if (!text->text || !text->font || !text->suckfont) return; suckfont = text->suckfont; fg_color = text->rgba; gnome_canvas_buf_ensure_buf (buf); lines = text->lines; start_i.y = get_line_ypos_item_relative (text); art_affine_scale (affine, item->canvas->pixels_per_unit, item->canvas->pixels_per_unit); for (i = 0; i < 6; i++) affine[i] = text->affine[i]; for (i = 0; i < text->num_lines; i++) { if (lines->length != 0) { start_i.x = get_line_xpos_item_relative (text, lines); art_affine_point (&start_c, &start_i, text->affine); xpos = start_c.x; ypos = start_c.y; for (j = 0; j < lines->length; j++) { ETextSuckChar *ch; ch = &suckfont->chars[(unsigned char)((lines->text)[j])]; affine[4] = xpos; affine[5] = ypos; art_rgb_bitmap_affine ( buf->buf, buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1, buf->buf_rowstride, suckfont->bitmap + (ch->bitmap_offset >> 3), ch->width, suckfont->bitmap_height, suckfont->bitmap_width >> 3, fg_color, affine, ART_FILTER_NEAREST, NULL); dx = ch->left_sb + ch->width + ch->right_sb; xpos += dx * affine[0]; ypos += dx * affine[1]; } } dy = text->font->ascent + text->font->descent; start_i.y += dy; lines++; } buf->is_bg = 0; } /* Point handler for the text item */ static double e_text_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) { EText *text; int i; struct line *lines; int x1, y1, x2, y2; int font_height; int dx, dy; double dist, best; text = E_TEXT (item); *actual_item = item; /* The idea is to build bounding rectangles for each of the lines of * text (clipped by the clipping rectangle, if it is activated) and see * whether the point is inside any of these. If it is, we are done. * Otherwise, calculate the distance to the nearest rectangle. */ if (text->font) font_height = text->font->ascent + text->font->descent; else font_height = 0; best = 1.0e36; lines = text->lines; for (i = 0; i < text->num_lines; i++) { /* Compute the coordinates of rectangle for the current line, * clipping if appropriate. */ x1 = get_line_xpos (text, lines); y1 = text->cy + i * font_height; x2 = x1 + lines->width; y2 = y1 + font_height; if (text->clip) { if (x1 < text->clip_cx) x1 = text->clip_cx; if (y1 < text->clip_cy) y1 = text->clip_cy; if (x2 > (text->clip_cx + text->clip_width)) x2 = text->clip_cx + text->clip_width; if (y2 > (text->clip_cy + text->clip_height)) y2 = text->clip_cy + text->clip_height; if ((x1 >= x2) || (y1 >= y2)) continue; } /* Calculate distance from point to rectangle */ if (cx < x1) dx = x1 - cx; else if (cx >= x2) dx = cx - x2 + 1; else dx = 0; if (cy < y1) dy = y1 - cy; else if (cy >= y2) dy = cy - y2 + 1; else dy = 0; if ((dx == 0) && (dy == 0)) return 0.0; dist = sqrt (dx * dx + dy * dy); if (dist < best) best = dist; /* Next! */ lines++; } return best / item->canvas->pixels_per_unit; } /* Bounds handler for the text item */ static void e_text_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) { EText *text; double width, height; text = E_TEXT (item); *x1 = text->x; *y1 = text->y; if (text->clip) { width = text->clip_width; height = text->clip_height; } else { width = text->max_width / item->canvas->pixels_per_unit; height = text->height / item->canvas->pixels_per_unit; } switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_W: case GTK_ANCHOR_SW: break; case GTK_ANCHOR_N: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_S: *x1 -= width / 2.0; break; case GTK_ANCHOR_NE: case GTK_ANCHOR_E: case GTK_ANCHOR_SE: *x1 -= width; break; } switch (text->anchor) { case GTK_ANCHOR_NW: case GTK_ANCHOR_N: case GTK_ANCHOR_NE: break; case GTK_ANCHOR_W: case GTK_ANCHOR_CENTER: case GTK_ANCHOR_E: *y1 -= height / 2.0; break; case GTK_ANCHOR_SW: case GTK_ANCHOR_S: case GTK_ANCHOR_SE: *y1 -= height; break; } *x2 = *x1 + width; *y2 = *y1 + height; } static void _get_xy_from_position (EText *text, gint position, gint *xp, gint *yp) { if (xp || yp) { struct line *lines; int x, y; int j; x = get_line_xpos (text, lines); y = text->cy; for (j = 0, lines = text->lines; j < text->num_lines; lines++, j++) { if (lines->text > text->text + position) break; y += text->font->ascent + text->font->descent; } lines --; y -= text->font->descent; x += gdk_text_width (text->font, lines->text, position - (lines->text - text->text)); x -= text->xofs_edit; if (xp) *xp = x; if (yp) *yp = y; } } static gint _get_position_from_xy (EText *text, gint x, gint y) { int i, j; int ypos = text->cy; int xpos; struct line *lines; j = 0; while (y > ypos) { ypos += text->font->ascent + text->font->descent; j ++; } j--; if (j >= text->num_lines) j = text->num_lines - 1; if (j < 0) j = 0; i = 0; lines = text->lines; lines += j; x += text->xofs_edit; xpos = get_line_xpos (text, lines); for(i = 0; i < lines->length; i++) { int charwidth = gdk_text_width(text->font, lines->text + i, 1); xpos += charwidth / 2; if (xpos > x) { break; } xpos += (charwidth + 1) / 2; } return lines->text + i - text->text; } #define SCROLL_WAIT_TIME 30000 static gboolean _blink_scroll_timeout (gpointer data) { EText *text = E_TEXT(data); gulong current_time; gboolean scroll = FALSE; gboolean redraw = FALSE; g_timer_elapsed(text->timer, ¤t_time); if (text->scroll_start + SCROLL_WAIT_TIME > 1000000) { if (current_time > text->scroll_start - (1000000 - SCROLL_WAIT_TIME) && current_time < text->scroll_start) scroll = TRUE; } else { if (current_time > text->scroll_start + SCROLL_WAIT_TIME || current_time < text->scroll_start) scroll = TRUE; } if (scroll && text->button_down) { if (text->lastx - text->clip_cx > text->clip_cwidth && text->xofs_edit < text->max_width - text->clip_cwidth) { text->xofs_edit += 4; if (text->xofs_edit > text->max_width - text->clip_cwidth + 1) text->xofs_edit = text->max_width - text->clip_cwidth + 1; redraw = TRUE; } if (text->lastx - text->clip_cx < 0 && text->xofs_edit > 0) { text->xofs_edit -= 4; if (text->xofs_edit < 0) text->xofs_edit = 0; redraw = TRUE; } if (redraw) { ETextEventProcessorEvent e_tep_event; e_tep_event.type = GDK_MOTION_NOTIFY; e_tep_event.motion.state = text->last_state; e_tep_event.motion.time = 0; e_tep_event.motion.position = _get_position_from_xy(text, text->lastx, text->lasty); _get_tep(text); e_text_event_processor_handle_event (text->tep, &e_tep_event); text->scroll_start = current_time; } } if (!((current_time / 500000) % 2)) { if (!text->show_cursor) redraw = TRUE; text->show_cursor = TRUE; } else { if (text->show_cursor) redraw = TRUE; text->show_cursor = FALSE; } if (redraw) gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text)); return TRUE; } static gint e_text_event (GnomeCanvasItem *item, GdkEvent *event) { EText *text = E_TEXT(item); ETextEventProcessorEvent e_tep_event; gint return_val = 0; e_tep_event.type = event->type; switch (event->type) { case GDK_FOCUS_CHANGE: if (text->editable) { GdkEventFocus *focus_event; focus_event = (GdkEventFocus *) event; if (focus_event->in) { if(!text->editing) { text->editing = TRUE; if ( text->pointer_in ) { if ( text->default_cursor_shown ) { gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, text->i_cursor); text->default_cursor_shown = FALSE; } } text->selection_start = 0; text->selection_end = 0; text->select_by_word = FALSE; text->xofs_edit = 0; if (text->timeout_id == 0) text->timeout_id = g_timeout_add(10, _blink_scroll_timeout, text); text->timer = g_timer_new(); g_timer_elapsed(text->timer, &(text->scroll_start)); g_timer_start(text->timer); } } else { text->editing = FALSE; if ( ! text->default_cursor_shown ) { gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, text->default_cursor); text->default_cursor_shown = TRUE; } if (text->timeout_id) { g_source_remove(text->timeout_id); text->timeout_id = 0; } if (text->timer) { g_timer_stop(text->timer); g_timer_destroy(text->timer); text->timer = NULL; } } if ( text->line_wrap ) split_into_lines (text); else calc_line_widths (text); recalc_bounds (text); } return_val = 0; break; case GDK_KEY_PRESS: /* Fall Through */ case GDK_KEY_RELEASE: if (text->editing) { GdkEventKey key = event->key; e_tep_event.key.time = key.time; e_tep_event.key.state = key.state; e_tep_event.key.keyval = key.keyval; e_tep_event.key.length = key.length; e_tep_event.key.string = key.string; _get_tep(text); return e_text_event_processor_handle_event (text->tep, &e_tep_event); } else return 0; break; case GDK_BUTTON_PRESS: /* Fall Through */ case GDK_BUTTON_RELEASE: if (text->editing) { GdkEventButton button = event->button; e_tep_event.button.time = button.time; e_tep_event.button.state = button.state; e_tep_event.button.button = button.button; e_tep_event.button.position = _get_position_from_xy(text, button.x, button.y); _get_tep(text); return_val = e_text_event_processor_handle_event (text->tep, &e_tep_event); if (event->button.button == 1) { if (event->type == GDK_BUTTON_PRESS) text->button_down = TRUE; else text->button_down = FALSE; } text->lastx = button.x; text->lasty = button.y; text->last_state = button.state; } else if (text->editable && event->type == GDK_BUTTON_RELEASE && event->button.button == 1) { gnome_canvas_item_grab_focus (item); return 1; } break; case GDK_MOTION_NOTIFY: if (text->editing) { GdkEventMotion motion = event->motion; e_tep_event.motion.time = motion.time; e_tep_event.motion.state = motion.state; e_tep_event.motion.position = _get_position_from_xy(text, motion.x, motion.y); _get_tep(text); return_val = e_text_event_processor_handle_event (text->tep, &e_tep_event); text->lastx = motion.x; text->lasty = motion.y; text->last_state = motion.state; } break; case GDK_ENTER_NOTIFY: text->pointer_in = TRUE; if (text->editing) { if ( text->default_cursor_shown ) { gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, text->i_cursor); text->default_cursor_shown = FALSE; } } break; case GDK_LEAVE_NOTIFY: text->pointer_in = FALSE; if (text->editing) { if ( ! text->default_cursor_shown ) { gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, text->default_cursor); text->default_cursor_shown = TRUE; } } break; default: break; } if (return_val) return return_val; if (GNOME_CANVAS_ITEM_CLASS(parent_class)->event) return GNOME_CANVAS_ITEM_CLASS(parent_class)->event(item, event); else return 0; } static int _get_position(EText *text, ETextEventProcessorCommand *command) { int i; int length; int x, y; switch (command->position) { case E_TEP_VALUE: return command->value; case E_TEP_SELECTION: return text->selection_end; case E_TEP_START_OF_BUFFER: return 0; case E_TEP_END_OF_BUFFER: return strlen(text->text); case E_TEP_START_OF_LINE: for (i = text->selection_end - 2; i > 0; i--) if (text->text[i] == '\n') { i++; break; } return i; case E_TEP_END_OF_LINE: length = strlen(text->text); for (i = text->selection_end + 1; i < length; i++) if (text->text[i] == '\n') { break; } if (i > length) i = length; return i; case E_TEP_FORWARD_CHARACTER: length = strlen(text->text); i = text->selection_end + 1; if (i > length) i = length; return i; case E_TEP_BACKWARD_CHARACTER: i = text->selection_end - 1; if (i < 0) i = 0; return i; case E_TEP_FORWARD_WORD: length = strlen(text->text); for (i = text->selection_end + 1; i < length; i++) if (isspace(text->text[i])) { break; } if (i > length) i = length; return i; case E_TEP_BACKWARD_WORD: for (i = text->selection_end - 2; i > 0; i--) if (isspace(text->text[i])) { i++; break; } if (i < 0) i = 0; return i; case E_TEP_FORWARD_LINE: _get_xy_from_position(text, text->selection_end, &x, &y); y += text->font->ascent + text->font->descent; return _get_position_from_xy(text, x, y); case E_TEP_BACKWARD_LINE: _get_xy_from_position(text, text->selection_end, &x, &y); y -= text->font->ascent + text->font->descent; return _get_position_from_xy(text, x, y); case E_TEP_FORWARD_PARAGRAPH: case E_TEP_BACKWARD_PARAGRAPH: case E_TEP_FORWARD_PAGE: case E_TEP_BACKWARD_PAGE: return text->selection_end; default: return text->selection_end; } } static void _delete_selection(EText *text) { gint length = strlen(text->text); if (text->selection_end == text->selection_start) return; if (text->selection_end < text->selection_start) { text->selection_end ^= text->selection_start; text->selection_start ^= text->selection_end; text->selection_end ^= text->selection_start; } memmove( text->text + text->selection_start, text->text + text->selection_end, length - text->selection_end + 1 ); length -= text->selection_end - text->selection_start; text->selection_end = text->selection_start; } static void _insert(EText *text, char *string, int value) { if (value > 0) { char *temp; gint length = strlen(text->text); temp = g_new(gchar, length + value + 1); strncpy(temp, text->text, text->selection_start); strncpy(temp + text->selection_start, string, value); strcpy(temp + text->selection_start + value, text->text + text->selection_start); g_free(text->text); text->text = temp; text->selection_start += value; text->selection_end = text->selection_start; } } static void e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data) { EText *text = E_TEXT(data); int sel_start, sel_end; switch (command->action) { case E_TEP_MOVE: text->selection_start = _get_position(text, command); text->selection_end = text->selection_start; if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_SELECT: text->selection_end = _get_position(text, command); sel_start = MIN(text->selection_start, text->selection_end); sel_end = MAX(text->selection_start, text->selection_end); if (sel_start != sel_end) { e_text_supply_selection (text, command->time, GDK_SELECTION_PRIMARY, text->text + sel_start, sel_end - sel_start); } else if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_DELETE: if (text->selection_end == text->selection_start) { text->selection_end = _get_position(text, command); } _delete_selection(text); split_into_lines (text); recalc_bounds (text); if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_INSERT: if (text->selection_end != text->selection_start) { _delete_selection(text); } _insert(text, command->string, command->value); split_into_lines (text); recalc_bounds (text); if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_COPY: sel_start = MIN(text->selection_start, text->selection_end); sel_end = MAX(text->selection_start, text->selection_end); if (sel_start != sel_end) { e_text_supply_selection (text, command->time, clipboard_atom, text->text + sel_start, sel_end - sel_start); } if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_PASTE: e_text_get_selection (text, clipboard_atom, command->time); if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_GET_SELECTION: e_text_get_selection (text, GDK_SELECTION_PRIMARY, command->time); break; case E_TEP_ACTIVATE: if (text->timer) { g_timer_reset(text->timer); } break; case E_TEP_SET_SELECT_BY_WORD: text->select_by_word = command->value; break; case E_TEP_GRAB: gnome_canvas_item_grab (GNOME_CANVAS_ITEM(text), GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, text->i_cursor, command->time); break; case E_TEP_UNGRAB: gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM(text), command->time); break; case E_TEP_NOP: break; } if (!text->button_down) { int x; int i; struct line *lines = text->lines; for (lines = text->lines, i = 0; i < text->num_lines ; i++, lines ++) { if (lines->text - text->text > text->selection_end) { break; } } lines --; x = gdk_text_width(text->font, lines->text, text->selection_end - (lines->text - text->text)); if (x < text->xofs_edit) { text->xofs_edit = x; } if (2 + x - text->clip_width > text->xofs_edit) { text->xofs_edit = 2 + x - text->clip_width; } } gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text)); } static void _invisible_destroy (GtkInvisible *invisible, EText *text) { text->invisible = NULL; } static GtkWidget *e_text_get_invisible(EText *text) { GtkWidget *invisible; if (text->invisible) { invisible = text->invisible; } else { invisible = gtk_invisible_new(); text->invisible = invisible; gtk_selection_add_target (invisible, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, E_SELECTION_PRIMARY); gtk_selection_add_target (invisible, clipboard_atom, GDK_SELECTION_TYPE_STRING, E_SELECTION_CLIPBOARD); gtk_signal_connect (GTK_OBJECT(invisible), "selection_get", GTK_SIGNAL_FUNC (_selection_get), text); gtk_signal_connect (GTK_OBJECT(invisible), "selection_clear_event", GTK_SIGNAL_FUNC (_selection_clear_event), text); gtk_signal_connect (GTK_OBJECT(invisible), "selection_received", GTK_SIGNAL_FUNC (_selection_received), text); gtk_signal_connect (GTK_OBJECT(invisible), "destroy", GTK_SIGNAL_FUNC (_invisible_destroy), text); } return invisible; } static void _selection_clear_event (GtkInvisible *invisible, GdkEventSelection *event, EText *text) { if (event->selection == GDK_SELECTION_PRIMARY) { g_free (text->primary_selection); text->primary_selection = NULL; text->primary_length = 0; text->has_selection = FALSE; gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text)); } else if (event->selection == clipboard_atom) { g_free (text->clipboard_selection); text->clipboard_selection = NULL; text->clipboard_length = 0; } } static void _selection_get (GtkInvisible *invisible, GtkSelectionData *selection_data, guint info, guint time_stamp, EText *text) { switch (info) { case E_SELECTION_PRIMARY: gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, 8, text->primary_selection, text->primary_length); break; case E_SELECTION_CLIPBOARD: gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, 8, text->clipboard_selection, text->clipboard_length); break; } } static void _selection_received (GtkInvisible *invisible, GtkSelectionData *selection_data, guint time, EText *text) { if (selection_data->length < 0 || selection_data->type != GDK_SELECTION_TYPE_STRING) { return; } else { ETextEventProcessorCommand command; command.action = E_TEP_INSERT; command.position = E_TEP_SELECTION; command.string = selection_data->data; command.value = selection_data->length; command.time = time; e_text_command(text->tep, &command, text); } } static void e_text_supply_selection (EText *text, guint time, GdkAtom selection, guchar *data, gint length) { gboolean successful; GtkWidget *invisible; invisible = e_text_get_invisible(text); if (selection == GDK_SELECTION_PRIMARY ) { if (text->primary_selection) { g_free (text->primary_selection); } text->primary_selection = g_strndup(data, length); text->primary_length = length; } else if (selection == clipboard_atom) { if (text->clipboard_selection) { g_free (text->clipboard_selection); } text->clipboard_selection = g_strndup(data, length); text->clipboard_length = length; } successful = gtk_selection_owner_set (invisible, selection, time); if (selection == GDK_SELECTION_PRIMARY) text->has_selection = successful; } static void e_text_get_selection(EText *text, GdkAtom selection, guint32 time) { GtkWidget *invisible; invisible = e_text_get_invisible(text); gtk_selection_convert(invisible, selection, GDK_SELECTION_TYPE_STRING, time); } #if 0 static void e_text_real_copy_clipboard (EText *text) { guint32 time; gint selection_start_pos; gint selection_end_pos; g_return_if_fail (text != NULL); g_return_if_fail (E_IS_TEXT (text)); time = gtk_text_get_event_time (text); selection_start_pos = MIN (text->selection_start, text->selection_end); selection_end_pos = MAX (text->selection_start, text->selection_end); if (selection_start_pos != selection_end_pos) { if (gtk_selection_owner_set (GTK_WIDGET (text->canvas), clipboard_atom, time)) text->clipboard_text = ""; } } static void e_text_real_paste_clipboard (EText *text) { guint32 time; g_return_if_fail (text != NULL); g_return_if_fail (E_IS_TEXT (text)); time = e_text_get_event_time (text); if (text->editable) gtk_selection_convert (GTK_WIDGET(text->widget), clipboard_atom, gdk_atom_intern ("COMPOUND_TEXT", FALSE), time); } #endif /* Get the timestamp of the current event. Actually, the only thing * we really care about below is the key event */ static guint32 e_text_get_event_time (EText *text) { GdkEvent *event; guint32 tm = GDK_CURRENT_TIME; event = gtk_get_current_event(); if (event) switch (event->type) { case GDK_MOTION_NOTIFY: tm = event->motion.time; break; case GDK_BUTTON_PRESS: case GDK_2BUTTON_PRESS: case GDK_3BUTTON_PRESS: case GDK_BUTTON_RELEASE: tm = event->button.time; break; case GDK_KEY_PRESS: case GDK_KEY_RELEASE: tm = event->key.time; break; case GDK_ENTER_NOTIFY: case GDK_LEAVE_NOTIFY: tm = event->crossing.time; break; case GDK_PROPERTY_NOTIFY: tm = event->property.time; break; case GDK_SELECTION_CLEAR: case GDK_SELECTION_REQUEST: case GDK_SELECTION_NOTIFY: tm = event->selection.time; break; case GDK_PROXIMITY_IN: case GDK_PROXIMITY_OUT: tm = event->proximity.time; break; default: /* use current time */ break; } gdk_event_free(event); return tm; } /* Routines for sucking fonts from the X server */ static ETextSuckFont * e_suck_font (GdkFont *font) { ETextSuckFont *suckfont; int i; int x, y; char text[1]; int lbearing, rbearing, ch_width, ascent, descent; GdkPixmap *pixmap; GdkColor black, white; GdkImage *image; GdkGC *gc; guchar *line; int width, height; int black_pixel, pixel; if (!font) return NULL; suckfont = g_new (ETextSuckFont, 1); height = font->ascent + font->descent; x = 0; for (i = 0; i < 256; i++) { text[0] = i; gdk_text_extents (font, text, 1, &lbearing, &rbearing, &ch_width, &ascent, &descent); suckfont->chars[i].left_sb = lbearing; suckfont->chars[i].right_sb = ch_width - rbearing; suckfont->chars[i].width = rbearing - lbearing; suckfont->chars[i].ascent = ascent; suckfont->chars[i].descent = descent; suckfont->chars[i].bitmap_offset = x; x += (ch_width + 31) & -32; } width = x; suckfont->bitmap_width = width; suckfont->bitmap_height = height; suckfont->ascent = font->ascent; pixmap = gdk_pixmap_new (NULL, suckfont->bitmap_width, suckfont->bitmap_height, 1); gc = gdk_gc_new (pixmap); gdk_gc_set_font (gc, font); black_pixel = BlackPixel (gdk_display, DefaultScreen (gdk_display)); black.pixel = black_pixel; white.pixel = WhitePixel (gdk_display, DefaultScreen (gdk_display)); gdk_gc_set_foreground (gc, &white); gdk_draw_rectangle (pixmap, gc, 1, 0, 0, width, height); gdk_gc_set_foreground (gc, &black); for (i = 0; i < 256; i++) { text[0] = i; gdk_draw_text (pixmap, font, gc, suckfont->chars[i].bitmap_offset - suckfont->chars[i].left_sb, font->ascent, text, 1); } /* The handling of the image leaves me with distinct unease. But this * is more or less copied out of gimp/app/text_tool.c, so it _ought_ to * work. -RLL */ image = gdk_image_get (pixmap, 0, 0, width, height); suckfont->bitmap = g_malloc0 ((width >> 3) * height); line = suckfont->bitmap; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { pixel = gdk_image_get_pixel (image, x, y); if (pixel == black_pixel) line[x >> 3] |= 128 >> (x & 7); } line += width >> 3; } gdk_image_destroy (image); /* free the pixmap */ gdk_pixmap_unref (pixmap); /* free the gc */ gdk_gc_destroy (gc); return suckfont; } static void e_suck_font_free (ETextSuckFont *suckfont) { g_free (suckfont->bitmap); g_free (suckfont); }