Files
evolution/widgets/text/e-text.c
Chris Toshok 2579e70339 [ fixes several utf8/pango related problems, including bugs #41288,
2003-05-13  Chris Toshok  <toshok@ximian.com>

	[ fixes several utf8/pango related problems, including bugs
	#41288, #42596, #42604 ]

	* configure.in (GAL_CURRENT): bump to 3, per mkestner.
	(GAL_REVISION): drop to 0, per mkestner.

	* gal/e-text/e-text.c (reset_layout_attrs): we need to convert the
	start/end bounds of the object to byte indices for the attribute.
	(reset_layout): in the layout == NULL case don't create the layout
	then immediately set it again with the same text.  also, we need
	to convert selection_start to a byte index before calling
	pango_layout_get_cursor_pos.
	(e_text_draw): remove some #ifdef 0'd code, move the calculation
	of our initial clip_rect below the xpos/ypos assignments so we
	don't duplicate the expression.  Fix the selection drawing in the
	multiline case so that it actually works, instead of assuming that
	all ETexts only have 1 line *boggle*.
	(get_position_from_xy): this needs to return a utf8 offset.
	(e_text_copy_clipboard): convert sel_start/sel_end to byte indices
	before copying.
	(primary_get_cb): same.
	(paste_received): validate the input here, and drop the length
	parameter from e_text_insert.
	(next_word): convert from an utf8 offset on entry to this
	function, and return a utf8 offset when we're done.  also, remove
	the call the g_unichar_validate.  we validate at all points where
	text is inserted.
	(find_offset_into_line): new function used in the backward/forward
	line code.  find the utf8 offset into a line (the number of utf8
	characters from a prior \n or beginning of the string.)
	(_get_position): in general there are lots of changes here because
	text->selection_start/text->selection_end are utf8 offsets, not
	byte offsets. fix E_TEP_START_OF_LINE so that hitting Ctrl-a when
	you're at the beginning of a line doesn't take you to the
	beginning of the previous line.  fix E_TEP_END_OF_LINE in an
	analogous fashion. for E_TEP_FORWARD_CHARACTER we just increment
	by 1.  for E_TEP_BACKWARD_CHARACTER we just decrement by 1.  for
	E_TEP_BACKWARD_WORD we drop the g_unichar_validate call and
	simplify things a bit.  reimplement
	E_TEP_FORWARD_LINE/E_TEP_BACKWARD_LINE so they find the current
	offset into the line, then scan forward/backward for the next/prev
	line, and put us at the right offset on that line.  fix
	E_TEP_SELECT_WORD so double clicking in the space between words
	doesn't select both words - if you double click on the trailing
	edge of the space, it selects the next word.  leading edge selects
	the previous one.  for E_TEP_SELECT_ALL use g_utf8_strlen.
	(e_text_insert): everything that calls this passes a \0 terminated
	string, so we assume it's \0 terminated (the old code did as well,
	with calls to strlen) and drop the length parameter.  also make
	sure this is all utf8 happy.
	(capitalize): use g_utf8_offset_to_pointer instead of just adding
	text->text and start/end, and remove the validate call.  also fix
	the call to e_text_model_delete and use e_text_model_insert_length
	instead of e_text_model_insert.
	(e_text_command): for E_TEP_INSERT, validate the input.  for
	E_TEP_CAPS just use MAX instead of the neat little hack.  also,
	fix the scrolling so that it scrolls properly in both X and Y
	directions (there are still some hiccups but it's much much better
	than previously).
	(e_text_commit_cb): validate the input here.

	* gal/e-text/e-text-model.c (struct _ETextModelPrivate): just use
	a GString here and get rid of MAX_LENGTH.
	(e_text_model_dispose): free GString.
	(e_text_model_real_validate_position): clean this up a bit.
	(e_text_model_real_get_text): return the contents of the GString.
	(e_text_model_real_get_text_length): use g_utf8_strlen here.
	(e_text_model_real_set_text): convert to GString
	(e_text_model_real_insert): just call e_text_model_insert_length
	here instead of duplicating the function.
	(e_text_model_real_insert_length): convert to utf8/gstring.
	i.e. convert @position and @length to a bytes and use
	g_string_insert_len.
	(e_text_model_real_delete): same, with g_string_erase.
	(e_text_model_get_text_length): use g_utf8_strlen
	(e_text_model_strdup_nth_object): convert the length of the object
	to bytes before copying.
	(e_text_model_get_nth_object_bounds): calculate start/end properly
	for u

svn path=/trunk/; revision=21163
2003-05-14 00:42:36 +00:00

3706 lines
94 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* e-text.c - Text item for evolution.
* Copyright 2000, 2001, Ximian, Inc.
*
* Authors:
* Chris Lahey <clahey@ximian.com>
* Jon Trowbridge <trow@ximian.com>
*
* 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 <federico@nuclecu.unam.mx>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License, version 2, as published by the Free Software Foundation.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
#include <config.h>
#include "e-text.h"
#include <math.h>
#include <ctype.h>
#include <string.h>
#include <glib-object.h>
#include <gdk/gdkx.h> /* for BlackPixel */
#include <gtk/gtkclipboard.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkselection.h>
#include <gtk/gtkstock.h>
#include <gtk/gtkwindow.h>
#include <gtk/gtktypebuiltins.h>
#include <gtk/gtkmenu.h>
#include <gtk/gtkimagemenuitem.h>
#include <gtk/gtkimmulticontext.h>
#include <gtk/gtkmenuitem.h>
#include <gtk/gtkseparatormenuitem.h>
#include <libgnomecanvas/gnome-canvas-rect-ellipse.h>
#include <libgnome/gnome-i18n.h>
#include "gal/util/e-util.h"
#include "gal/widgets/e-canvas.h"
#include "gal/widgets/e-canvas-utils.h"
#include "gal/widgets/e-unicode.h"
#include "gal/util/e-text-event-processor-emacs-like.h"
#include "gal/util/e-util.h"
#include <libart_lgpl/art_affine.h>
#include <libart_lgpl/art_rgb.h>
#include <libart_lgpl/art_rgb_bitmap_affine.h>
#define PARENT_TYPE (gnome_canvas_item_get_type())
#define BORDER_INDENT 3
#define d(x)
enum {
E_TEXT_CHANGED,
E_TEXT_ACTIVATE,
E_TEXT_KEYPRESS,
E_TEXT_POPULATE_POPUP,
E_TEXT_STYLE_SET,
E_TEXT_LAST_SIGNAL
};
static GQuark e_text_signals[E_TEXT_LAST_SIGNAL] = { 0 };
/* Object argument IDs */
enum {
PROP_0,
PROP_MODEL,
PROP_EVENT_PROCESSOR,
PROP_TEXT,
PROP_BOLD,
PROP_STRIKEOUT,
PROP_ANCHOR,
PROP_JUSTIFICATION,
PROP_CLIP_WIDTH,
PROP_CLIP_HEIGHT,
PROP_CLIP,
PROP_FILL_CLIP_RECTANGLE,
PROP_X_OFFSET,
PROP_Y_OFFSET,
PROP_FILL_COLOR,
PROP_FILL_COLOR_GDK,
PROP_FILL_COLOR_RGBA,
PROP_FILL_STIPPLE,
PROP_TEXT_WIDTH,
PROP_TEXT_HEIGHT,
PROP_EDITABLE,
PROP_USE_ELLIPSIS,
PROP_ELLIPSIS,
PROP_LINE_WRAP,
PROP_BREAK_CHARACTERS,
PROP_MAX_LINES,
PROP_WIDTH,
PROP_HEIGHT,
PROP_DRAW_BORDERS,
PROP_ALLOW_NEWLINES,
PROP_DRAW_BACKGROUND,
PROP_DRAW_BUTTON,
PROP_CURSOR_POS,
PROP_IM_CONTEXT,
PROP_HANDLE_POPUP
};
static void e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data);
static void e_text_text_model_changed(ETextModel *model, EText *text);
static void e_text_text_model_reposition (ETextModel *model, ETextModelReposFn fn, gpointer repos_data, gpointer data);
static void _get_tep(EText *text);
static void calc_height (EText *text);
static gboolean show_pango_rectangle (EText *text, PangoRectangle rect);
static void e_text_do_popup (EText *text, GdkEventButton *button, int position);
static void e_text_update_primary_selection (EText *text);
static void e_text_paste (EText *text, GdkAtom selection);
static void e_text_insert(EText *text, const char *string);
/* GtkEditable Methods */
static void e_text_editable_do_insert_text (GtkEditable *editable,
const gchar *text,
gint length,
gint *position);
static void e_text_editable_do_delete_text (GtkEditable *editable,
gint start_pos,
gint end_pos);
static gchar* e_text_editable_get_chars (GtkEditable *editable,
gint start_pos,
gint end_pos);
static void e_text_editable_set_selection_bounds (GtkEditable *editable,
gint start_pos,
gint end_pos);
static gboolean e_text_editable_get_selection_bounds (GtkEditable *editable,
gint *start_pos,
gint *end_pos);
static void e_text_editable_set_position (GtkEditable *editable,
gint position);
static gint e_text_editable_get_position (GtkEditable *editable);
/* IM Context Callbacks */
static void e_text_commit_cb (GtkIMContext *context,
const gchar *str,
EText *text);
static gboolean e_text_retrieve_surrounding_cb (GtkIMContext *context,
EText *text);
static gboolean e_text_delete_surrounding_cb (GtkIMContext *context,
gint offset,
gint n_chars,
EText *text);
static GnomeCanvasItemClass *parent_class;
static GdkAtom clipboard_atom = GDK_NONE;
/* Dispose handler for the text item */
static void
e_text_style_set (EText *text, GtkStyle *previous_style)
{
if ( text->line_wrap ) {
text->needs_split_into_lines = 1;
} else {
text->needs_calc_height = 1;
}
e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
}
static void
e_text_dispose (GObject *object)
{
EText *text;
g_return_if_fail (object != NULL);
g_return_if_fail (E_IS_TEXT (object));
text = E_TEXT (object);
if (text->tooltip_owner)
e_canvas_hide_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(text)->canvas));
text->tooltip_owner = 0;
if (text->model_changed_signal_id)
g_signal_handler_disconnect (text->model,
text->model_changed_signal_id);
text->model_changed_signal_id = 0;
if (text->model_repos_signal_id)
g_signal_handler_disconnect (text->model,
text->model_repos_signal_id);
text->model_repos_signal_id = 0;
if (text->model)
g_object_unref(text->model);
text->model = NULL;
if (text->tep_command_id)
g_signal_handler_disconnect(text->tep,
text->tep_command_id);
text->tep_command_id = 0;
if (text->tep)
g_object_unref (text->tep);
text->tep = NULL;
g_free (text->revert);
text->revert = NULL;
if (text->stipple)
gdk_bitmap_unref (text->stipple);
text->stipple = NULL;
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->tooltip_timeout ) {
gtk_timeout_remove (text->tooltip_timeout);
text->tooltip_timeout = 0;
}
if ( text->dbl_timeout ) {
gtk_timeout_remove (text->dbl_timeout);
text->dbl_timeout = 0;
}
if ( text->tpl_timeout ) {
gtk_timeout_remove (text->tpl_timeout);
text->tpl_timeout = 0;
}
if (text->layout) {
g_object_unref (text->layout);
text->layout = NULL;
}
if (text->im_context) {
g_signal_handlers_disconnect_matched (text->im_context,
G_SIGNAL_MATCH_DATA,
0, 0, NULL,
NULL, text);
g_object_unref (text->im_context);
text->im_context = NULL;
}
if (G_OBJECT_CLASS (parent_class)->dispose)
(* G_OBJECT_CLASS (parent_class)->dispose) (object);
}
static void
reset_layout_attrs (EText *text)
{
PangoAttrList *attrs = NULL;
int object_count;
if (text->layout == NULL)
return;
object_count = e_text_model_object_count (text->model);
if (text->bold || text->strikeout || object_count > 0) {
int length = 0;
int i;
attrs = pango_attr_list_new ();
for (i = 0; i < object_count; i++) {
int start_pos, end_pos;
PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
e_text_model_get_nth_object_bounds (text->model, i, &start_pos, &end_pos);
attr->start_index = g_utf8_offset_to_pointer (text->text, start_pos) - text->text;
attr->end_index = g_utf8_offset_to_pointer (text->text, end_pos) - text->text;
pango_attr_list_insert (attrs, attr);
}
if (text->bold || text->strikeout)
length = strlen (text->text);
if (text->bold) {
PangoAttribute *attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
attr->start_index = 0;
attr->end_index = length;
pango_attr_list_insert_before (attrs, attr);
}
if (text->strikeout) {
PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
attr->start_index = 0;
attr->end_index = length;
pango_attr_list_insert_before (attrs, attr);
}
}
pango_layout_set_attributes (text->layout, attrs);
if (attrs)
pango_attr_list_unref (attrs);
calc_height (text);
}
static void
create_layout (EText *text)
{
GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
if (text->layout)
return;
text->layout = gtk_widget_create_pango_layout (GTK_WIDGET (item->canvas), text->text);
if (text->line_wrap)
pango_layout_set_width (text->layout, text->clip_width < 0 ? -1 : text->clip_width * PANGO_SCALE);
reset_layout_attrs (text);
}
static void
reset_layout (EText *text)
{
if (text->layout == NULL) {
create_layout (text);
}
else {
pango_layout_set_text (text->layout, text->text, -1);
reset_layout_attrs (text);
}
if (!text->button_down) {
PangoRectangle strong_pos, weak_pos;
char *offs = g_utf8_offset_to_pointer (text->text, text->selection_start);
pango_layout_get_cursor_pos (text->layout, offs - text->text, &strong_pos, &weak_pos);
if (strong_pos.x != weak_pos.x ||
strong_pos.y != weak_pos.y ||
strong_pos.width != weak_pos.width ||
strong_pos.height != weak_pos.height)
show_pango_rectangle (text, weak_pos);
show_pango_rectangle (text, strong_pos);
}
}
static void
e_text_text_model_changed (ETextModel *model, EText *text)
{
gint model_len = e_text_model_get_text_length (model);
text->text = e_text_model_get_text(model);
/* Make sure our selection doesn't extend past the bounds of our text. */
text->selection_start = CLAMP (text->selection_start, 0, model_len);
text->selection_end = CLAMP (text->selection_end, 0, model_len);
text->needs_reset_layout = 1;
text->needs_split_into_lines = 1;
text->needs_redraw = 1;
e_canvas_item_request_reflow (GNOME_CANVAS_ITEM(text));
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
}
static void
e_text_text_model_reposition (ETextModel *model, ETextModelReposFn fn, gpointer repos_data, gpointer user_data)
{
EText *text = E_TEXT (user_data);
gint model_len = e_text_model_get_text_length (model);
text->selection_start = fn (text->selection_start, repos_data);
text->selection_end = fn (text->selection_end, repos_data);
/* Our repos function should make sure we don't overrun the buffer, but it never
hurts to be paranoid. */
text->selection_start = CLAMP (text->selection_start, 0, model_len);
text->selection_end = CLAMP (text->selection_end, 0, model_len);
if (text->selection_start > text->selection_end) {
gint tmp = text->selection_start;
text->selection_start = text->selection_end;
text->selection_end = tmp;
}
}
static void
get_bounds (EText *text, double *px1, double *py1, double *px2, double *py2)
{
GnomeCanvasItem *item;
double wx, wy, clip_width, clip_height;
item = GNOME_CANVAS_ITEM (text);
/* Get canvas pixel coordinates for text position */
wx = 0;
wy = 0;
gnome_canvas_item_i2w (item, &wx, &wy);
gnome_canvas_w2c (item->canvas, wx + text->xofs, wy + text->yofs, &text->cx, &text->cy);
gnome_canvas_w2c (item->canvas, wx, wy, &text->clip_cx, &text->clip_cy);
if (text->clip_width < 0)
clip_width = text->width;
else
clip_width = text->clip_width;
if ( text->clip_height < 0 )
clip_height = text->height;
else
clip_height = text->clip_height;
/* Get canvas pixel coordinates for clip rectangle position */
text->clip_cwidth = clip_width * item->canvas->pixels_per_unit;
text->clip_cheight = clip_height * item->canvas->pixels_per_unit;
/* 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->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->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;
}
text->text_cx = text->cx;
text->text_cy = text->cy;
if (text->draw_borders) {
text->text_cx += BORDER_INDENT;
text->text_cy += BORDER_INDENT;
}
/* 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->width;
*py2 = text->cy + text->height;
}
}
static void
calc_height (EText *text)
{
GnomeCanvasItem *item;
int old_height;
int old_width;
int width = 0;
int height = 0;
item = GNOME_CANVAS_ITEM (text);
/* Calculate text dimensions */
old_height = text->height;
old_width = text->width;
if (text->layout)
pango_layout_get_pixel_size (text->layout, &width, &height);
text->height = height;
text->width = width;
if (old_height != text->height || old_width != text->width)
e_canvas_item_request_parent_reflow(item);
}
static void
calc_ellipsis (EText *text)
{
#warning "AIEEEE FIX ME. a pango layout per calc_ellipsis sucks"
int width;
PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
text->ellipsis ? text->ellipsis : "...");
pango_layout_get_size (layout, &width, NULL);
text->ellipsis_width = width;
g_object_unref (layout);
}
static void
split_into_lines (EText *text)
{
text->num_lines = pango_layout_get_line_count (text->layout);
}
/* Convenience function to set the text's GC's foreground color */
static void
set_text_gc_foreground (EText *text)
{
if (!text->gc)
return;
gdk_gc_set_foreground (text->gc, &text->color);
}
/* 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_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GnomeCanvasItem *item;
EText *text;
GdkColor color = { 0, 0, 0, 0, };
GdkColor *pcolor;
gboolean color_changed;
int have_pixel;
gboolean needs_update = 0;
gboolean needs_reflow = 0;
item = GNOME_CANVAS_ITEM (object);
text = E_TEXT (object);
color_changed = FALSE;
have_pixel = FALSE;
switch (prop_id) {
case PROP_MODEL:
if ( text->model_changed_signal_id )
g_signal_handler_disconnect (text->model,
text->model_changed_signal_id);
if ( text->model_repos_signal_id )
g_signal_handler_disconnect (text->model,
text->model_repos_signal_id);
g_object_unref (text->model);
text->model = E_TEXT_MODEL (g_value_get_object (value));
g_object_ref (text->model);
text->model_changed_signal_id =
g_signal_connect (text->model,
"changed",
G_CALLBACK (e_text_text_model_changed),
text);
text->model_repos_signal_id =
g_signal_connect (text->model,
"reposition",
G_CALLBACK (e_text_text_model_reposition),
text);
text->text = e_text_model_get_text(text->model);
g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_EVENT_PROCESSOR:
if ( text->tep && text->tep_command_id )
g_signal_handler_disconnect(text->tep,
text->tep_command_id);
if ( text->tep ) {
g_object_unref(text->tep);
}
text->tep = E_TEXT_EVENT_PROCESSOR(g_value_get_object (value));
g_object_ref(text->tep);
text->tep_command_id =
g_signal_connect(text->tep,
"command",
G_CALLBACK(e_text_command),
text);
if (!text->allow_newlines)
g_object_set (text->tep,
"allow_newlines", FALSE,
NULL);
break;
case PROP_TEXT:
e_text_model_set_text(text->model, g_value_get_string (value));
break;
case PROP_BOLD:
text->bold = g_value_get_boolean (value);
text->needs_redraw = 1;
text->needs_recalc_bounds = 1;
if ( text->line_wrap )
text->needs_split_into_lines = 1;
else {
text->needs_calc_height = 1;
}
needs_update = 1;
needs_reflow = 1;
break;
case PROP_STRIKEOUT:
text->strikeout = g_value_get_boolean (value);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_ANCHOR:
text->anchor = g_value_get_enum (value);
text->needs_recalc_bounds = 1;
needs_update = 1;
break;
case PROP_JUSTIFICATION:
text->justification = g_value_get_enum (value);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_CLIP_WIDTH:
text->clip_width = fabs (g_value_get_double (value));
calc_ellipsis (text);
if ( text->line_wrap )
text->needs_split_into_lines = 1;
else {
text->needs_calc_height = 1;
}
needs_reflow = 1;
break;
case PROP_CLIP_HEIGHT:
text->clip_height = fabs (g_value_get_double (value));
text->needs_recalc_bounds = 1;
/* toshok: kind of a hack - set needs_reset_layout
here so when something about the style/them
changes, we redraw the text at the proper size/with
the proper font. */
text->needs_reset_layout = 1;
needs_reflow = 1;
break;
case PROP_CLIP:
text->clip = g_value_get_boolean (value);
calc_ellipsis (text);
if ( text->line_wrap )
text->needs_split_into_lines = 1;
else {
text->needs_calc_height = 1;
}
needs_reflow = 1;
break;
case PROP_FILL_CLIP_RECTANGLE:
text->fill_clip_rectangle = g_value_get_boolean (value);
needs_update = 1;
break;
case PROP_X_OFFSET:
text->xofs = g_value_get_double (value);
text->needs_recalc_bounds = 1;
needs_update = 1;
break;
case PROP_Y_OFFSET:
text->yofs = g_value_get_double (value);
text->needs_recalc_bounds = 1;
needs_update = 1;
break;
case PROP_FILL_COLOR:
if (g_value_get_string (value))
gdk_color_parse (g_value_get_string (value), &color);
text->rgba = ((color.red & 0xff00) << 16 |
(color.green & 0xff00) << 8 |
(color.blue & 0xff00) |
0xff);
color_changed = TRUE;
break;
case PROP_FILL_COLOR_GDK:
pcolor = g_value_get_boxed (value);
if (pcolor) {
color = *pcolor;
}
text->rgba = ((color.red & 0xff00) << 16 |
(color.green & 0xff00) << 8 |
(color.blue & 0xff00) |
0xff);
color_changed = TRUE;
break;
case PROP_FILL_COLOR_RGBA:
text->rgba = g_value_get_uint (value);
color.red = ((text->rgba >> 24) & 0xff) * 0x101;
color.green = ((text->rgba >> 16) & 0xff) * 0x101;
color.blue = ((text->rgba >> 8) & 0xff) * 0x101;
color_changed = TRUE;
break;
case PROP_FILL_STIPPLE:
set_stipple (text, g_value_get_object (value), FALSE);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_EDITABLE:
text->editable = g_value_get_boolean (value);
text->needs_redraw = 1;
needs_update = 1;
break;
case PROP_USE_ELLIPSIS:
text->use_ellipsis = g_value_get_boolean (value);
needs_reflow = 1;
break;
case PROP_ELLIPSIS:
if (text->ellipsis)
g_free (text->ellipsis);
text->ellipsis = g_strdup (g_value_get_string (value));
calc_ellipsis (text);
needs_reflow = 1;
break;
case PROP_LINE_WRAP:
text->line_wrap = g_value_get_boolean (value);
if (text->line_wrap) {
if (text->layout) {
pango_layout_set_width (text->layout, text->width < 0 ? -1 : text->width * PANGO_SCALE);
}
}
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_BREAK_CHARACTERS:
if ( text->break_characters ) {
g_free(text->break_characters);
text->break_characters = NULL;
}
if ( g_value_get_string (value) )
text->break_characters = g_strdup( g_value_get_string (value) );
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_MAX_LINES:
text->max_lines = g_value_get_int (value);
text->needs_split_into_lines = 1;
needs_reflow = 1;
break;
case PROP_WIDTH:
text->clip_width = fabs (g_value_get_double (value));
calc_ellipsis (text);
if ( text->line_wrap ) {
if (text->layout) {
pango_layout_set_width (text->layout, text->width < 0 ? -1 : text->width * PANGO_SCALE);
}
text->needs_split_into_lines = 1;
}
else {
text->needs_calc_height = 1;
}
needs_reflow = 1;
break;
case PROP_DRAW_BORDERS:
if (text->draw_borders != g_value_get_boolean (value)) {
text->draw_borders = g_value_get_boolean (value);
text->needs_calc_height = 1;
text->needs_redraw = 1;
needs_reflow = 1;
needs_update = 1;
}
break;
case PROP_DRAW_BACKGROUND:
if (text->draw_background != g_value_get_boolean (value)) {
text->draw_background = g_value_get_boolean (value);
text->needs_redraw = 1;
}
break;
case PROP_DRAW_BUTTON:
if (text->draw_button != g_value_get_boolean (value)) {
text->draw_button = g_value_get_boolean (value);
text->needs_redraw = 1;
}
break;
case PROP_ALLOW_NEWLINES:
text->allow_newlines = g_value_get_boolean (value);
_get_tep(text);
g_object_set (text->tep,
"allow_newlines", g_value_get_boolean (value),
NULL);
break;
case PROP_CURSOR_POS: {
ETextEventProcessorCommand command;
command.action = E_TEP_MOVE;
command.position = E_TEP_VALUE;
command.value = g_value_get_int (value);
command.time = GDK_CURRENT_TIME;
e_text_command (text->tep, &command, text);
break;
}
case PROP_IM_CONTEXT:
if (text->im_context)
g_object_unref (text->im_context);
text->im_context = g_value_get_object (value);
if (text->im_context)
g_object_ref (text->im_context);
text->need_im_reset = FALSE;
break;
case PROP_HANDLE_POPUP:
text->handle_popup = g_value_get_boolean (value);
break;
default:
return;
}
if (color_changed) {
GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (item->canvas));
text->color = color;
gdk_rgb_find_color (colormap, &text->color);
if (!item->canvas->aa)
set_text_gc_foreground (text);
text->needs_redraw = 1;
needs_update = 1;
}
if ( needs_reflow )
e_canvas_item_request_reflow (item);
if ( needs_update )
gnome_canvas_item_request_update (item);
}
/* Get_arg handler for the text item */
static void
e_text_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EText *text;
text = E_TEXT (object);
switch (prop_id) {
case PROP_MODEL:
g_value_set_object (value, text->model);
break;
case PROP_EVENT_PROCESSOR:
_get_tep(text);
g_value_set_object (value, text->tep);
break;
case PROP_TEXT:
g_value_set_string (value, g_strdup (text->text));
break;
case PROP_BOLD:
g_value_set_boolean (value, text->bold);
break;
case PROP_STRIKEOUT:
g_value_set_boolean (value, text->strikeout);
break;
case PROP_ANCHOR:
g_value_set_enum (value, text->anchor);
break;
case PROP_JUSTIFICATION:
g_value_set_enum (value, text->justification);
break;
case PROP_CLIP_WIDTH:
g_value_set_double (value, text->clip_width);
break;
case PROP_CLIP_HEIGHT:
g_value_set_double (value, text->clip_height);
break;
case PROP_CLIP:
g_value_set_boolean (value, text->clip);
break;
case PROP_FILL_CLIP_RECTANGLE:
g_value_set_boolean (value, text->fill_clip_rectangle);
break;
case PROP_X_OFFSET:
g_value_set_double (value, text->xofs);
break;
case PROP_Y_OFFSET:
g_value_set_double (value, text->yofs);
break;
case PROP_FILL_COLOR_GDK:
g_value_set_boxed (value, &text->color);
break;
case PROP_FILL_COLOR_RGBA:
g_value_set_uint (value, text->rgba);
break;
case PROP_FILL_STIPPLE:
g_value_set_object (value, text->stipple);
break;
case PROP_TEXT_WIDTH:
g_value_set_double (value, text->width / text->item.canvas->pixels_per_unit);
break;
case PROP_TEXT_HEIGHT:
g_value_set_double (value, text->height / text->item.canvas->pixels_per_unit);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, text->editable);
break;
case PROP_USE_ELLIPSIS:
g_value_set_boolean (value, text->use_ellipsis);
break;
case PROP_ELLIPSIS:
g_value_set_string (value, g_strdup (text->ellipsis));
break;
case PROP_LINE_WRAP:
g_value_set_boolean (value, text->line_wrap);
break;
case PROP_BREAK_CHARACTERS:
g_value_set_string (value, g_strdup (text->break_characters));
break;
case PROP_MAX_LINES:
g_value_set_int (value, text->max_lines);
break;
case PROP_WIDTH:
g_value_set_double (value, text->clip_width);
break;
case PROP_HEIGHT:
g_value_set_double (value, text->clip && text->clip_height != -1 ? text->clip_height : text->height / text->item.canvas->pixels_per_unit);
break;
case PROP_DRAW_BORDERS:
g_value_set_boolean (value, text->draw_borders);
break;
case PROP_DRAW_BACKGROUND:
g_value_set_boolean (value, text->draw_background);
break;
case PROP_DRAW_BUTTON:
g_value_set_boolean (value, text->draw_button);
break;
case PROP_ALLOW_NEWLINES:
g_value_set_boolean (value, text->allow_newlines);
break;
case PROP_CURSOR_POS:
g_value_set_int (value, text->selection_start);
break;
case PROP_IM_CONTEXT:
g_value_set_object (value, text->im_context);
break;
case PROP_HANDLE_POPUP:
g_value_set_boolean (value, text->handle_popup);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/* Update handler for the text item */
static void
e_text_reflow (GnomeCanvasItem *item, int flags)
{
EText *text;
text = E_TEXT (item);
if (text->needs_reset_layout) {
reset_layout (text);
text->needs_reset_layout = 0;
text->needs_calc_height = 1;
}
if (text->needs_split_into_lines) {
split_into_lines (text);
text->needs_split_into_lines = 0;
text->needs_calc_height = 1;
}
if ( text->needs_calc_height ) {
calc_height (text);
gnome_canvas_item_request_update(item);
text->needs_calc_height = 0;
text->needs_recalc_bounds = 1;
}
}
/* 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;
text = E_TEXT (item);
if (parent_class->update)
(* parent_class->update) (item, affine, clip_path, flags);
if ( text->needs_recalc_bounds
|| (flags & GNOME_CANVAS_UPDATE_AFFINE)) {
if (!item->canvas->aa) {
set_text_gc_foreground (text);
set_stipple (text, text->stipple, TRUE);
get_bounds (text, &x1, &y1, &x2, &y2);
if ( item->x1 != x1 ||
item->x2 != x2 ||
item->y1 != y1 ||
item->y2 != y2 ) {
gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2);
item->x1 = x1;
item->y1 = y1;
item->x2 = x2;
item->y2 = y2;
text->needs_redraw = 1;
item->canvas->need_repick = TRUE;
}
if (!text->fill_clip_rectangle)
item->canvas->need_repick = TRUE;
}
text->needs_recalc_bounds = 0;
}
if ( text->needs_redraw ) {
gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2);
text->needs_redraw = 0;
}
}
/* 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);
create_layout (text);
text->gc = gdk_gc_new (item->canvas->layout.bin_window);
#ifndef NO_WARNINGS
#warning Color brokenness ...
#endif
#if 0
gdk_color_context_query_color (item->canvas->cc, &text->color);
gdk_gc_set_foreground (text->gc, &text->color);
#endif
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);
text->i_cursor = NULL;
gdk_cursor_destroy (text->default_cursor);
text->default_cursor = NULL;
if (parent_class->unrealize)
(* parent_class->unrealize) (item);
}
static void
_get_tep(EText *text)
{
if (!text->tep) {
text->tep = e_text_event_processor_emacs_like_new();
text->tep_command_id =
g_signal_connect(text->tep,
"command",
G_CALLBACK(e_text_command),
text);
}
}
static void
draw_pango_rectangle (GdkDrawable *drawable, GdkGC *gc, int x1, int y1, PangoRectangle rect)
{
int width = rect.width / PANGO_SCALE;
int height = rect.height / PANGO_SCALE;
if (width <= 0)
width = 1;
if (height <= 0)
height = 1;
gdk_draw_rectangle (drawable, gc, TRUE,
x1 + rect.x / PANGO_SCALE, y1 + rect.y / PANGO_SCALE, width, height);
}
static gboolean
show_pango_rectangle (EText *text, PangoRectangle rect)
{
int x1 = rect.x / PANGO_SCALE;
int x2 = (rect.x + rect.width) / PANGO_SCALE;
int y1 = rect.y / PANGO_SCALE;
int y2 = (rect.y + rect.height) / PANGO_SCALE;
int new_xofs_edit = text->xofs_edit;
int new_yofs_edit = text->yofs_edit;
int clip_width, clip_height;
clip_width = text->clip_width;
if (clip_width >= 0 && text->draw_borders) {
clip_width -= 6;
if (clip_width < 0)
clip_width = 0;
}
clip_height = text->clip_height;
if (clip_height >= 0 && text->draw_borders) {
clip_height -= 6;
if (clip_height < 0)
clip_height = 0;
}
if (x1 < new_xofs_edit)
new_xofs_edit = x1;
if (y1 < new_yofs_edit)
new_yofs_edit = y1;
if (clip_width >= 0) {
if (2 + x2 - clip_width > new_xofs_edit)
new_xofs_edit = 2 + x2 - clip_width;
} else {
new_xofs_edit = 0;
}
if (clip_height >= 0) {
if (y2 - clip_height > new_yofs_edit)
new_yofs_edit = y2 - clip_height;
} else {
new_yofs_edit = 0;
}
if (new_xofs_edit < 0)
new_xofs_edit = 0;
if (new_yofs_edit < 0)
new_yofs_edit = 0;
if (new_xofs_edit != text->xofs_edit ||
new_yofs_edit != text->yofs_edit) {
text->xofs_edit = new_xofs_edit;
text->yofs_edit = new_yofs_edit;
return TRUE;
}
return FALSE;
}
/* 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;
int xpos, ypos;
GdkGC *main_gc;
GnomeCanvas *canvas;
GtkWidget *widget;
text = E_TEXT (item);
canvas = GNOME_CANVAS_ITEM(text)->canvas;
widget = GTK_WIDGET(canvas);
if (text->draw_background || text->draw_button) {
main_gc = widget->style->fg_gc[GTK_WIDGET_STATE (widget)];
} else {
main_gc = text->gc;
}
if (text->draw_borders || text->draw_background) {
gdouble thisx = item->x1 - x;
gdouble thisy = item->y1 - y;
gdouble thiswidth, thisheight;
GtkWidget *widget = GTK_WIDGET(item->canvas);
g_object_get(text,
"width", &thiswidth,
"height", &thisheight,
NULL);
if (text->draw_borders){
gtk_paint_shadow (widget->style, drawable,
GTK_STATE_NORMAL, GTK_SHADOW_IN,
NULL, widget, "entry",
thisx, thisy, thiswidth, thisheight);
}
if (text->draw_background) {
gtk_paint_flat_box (widget->style, drawable,
GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
NULL, widget, "entry_bg",
thisx + widget->style->xthickness,
thisy + widget->style->ythickness,
thiswidth - widget->style->xthickness * 2,
thisheight - widget->style->ythickness * 2);
}
}
if (text->draw_button) {
GtkWidget *widget;
int xoff = item->x1 - x;
int yoff = item->y1 - y;
widget = GTK_WIDGET (item->canvas);
xoff -= widget->allocation.x;
yoff -= widget->allocation.y;
widget = widget->parent;
while (widget && !GTK_IS_BUTTON(widget)) {
if (!GTK_WIDGET_NO_WINDOW (widget)) {
widget = NULL;
break;
}
widget = widget->parent;
}
if (widget) {
GtkButton *button = GTK_BUTTON (widget);
GtkShadowType shadow_type;
int thisx, thisy, thisheight, thiswidth;
int default_spacing;
GdkRectangle area;
area.x = 0;
area.y = 0;
area.width = width;
area.height = height;
#define DEFAULT_SPACING 7
#if 0
default_spacing = gtk_style_get_prop_experimental (widget->style,
"GtkButton::default_spacing",
DEFAULT_SPACING);
#endif
default_spacing = 7;
thisx = 0;
thisy = 0;
thiswidth = widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2;
thisheight = widget->allocation.height - GTK_CONTAINER (widget)->border_width * 2;
if (GTK_WIDGET_HAS_DEFAULT (widget) &&
GTK_BUTTON (widget)->relief == GTK_RELIEF_NORMAL)
{
gtk_paint_box (widget->style, drawable,
GTK_STATE_NORMAL, GTK_SHADOW_IN,
&area, widget, "buttondefault",
thisx + xoff, thisy + yoff, thiswidth, thisheight);
}
if (GTK_WIDGET_CAN_DEFAULT (widget)) {
thisx += widget->style->xthickness;
thisy += widget->style->ythickness;
thiswidth -= 2 * thisx + default_spacing;
thisheight -= 2 * thisy + default_spacing;
thisx += (1 + default_spacing) / 2;
thisy += (1 + default_spacing) / 2;
}
if (GTK_WIDGET_HAS_FOCUS (widget)) {
thisx += 1;
thisy += 1;
thiswidth -= 2;
thisheight -= 2;
}
if (GTK_WIDGET_STATE (widget) == GTK_STATE_ACTIVE)
shadow_type = GTK_SHADOW_IN;
else
shadow_type = GTK_SHADOW_OUT;
if ((button->relief != GTK_RELIEF_NONE) ||
((GTK_WIDGET_STATE(widget) != GTK_STATE_NORMAL) &&
(GTK_WIDGET_STATE(widget) != GTK_STATE_INSENSITIVE)))
gtk_paint_box (widget->style, drawable,
GTK_WIDGET_STATE (widget),
shadow_type, &area, widget, "button",
thisx + xoff, thisy + yoff, thiswidth, thisheight);
if (GTK_WIDGET_HAS_FOCUS (widget)) {
thisx -= 1;
thisy -= 1;
thiswidth += 2;
thisheight += 2;
gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget),
&area, widget, "button",
thisx + xoff, thisy + yoff, thiswidth - 1, thisheight - 1);
}
}
}
if (!text->text)
return;
if (text->stipple)
gnome_canvas_set_stipple_origin (item->canvas, main_gc);
xpos = text->text_cx;
ypos = text->text_cy;
xpos -= x;
ypos -= y;
clip_rect = NULL;
if (text->clip) {
rect.x = xpos;
rect.y = ypos;
rect.width = text->clip_cwidth;
rect.height = text->clip_cheight;
gdk_gc_set_clip_rectangle (main_gc, &rect);
clip_rect = &rect;
}
if (text->editing) {
xpos -= text->xofs_edit;
ypos -= text->yofs_edit;
}
gdk_draw_layout (drawable, main_gc,
xpos, ypos,
text->layout);
if (text->editing) {
if (text->selection_start != text->selection_end) {
PangoLayoutIter *iter;
GdkRegion *clip_region = gdk_region_new ();
GdkGC *selection_gc;
GdkGC *text_gc;
int start_index, end_index;
start_index = MIN (text->selection_start, text->selection_end);
end_index = MAX (text->selection_start, text->selection_end);
/* convert these into byte indices */
start_index = g_utf8_offset_to_pointer(text->text, start_index) - text->text;
end_index = g_utf8_offset_to_pointer(text->text, end_index) - text->text;
if (text->has_selection) {
selection_gc = widget->style->base_gc [GTK_STATE_SELECTED];
text_gc = widget->style->text_gc[GTK_STATE_SELECTED];
} else {
selection_gc = widget->style->base_gc [GTK_STATE_ACTIVE];
text_gc = widget->style->text_gc[GTK_STATE_ACTIVE];
}
gdk_gc_set_clip_rectangle (selection_gc, clip_rect);
iter = pango_layout_get_iter (text->layout);
do {
PangoLayoutLine *line = pango_layout_iter_get_line (iter);
gint n_ranges, i;
gint *ranges;
int y0, y1;
int s, e;
if (start_index < line->start_index + line->length
&& end_index > line->start_index) {
if (start_index <= line->start_index)
s = line->start_index;
else
s = start_index;
if (end_index > line->start_index + line->length)
e = line->start_index + line->length;
else
e = end_index;
pango_layout_line_get_x_ranges (line, s, e, &ranges, &n_ranges);
pango_layout_iter_get_line_yrange (iter, &y0, &y1);
for (i=0; i < n_ranges; i++) {
GdkRectangle sel_rect;
sel_rect.x = xpos + PANGO_PIXELS (ranges[2*i]);
sel_rect.y = ypos + PANGO_PIXELS (y0);
sel_rect.width = (ranges[2*i + 1] - ranges[2*i]) / PANGO_SCALE;
sel_rect.height = (y1 - y0 + PANGO_SCALE / 2) / PANGO_SCALE;
gdk_draw_rectangle (drawable, selection_gc, TRUE,
sel_rect.x, sel_rect.y, sel_rect.width, sel_rect.height);
gdk_region_union_with_rect (clip_region, &sel_rect);
}
g_free (ranges);
}
} while (pango_layout_iter_next_line (iter));
pango_layout_iter_free (iter);
if (clip_rect) {
GdkRegion *rect_region = gdk_region_rectangle (clip_rect);
gdk_region_intersect (clip_region, rect_region);
gdk_region_destroy (rect_region);
}
gdk_gc_set_clip_region (text_gc, clip_region);
gdk_draw_layout (drawable, text_gc,
xpos, ypos,
text->layout);
gdk_gc_set_clip_region (text_gc, NULL);
gdk_gc_set_clip_region (selection_gc, NULL);
gdk_region_destroy (clip_region);
} else {
if (text->show_cursor) {
PangoRectangle strong_pos, weak_pos;
char *offs = g_utf8_offset_to_pointer (text->text, text->selection_start);
pango_layout_get_cursor_pos (text->layout, offs - text->text, &strong_pos, &weak_pos);
draw_pango_rectangle (drawable, main_gc, xpos, ypos, strong_pos);
if (strong_pos.x != weak_pos.x ||
strong_pos.y != weak_pos.y ||
strong_pos.width != weak_pos.width ||
strong_pos.height != weak_pos.height)
draw_pango_rectangle (drawable, main_gc, xpos, ypos, weak_pos);
}
}
}
if (text->clip) {
gdk_gc_set_clip_rectangle (main_gc, NULL);
}
}
/* 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;
double clip_width;
double clip_height;
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->clip_width < 0)
clip_width = text->width;
else
clip_width = text->clip_width;
if ( text->clip_height < 0 )
clip_height = text->height;
else
clip_height = text->clip_height;
/* Get canvas pixel coordinates for clip rectangle position */
clip_width = clip_width * item->canvas->pixels_per_unit;
clip_height = clip_height * item->canvas->pixels_per_unit;
if (cx < text->clip_cx ||
cx > text->clip_cx + clip_width ||
cy < text->clip_cy ||
cy > text->clip_cy + clip_height)
return 1;
if (text->fill_clip_rectangle)
return 0;
cx -= text->cx;
if (pango_layout_xy_to_index (text->layout, cx, cy, NULL, NULL))
return 0;
return 1;
}
/* 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 = 0;
*y1 = 0;
width = text->width;
height = text->height;
if (text->clip) {
if (text->clip_width >= 0)
width = text->clip_width;
if ( text->clip_height >= 0 )
height = text->clip_height;
}
width = width / item->canvas->pixels_per_unit;
height = 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 gint
get_position_from_xy (EText *text, gint x, gint y)
{
int index;
int trailing;
if (text->draw_borders) {
x -= BORDER_INDENT;
y -= BORDER_INDENT;
}
x -= text->xofs;
y -= text->yofs;
if (text->editing) {
x += text->xofs_edit;
y += text->yofs_edit;
}
x -= text->cx;
y -= text->cy;
pango_layout_xy_to_index (text->layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, &trailing);
return g_utf8_pointer_to_offset (text->text, text->text + index + trailing);
}
#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, &current_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 && text->clip) {
int old_xofs_edit = text->xofs_edit;
int old_yofs_edit = text->yofs_edit;
if (text->clip_cwidth >= 0 &&
text->lastx - text->clip_cx > text->clip_cwidth &&
text->xofs_edit < text->width - text->clip_cwidth) {
text->xofs_edit += 4;
if (text->xofs_edit > text->width - text->clip_cwidth + 1)
text->xofs_edit = text->width - text->clip_cwidth + 1;
}
if (text->lastx - text->clip_cx < 0 &&
text->xofs_edit > 0) {
text->xofs_edit -= 4;
if (text->xofs_edit < 0)
text->xofs_edit = 0;
}
if (text->clip_cheight >= 0 &&
text->lasty - text->clip_cy > text->clip_cheight &&
text->yofs_edit < text->height - text->clip_cheight) {
text->yofs_edit += 4;
if (text->yofs_edit > text->height - text->clip_cheight + 1)
text->yofs_edit = text->height - text->clip_cheight + 1;
}
if (text->lasty - text->clip_cy < 0 &&
text->yofs_edit > 0) {
text->yofs_edit -= 4;
if (text->yofs_edit < 0)
text->yofs_edit = 0;
}
if (old_xofs_edit != text->xofs_edit ||
old_yofs_edit != text->yofs_edit) {
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;
redraw = TRUE;
}
}
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) {
text->needs_redraw = 1;
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text));
}
return TRUE;
}
static gboolean
tooltip_event(GtkWidget *tooltip, GdkEvent *event, EText *text)
{
gint ret_val = FALSE;
if (!text->model)
return FALSE;
switch (event->type) {
case GDK_LEAVE_NOTIFY:
e_canvas_hide_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(text)->canvas));
break;
case GDK_BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
if (event->type == GDK_BUTTON_RELEASE) {
e_canvas_hide_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(text)->canvas));
}
/* Forward events to the text item */
g_signal_emit_by_name (text, "event", event,
&ret_val);
if (!ret_val)
gtk_propagate_event (GTK_WIDGET(GNOME_CANVAS_ITEM(text)->canvas), event);
ret_val = TRUE;
default:
break;
}
return ret_val;
}
static void
tooltip_destroy(gpointer data, GObject *where_object_was)
{
EText *text = data;
text->tooltip_owner = FALSE;
g_object_unref (text);
}
static gboolean
_do_tooltip (gpointer data)
{
#warning "need to sort out tooltip stuff."
EText *text = E_TEXT (data);
struct line *lines;
GtkWidget *canvas;
int i;
int max_width;
gboolean cut_off;
double i2c[6];
ArtPoint origin = {0, 0};
ArtPoint pixel_origin;
int canvas_x, canvas_y;
GnomeCanvasItem *tooltip_text;
double tooltip_width;
double tooltip_height;
double tooltip_x;
double tooltip_y;
#if 0
double x1, x2, y1, y2;
#endif
GnomeCanvasItem *rect;
GtkWidget *tooltip_window; /* GtkWindow for displaying the tooltip */
text->tooltip_count = 0;
if (E_CANVAS(GNOME_CANVAS_ITEM(text)->canvas)->tooltip_window || text->editing || !text->num_lines) {
text->tooltip_timeout = 0;
return FALSE;
}
cut_off = FALSE;
for ( i = 0; i < text->num_lines; i++ ) {
PangoLayoutLine *line = pango_layout_get_line (text->layout, i);
PangoRectangle rect;
pango_layout_line_get_pixel_extents (line, &rect, NULL);
if (rect.width > text->clip_width) {
cut_off = TRUE;
break;
}
}
if ( ! cut_off ) {
text->tooltip_timeout = 0;
return FALSE;
}
gnome_canvas_item_i2c_affine(GNOME_CANVAS_ITEM(text), i2c);
art_affine_point (&pixel_origin, &origin, i2c);
gdk_window_get_origin (GTK_WIDGET(GNOME_CANVAS_ITEM(text)->canvas)->window, &canvas_x, &canvas_y);
pixel_origin.x += canvas_x;
pixel_origin.y += canvas_y;
pixel_origin.x -= (int) gtk_layout_get_hadjustment(GTK_LAYOUT(GNOME_CANVAS_ITEM(text)->canvas))->value;
pixel_origin.y -= (int) gtk_layout_get_vadjustment(GTK_LAYOUT(GNOME_CANVAS_ITEM(text)->canvas))->value;
tooltip_window = gtk_window_new (GTK_WINDOW_POPUP);
gtk_container_set_border_width (GTK_CONTAINER (tooltip_window), 1);
canvas = e_canvas_new ();
gtk_container_add (GTK_CONTAINER (tooltip_window), canvas);
/* Get the longest line length */
pango_layout_get_size (text->layout, &max_width, NULL);
rect = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)),
gnome_canvas_rect_get_type (),
"x1", (double) 0,
"y1", (double) 0,
"x2", (double) max_width + 4,
"y2", (double) text->height + 4,
"fill_color", "light gray",
NULL);
tooltip_text = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)),
e_text_get_type (),
"anchor", GTK_ANCHOR_NW,
"bold", text->bold,
"strikeout", text->strikeout,
"text", text->text,
"editable", FALSE,
"clip_width", text->max_lines != 1 ? text->clip_width : max_width,
"clip_height", text->max_lines != 1 ? -1 : (double)text->height,
"clip", TRUE,
"line_wrap", text->line_wrap,
"justification", text->justification,
NULL);
if (text->draw_borders)
e_canvas_item_move_absolute(tooltip_text, 1 + BORDER_INDENT, 1 + BORDER_INDENT);
else
e_canvas_item_move_absolute(tooltip_text, 1, 1);
create_layout (E_TEXT (tooltip_text));
split_into_lines (E_TEXT(tooltip_text));
calc_height (E_TEXT(tooltip_text));
gnome_canvas_item_set (tooltip_text,
"clip_height", (double) E_TEXT(tooltip_text)->height,
"clip_width", (double) E_TEXT(tooltip_text)->width,
NULL);
tooltip_width = E_TEXT(tooltip_text)->width;
tooltip_height = E_TEXT(tooltip_text)->height;
tooltip_x = 0;
tooltip_y = 0;
switch(E_TEXT(tooltip_text)->justification) {
case GTK_JUSTIFY_CENTER:
tooltip_x = - tooltip_width / 2;
break;
case GTK_JUSTIFY_RIGHT:
tooltip_x = tooltip_width / 2;
break;
case GTK_JUSTIFY_FILL:
case GTK_JUSTIFY_LEFT:
tooltip_x = 0;
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:
tooltip_y -= tooltip_height / 2.0;
break;
case GTK_ANCHOR_SW:
case GTK_ANCHOR_S:
case GTK_ANCHOR_SE:
tooltip_y -= tooltip_height;
break;
}
switch(E_TEXT(tooltip_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:
tooltip_x -= tooltip_width / 2.0;
break;
case GTK_ANCHOR_NE:
case GTK_ANCHOR_E:
case GTK_ANCHOR_SE:
tooltip_x -= tooltip_width;
break;
}
gnome_canvas_item_set(rect,
"x2", (double) tooltip_width + 4 + (text->draw_borders ? BORDER_INDENT * 2 : 0),
"y2", (double) tooltip_height + 4 + (text->draw_borders ? BORDER_INDENT * 2 : 0),
NULL);
gtk_widget_show (canvas);
gtk_widget_realize (tooltip_window);
gtk_widget_set_usize (tooltip_window,
tooltip_width + 4 + (text->draw_borders ? BORDER_INDENT * 2 : 0),
tooltip_height + 4 + (text->draw_borders ? BORDER_INDENT * 2 : 0));
gnome_canvas_set_scroll_region (GNOME_CANVAS(canvas), 0.0, 0.0,
tooltip_width + (text->draw_borders ? BORDER_INDENT * 2 : 0),
(double)tooltip_height + (text->draw_borders ? BORDER_INDENT * 2 : 0));
g_signal_connect (tooltip_window, "event",
G_CALLBACK(tooltip_event), text);
g_object_weak_ref (G_OBJECT (tooltip_window),
tooltip_destroy, text);
g_object_ref (text);
e_canvas_popup_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(text)->canvas),
tooltip_window,
pixel_origin.x - 2 + tooltip_x,
pixel_origin.y - 2 + tooltip_y);
text->tooltip_owner = TRUE;
text->tooltip_timeout = 0;
return FALSE;
}
static void
start_editing (EText *text)
{
if (text->editing)
return;
g_free (text->revert);
text->revert = g_strdup (text->text);
text->editing = TRUE;
if (text->pointer_in) {
if (text->default_cursor_shown && (!text->draw_borders)) {
gdk_window_set_cursor (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas)->window, text->i_cursor);
text->default_cursor_shown = FALSE;
}
}
text->select_by_word = FALSE;
text->xofs_edit = 0;
text->yofs_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);
}
void
e_text_stop_editing (EText *text)
{
if (!text->editing)
return;
g_free (text->revert);
text->revert = NULL;
text->editing = FALSE;
if ( (!text->default_cursor_shown) && (!text->draw_borders) ) {
gdk_window_set_cursor (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas)->window, text->default_cursor);
text->default_cursor_shown = TRUE;
}
if (text->timer) {
g_timer_stop(text->timer);
g_timer_destroy(text->timer);
text->timer = NULL;
}
}
void
e_text_cancel_editing (EText *text)
{
if (text->revert)
e_text_model_set_text(text->model, text->revert);
e_text_stop_editing (text);
}
static gboolean
_click (gpointer data)
{
*(gint *)data = 0;
return FALSE;
}
static gint
e_text_event (GnomeCanvasItem *item, GdkEvent *event)
{
EText *text = E_TEXT(item);
ETextEventProcessorEvent e_tep_event;
gint return_val = 0;
if (!text->model)
return FALSE;
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->im_context) {
if (!text->im_context_signals_registered) {
g_signal_connect (text->im_context, "commit",
G_CALLBACK (e_text_commit_cb), text);
g_signal_connect (text->im_context, "retrieve_surrounding",
G_CALLBACK (e_text_retrieve_surrounding_cb), text);
g_signal_connect (text->im_context, "delete_surrounding",
G_CALLBACK (e_text_delete_surrounding_cb), text);
text->im_context_signals_registered = TRUE;
}
}
start_editing (text);
text->show_cursor = FALSE; /* so we'll redraw and the cursor will be shown */
} else {
if (text->im_context) {
g_signal_handlers_disconnect_matched (text->im_context,
G_SIGNAL_MATCH_DATA,
0, 0, NULL,
NULL, text);
text->im_context_signals_registered = FALSE;
}
e_text_stop_editing (text);
if (text->timeout_id) {
g_source_remove(text->timeout_id);
text->timeout_id = 0;
}
if (text->show_cursor || text->draw_borders) {
text->show_cursor = FALSE;
text->needs_redraw = 1;
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text));
}
}
if ( text->line_wrap )
text->needs_split_into_lines = 1;
e_canvas_item_request_reflow (GNOME_CANVAS_ITEM(text));
}
return_val = 0;
break;
case GDK_KEY_PRESS: /* Fall Through */
case GDK_KEY_RELEASE:
if (text->editing) {
GdkEventKey key;
gint ret;
if (text->im_context && gtk_im_context_filter_keypress (text->im_context, (GdkEventKey*)event)) {
text->need_im_reset = TRUE;
return 1;
}
key = event->key;
e_tep_event.key.time = key.time;
e_tep_event.key.state = key.state;
e_tep_event.key.keyval = key.keyval;
/* This is probably ugly hack, but we have to handle UTF-8 input somehow */
#if 0
e_tep_event.key.length = key.length;
e_tep_event.key.string = key.string;
#else
e_tep_event.key.string = e_utf8_from_gtk_event_key (GTK_WIDGET (item->canvas), key.keyval, key.string);
if (e_tep_event.key.string != NULL) {
e_tep_event.key.length = strlen (e_tep_event.key.string);
} else {
e_tep_event.key.length = 0;
}
#endif
_get_tep(text);
ret = e_text_event_processor_handle_event (text->tep, &e_tep_event);
if (event->type == GDK_KEY_PRESS)
g_signal_emit (text, e_text_signals[E_TEXT_KEYPRESS], 0,
e_tep_event.key.keyval, e_tep_event.key.state);
if (e_tep_event.key.string)
g_free (e_tep_event.key.string);
return ret;
}
break;
case GDK_BUTTON_PRESS: /* Fall Through */
case GDK_BUTTON_RELEASE:
if (text->tooltip_timeout) {
gtk_timeout_remove (text->tooltip_timeout);
text->tooltip_timeout = 0;
}
e_canvas_hide_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(text)->canvas));
#if 0
if ((!text->editing)
&& text->editable
&& event->type == GDK_BUTTON_RELEASE
&& event->button.button == 1) {
GdkEventButton button = event->button;
e_canvas_item_grab_focus (item, TRUE);
e_tep_event.type = GDK_BUTTON_RELEASE;
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);
e_tep_event.type = GDK_BUTTON_RELEASE;
}
#else
if ((!text->editing)
&& text->editable
&& (event->button.button == 1 ||
event->button.button == 2)) {
e_canvas_item_grab_focus (item, TRUE);
start_editing (text);
}
#endif
/* We follow convention and emit popup events on right-clicks. */
if (event->type == GDK_BUTTON_PRESS && event->button.button == 3) {
if (text->handle_popup) {
e_text_do_popup (text, &(event->button),
get_position_from_xy (text, event->button.x, event->button.y));
return TRUE;
}
else {
break;
}
}
/* Create our own double and triple click events,
as gnome-canvas doesn't forward them to us */
if (event->type == GDK_BUTTON_PRESS) {
if (text->dbl_timeout == 0 &&
text->tpl_timeout == 0) {
text->dbl_timeout = gtk_timeout_add (200,
_click,
&(text->dbl_timeout));
} else {
if (text->tpl_timeout == 0) {
e_tep_event.type = GDK_2BUTTON_PRESS;
text->tpl_timeout = gtk_timeout_add (200, _click, &(text->tpl_timeout));
} else {
e_tep_event.type = GDK_3BUTTON_PRESS;
}
}
}
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;
}
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:
{
if ( text->tooltip_count == 0 && text->clip) {
if (!text->tooltip_timeout)
text->tooltip_timeout = gtk_timeout_add (1000, _do_tooltip, text);
}
text->tooltip_count ++;
}
text->pointer_in = TRUE;
if (text->editing || text->draw_borders) {
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:
if (text->tooltip_count > 0)
text->tooltip_count --;
if ( text->tooltip_count == 0 && text->clip) {
if ( text->tooltip_timeout ) {
gtk_timeout_remove (text->tooltip_timeout);
text->tooltip_timeout = 0;
}
}
text->pointer_in = FALSE;
if (text->editing || text->draw_borders) {
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;
}
void
e_text_copy_clipboard (EText *text)
{
gint selection_start_pos;
gint selection_end_pos;
char *str;
selection_start_pos = MIN (text->selection_start, text->selection_end);
selection_end_pos = MAX (text->selection_start, text->selection_end);
/* convert sel_start/sel_end to byte indices */
selection_start_pos = g_utf8_offset_to_pointer (text->text, selection_start_pos) - text->text;
selection_end_pos = g_utf8_offset_to_pointer (text->text, selection_end_pos) - text->text;
str = g_strndup (text->text + selection_start_pos,
selection_end_pos - selection_start_pos);
gtk_clipboard_set_text (
#ifdef GTK_2_2
gtk_widget_get_clipboard (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
GDK_SELECTION_CLIPBOARD),
#else
gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
#endif
str, -1);
g_free (str);
}
void
e_text_delete_selection(EText *text)
{
int sel_start, sel_end;
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_model_delete(text->model, sel_start, sel_end - sel_start);
}
void
e_text_cut_clipboard (EText *text)
{
e_text_copy_clipboard (text);
e_text_delete_selection (text);
}
void
e_text_paste_clipboard (EText *text)
{
ETextEventProcessorCommand command;
command.action = E_TEP_PASTE;
command.position = E_TEP_SELECTION;
command.string = "";
command.value = 0;
e_text_command(text->tep, &command, text);
}
void
e_text_select_all (EText *text)
{
ETextEventProcessorCommand command;
command.action = E_TEP_SELECT;
command.position = E_TEP_SELECT_ALL;
command.string = "";
command.value = 0;
e_text_command(text->tep, &command, text);
}
static void
primary_get_cb (GtkClipboard *clipboard,
GtkSelectionData *selection_data,
guint info,
gpointer data)
{
EText *text = E_TEXT (data);
int sel_start, sel_end;
sel_start = MIN(text->selection_start, text->selection_end);
sel_end = MAX(text->selection_start, text->selection_end);
/* convert sel_start/sel_end to byte indices */
sel_start = g_utf8_offset_to_pointer (text->text, sel_start) - text->text;
sel_end = g_utf8_offset_to_pointer (text->text, sel_end) - text->text;
if (sel_start != sel_end) {
gchar *str = g_strndup (text->text + sel_start,
sel_end - sel_start);
gtk_selection_data_set_text (selection_data, str, -1);
g_free (str);
}
}
static void
primary_clear_cb (GtkClipboard *clipboard,
gpointer data)
{
EText *text = E_TEXT (data);
#if notyet
/* XXX */
gtk_editable_select_region (GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);
#endif
}
static void
e_text_update_primary_selection (EText *text)
{
static const GtkTargetEntry targets[] = {
{ "UTF8_STRING", 0, 0 },
{ "UTF-8", 0, 0 },
{ "STRING", 0, 0 },
{ "TEXT", 0, 0 },
{ "COMPOUND_TEXT", 0, 0 }
};
GtkClipboard *clipboard;
#ifdef GTK_2_2
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas), GDK_SELECTION_PRIMARY);
#else
clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
#endif
if (text->selection_start != text->selection_end) {
if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
primary_get_cb, primary_clear_cb, G_OBJECT (text)))
primary_clear_cb (clipboard, text);
}
else {
if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (text))
gtk_clipboard_clear (clipboard);
}
}
static void
paste_received (GtkClipboard *clipboard,
const gchar *text,
gpointer data)
{
EText *etext = E_TEXT (data);
if (text && g_utf8_validate (text, strlen (text), NULL)) {
if (etext->selection_end != etext->selection_start)
e_text_delete_selection (etext);
e_text_insert (etext, text);
}
g_object_unref (etext);
}
static void
e_text_paste (EText *text, GdkAtom selection)
{
g_object_ref (text);
gtk_clipboard_request_text (
#ifdef GTK_2_2
gtk_widget_get_clipboard (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
selection),
#else
gtk_clipboard_get (selection),
#endif
paste_received, text);
}
typedef struct {
EText *text;
GdkEventButton *button;
int position;
} PopupClosure;
static void
popup_menu_detach (GtkWidget *attach_widget,
GtkMenu *menu)
{
}
static void
popup_targets_received (GtkClipboard *clipboard,
GtkSelectionData *data,
gpointer user_data)
{
PopupClosure *closure = user_data;
EText *text = closure->text;
GdkEventButton *button = closure->button;
int position = closure->position;
GtkWidget *popup_menu = gtk_menu_new ();
GtkWidget *menuitem, *submenu;
g_free (closure);
gtk_menu_attach_to_widget (GTK_MENU (popup_menu),
GTK_WIDGET(GNOME_CANVAS_ITEM (text)->canvas),
popup_menu_detach);
/* cut menu item */
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_cut_clipboard), text);
gtk_widget_set_sensitive (menuitem, text->editable && (text->selection_start != text->selection_end));
/* copy menu item */
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_copy_clipboard), text);
gtk_widget_set_sensitive (menuitem, text->selection_start != text->selection_end);
/* paste menu item */
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PASTE, NULL);
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_paste_clipboard), text);
gtk_widget_set_sensitive (menuitem, text->editable && gtk_selection_data_targets_include_text (data));
menuitem = gtk_menu_item_new_with_label (_("Select All"));
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
g_signal_connect_swapped (menuitem, "activate",
G_CALLBACK (e_text_select_all), text);
gtk_widget_set_sensitive (menuitem, strlen (text->text) > 0);
menuitem = gtk_separator_menu_item_new ();
gtk_widget_show (menuitem);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
if (text->im_context && GTK_IS_IM_MULTICONTEXT (text->im_context)) {
menuitem = gtk_menu_item_new_with_label (_("Input Methods"));
gtk_widget_show (menuitem);
submenu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
gtk_im_multicontext_append_menuitems (GTK_IM_MULTICONTEXT (text->im_context),
GTK_MENU_SHELL (submenu));
}
g_signal_emit (text,
e_text_signals[E_TEXT_POPULATE_POPUP],
0,
button, position,
popup_menu);
gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL,
NULL, NULL,
button->button, GDK_CURRENT_TIME);
g_object_unref (text);
}
static void
e_text_do_popup (EText *text, GdkEventButton *button, int position)
{
PopupClosure *closure = g_new (PopupClosure, 1);
closure->text = text;
g_object_ref (closure->text);
closure->button = button;
closure->position = position;
gtk_clipboard_request_contents (
#ifdef GTK_2_2
gtk_widget_get_clipboard (GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
GDK_SELECTION_CLIPBOARD),
#else
gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
#endif
gdk_atom_intern ("TARGETS", FALSE),
popup_targets_received,
closure);
}
static void
e_text_reset_im_context (EText *text)
{
if (text->need_im_reset) {
text->need_im_reset = 0;
gtk_im_context_reset (text->im_context);
}
}
/* fixme: */
static int
next_word (EText *text, int start)
{
char *p = g_utf8_offset_to_pointer (text->text, start);
int length;
length = g_utf8_strlen (text->text, -1);
if (start >= length) {
return length;
} else {
p = g_utf8_next_char (p);
start++;
while (p && *p) {
gunichar unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
return start + 1;
}
else {
p = g_utf8_next_char (p);
start++;
}
}
}
return g_utf8_pointer_to_offset (text->text, p);
}
static int
find_offset_into_line (EText *text, int offset_into_text, char **start_of_line)
{
char *p;
p = g_utf8_offset_to_pointer (text->text, offset_into_text);
if (p == text->text) {
if (start_of_line)
*start_of_line = (char*)text->text;
return 0;
}
else {
p = g_utf8_find_prev_char (text->text, p);
while (p && p > text->text) {
if (*p == '\n') {
if (start_of_line)
*start_of_line = p+1;
return offset_into_text - g_utf8_pointer_to_offset (text->text, p + 1);
}
p = g_utf8_find_prev_char (text->text, p);
}
if (start_of_line)
*start_of_line = (char*)text->text;
return offset_into_text;
}
}
static int
_get_position(EText *text, ETextEventProcessorCommand *command)
{
int length, obj_num;
gunichar unival;
char *p = NULL;
gint new_pos = 0;
int index, trailing;
switch (command->position) {
case E_TEP_VALUE:
new_pos = command->value;
break;
case E_TEP_SELECTION:
new_pos = text->selection_end;
break;
case E_TEP_START_OF_BUFFER:
new_pos = 0;
break;
case E_TEP_END_OF_BUFFER:
new_pos = strlen (text->text);
break;
case E_TEP_START_OF_LINE:
if (text->selection_end >= 1) {
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
if (p != text->text) {
p = g_utf8_find_prev_char (text->text, p);
while (p && p > text->text) {
if (*p == '\n') {
new_pos = g_utf8_pointer_to_offset (text->text, p) + 1;
break;
}
p = g_utf8_find_prev_char (text->text, p);
}
}
}
break;
case E_TEP_END_OF_LINE:
new_pos = -1;
length = g_utf8_strlen (text->text, -1);
if (text->selection_end >= length) {
new_pos = length;
} else {
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
while (p && *p) {
if (*p == '\n') {
new_pos = g_utf8_pointer_to_offset (text->text, p);
p = NULL;
} else
p = g_utf8_next_char (p);
}
}
if (new_pos == -1)
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
case E_TEP_FORWARD_CHARACTER:
length = g_utf8_strlen (text->text, -1);
if (text->selection_end >= length)
new_pos = length;
else
new_pos = text->selection_end + 1;
break;
case E_TEP_BACKWARD_CHARACTER:
new_pos = 0;
if (text->selection_end >= 1) {
new_pos = text->selection_end - 1;
}
break;
case E_TEP_FORWARD_WORD:
new_pos = next_word (text, text->selection_end);
break;
case E_TEP_BACKWARD_WORD:
new_pos = 0;
if (text->selection_end >= 1) {
int pos = text->selection_end;
p = g_utf8_find_prev_char (text->text, g_utf8_offset_to_pointer (text->text, text->selection_end));
pos --;
if (p != text->text) {
p = g_utf8_find_prev_char (text->text, p);
pos --;
while (p && p > text->text) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
new_pos = pos + 1;
p = NULL;
}
else {
p = g_utf8_find_prev_char (text->text, p);
pos --;
}
}
}
}
break;
case E_TEP_FORWARD_LINE: {
int l;
PangoLayoutLine *line, *next_line;
int offset_into_line;
int next_line_length;
char *p;
offset_into_line = find_offset_into_line (text, text->selection_end, NULL);
if (offset_into_line == -1)
return text->selection_end;
/* now we search forward til we hit a \n, and then
offset_into_line more characters */
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
while (p && *p) {
if (*p == '\n')
break;
p = g_utf8_next_char (p);
}
if (p && *p == '\n') {
/* now we loop forward offset_into_line
characters, or until we hit \n or \0 */
p = g_utf8_next_char (p);
while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
p = g_utf8_next_char (p);
offset_into_line --;
}
}
/* at this point, p points to the new location,
convert it to an offset and we're done */
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
}
case E_TEP_BACKWARD_LINE: {
char *p, *prev = NULL;
int offset_into_line = find_offset_into_line (text, text->selection_end, &p);
if (offset_into_line == -1)
return text->selection_end;
/* p points to the first character on our line. if we
have a \n before it, skip it and scan til we hit
the next one */
if (p != text->text) {
p = g_utf8_find_prev_char (text->text, p);
if (*p == '\n') {
p = g_utf8_find_prev_char (text->text, p);
while (p > text->text) {
if (*p == '\n') {
p ++;
break;
}
p = g_utf8_find_prev_char (text->text, p);
}
}
}
/* at this point 'p' points to the start of the
previous line, move forward 'offset_into_line'
times. */
while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
p = g_utf8_next_char (p);
offset_into_line --;
}
/* at this point, p points to the new location,
convert it to an offset and we're done */
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
}
case E_TEP_SELECT_WORD:
/* This is a silly hack to cause double-clicking on an object
to activate that object.
(Normally, double click == select word, which is why this is here.) */
obj_num = e_text_model_get_object_at_offset (text->model, text->selection_start);
if (obj_num != -1) {
e_text_model_activate_nth_object (text->model, obj_num);
new_pos = text->selection_start;
break;
}
if (text->selection_end < 1) {
new_pos = 0;
break;
}
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
p = g_utf8_find_prev_char (text->text, p);
while (p && p > text->text) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
p = g_utf8_next_char (p);
break;
}
p = g_utf8_find_prev_char (text->text, p);
}
if (!p)
text->selection_start = 0;
else
text->selection_start = g_utf8_pointer_to_offset (text->text, p);
text->selection_start = e_text_model_validate_position (text->model, text->selection_start);
length = g_utf8_strlen (text->text, -1);
if (text->selection_end >= length) {
new_pos = length;
break;
}
p = g_utf8_offset_to_pointer (text->text, text->selection_end);
while (p && *p) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
new_pos = g_utf8_pointer_to_offset (text->text, p);
break;
} else
p = g_utf8_next_char (p);
}
if (!new_pos)
new_pos = g_utf8_strlen (text->text, -1);
return new_pos;
case E_TEP_SELECT_ALL:
text->selection_start = 0;
new_pos = g_utf8_strlen (text->text, -1);
break;
case E_TEP_FORWARD_PARAGRAPH:
case E_TEP_BACKWARD_PARAGRAPH:
case E_TEP_FORWARD_PAGE:
case E_TEP_BACKWARD_PAGE:
new_pos = text->selection_end;
break;
default:
new_pos = text->selection_end;
break;
}
new_pos = e_text_model_validate_position (text->model, new_pos);
return new_pos;
}
static void
e_text_insert(EText *text, const char *string)
{
int len = strlen (string);
if (len > 0) {
int utf8len = 0;
if (!text->allow_newlines) {
const char *i;
char *new_string = g_malloc (len + 1);
char *j = new_string;
for (i = string; *i; i = g_utf8_next_char(i)) {
if (*i != '\n') {
gunichar c;
int charlen;
c = g_utf8_get_char (i);
charlen = g_unichar_to_utf8 (c, j);
j += charlen;
utf8len++;
}
}
*j = 0;
e_text_model_insert_length(text->model, text->selection_start, new_string, utf8len);
g_free (new_string);
}
else {
utf8len = g_utf8_strlen (string, -1);
e_text_model_insert_length(text->model, text->selection_start, string, utf8len);
}
}
}
static void
capitalize (EText *text, int start, int end, ETextEventProcessorCaps type)
{
gboolean first = TRUE;
const char *p = g_utf8_offset_to_pointer (text->text, start);
const char *text_end = g_utf8_offset_to_pointer (text->text, end);
int utf8len = text_end - p;
char *new_text = g_new0 (char, utf8len * 6);
char *output = new_text;
while (p && *p && p < text_end) {
gunichar unival = g_utf8_get_char (p);
gunichar newval = unival;
switch (type) {
case E_TEP_CAPS_UPPER:
newval = g_unichar_toupper (unival);
break;
case E_TEP_CAPS_LOWER:
newval = g_unichar_tolower (unival);
break;
case E_TEP_CAPS_TITLE:
if (g_unichar_isalpha (unival)) {
if (first)
newval = g_unichar_totitle (unival);
else
newval = g_unichar_tolower (unival);
first = FALSE;
} else {
first = TRUE;
}
break;
}
g_unichar_to_utf8 (newval, output);
output = g_utf8_next_char (output);
p = g_utf8_next_char (p);
}
*output = 0;
e_text_model_delete (text->model, start, utf8len);
e_text_model_insert_length (text->model, start, new_text, utf8len);
g_free (new_text);
}
static void
e_text_command(ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data)
{
EText *text = E_TEXT(data);
int sel_start, sel_end;
gboolean scroll = TRUE;
gboolean use_start = TRUE;
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);
}
use_start = TRUE;
break;
case E_TEP_SELECT:
text->selection_start = e_text_model_validate_position (text->model, text->selection_start); /* paranoia */
text->selection_end = _get_position(text, command);
e_text_update_primary_selection (text);
use_start = FALSE;
break;
case E_TEP_DELETE:
if (text->selection_end == text->selection_start) {
text->selection_end = _get_position(text, command);
}
e_text_delete_selection(text);
if (text->timer) {
g_timer_reset(text->timer);
}
use_start = FALSE;
break;
case E_TEP_INSERT:
if (g_utf8_validate (command->string, command->value, NULL)) {
if (text->selection_end != text->selection_start) {
e_text_delete_selection(text);
}
e_text_insert(text, command->string);
if (text->timer) {
g_timer_reset(text->timer);
}
}
break;
case E_TEP_COPY:
e_text_copy_clipboard (text);
if (text->timer) {
g_timer_reset(text->timer);
}
scroll = FALSE;
break;
case E_TEP_PASTE:
e_text_paste (text, GDK_NONE);
if (text->timer) {
g_timer_reset(text->timer);
}
break;
case E_TEP_GET_SELECTION:
e_text_paste (text, GDK_SELECTION_PRIMARY);
break;
case E_TEP_ACTIVATE:
g_signal_emit (text, e_text_signals[E_TEXT_ACTIVATE], 0);
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:
e_canvas_item_grab (E_CANVAS (GNOME_CANVAS_ITEM(text)->canvas),
GNOME_CANVAS_ITEM(text),
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
text->i_cursor,
command->time,
NULL,
NULL);
scroll = FALSE;
break;
case E_TEP_UNGRAB:
e_canvas_item_ungrab (E_CANVAS (GNOME_CANVAS_ITEM(text)->canvas),
GNOME_CANVAS_ITEM(text),
command->time);
scroll = FALSE;
break;
case E_TEP_CAPS:
if (text->selection_start == text->selection_end) {
capitalize (text, text->selection_start, next_word (text, text->selection_start), command->value);
} else {
int selection_start = MIN (text->selection_start, text->selection_end);
int selection_end = MAX (text->selection_start, text->selection_end);
capitalize (text, selection_start, selection_end, command->value);
}
break;
case E_TEP_NOP:
scroll = FALSE;
break;
}
if (scroll && !text->button_down) {
/* XXX do we really need the @trailing logic here? if
we don't we can scrap the loop and just use
pango_layout_index_to_pos */
int i;
PangoLayoutLine *cur_line = NULL;
int selection_index;
PangoLayoutIter *iter = pango_layout_get_iter (text->layout);
selection_index = use_start ? text->selection_start : text->selection_end;
/* convert to a byte index */
selection_index = g_utf8_offset_to_pointer (text->text, selection_index) - text->text;
do {
PangoLayoutLine *line = pango_layout_iter_get_line (iter);
if (selection_index >= line->start_index && selection_index <= line->start_index + line->length) {
/* found the line with the start of the selection */
cur_line = line;
break;
}
} while (pango_layout_iter_next_line (iter));
if (cur_line) {
int xpos, ypos;
double clip_width, clip_height;
gboolean trailing = FALSE;
PangoRectangle pango_pos;
if (selection_index > 0 && selection_index == cur_line->start_index + cur_line->length) {
selection_index--;
trailing = TRUE;
}
pango_layout_index_to_pos (text->layout, selection_index, &pango_pos);
pango_pos.x = PANGO_PIXELS (pango_pos.x);
pango_pos.y = PANGO_PIXELS (pango_pos.y);
pango_pos.width = (pango_pos.width + PANGO_SCALE / 2) / PANGO_SCALE;
pango_pos.height = (pango_pos.height + PANGO_SCALE / 2) / PANGO_SCALE;
/* scroll for X */
xpos = pango_pos.x; /* + (trailing ? 0 : pango_pos.width);*/
if (xpos + 2 < text->xofs_edit) {
text->xofs_edit = xpos;
}
clip_width = text->clip_width;
if (clip_width >= 0 && text->draw_borders) {
clip_width -= 6;
if (clip_width < 0)
clip_width = 0;
}
if (xpos + pango_pos.width - clip_width > text->xofs_edit) {
text->xofs_edit = xpos + pango_pos.width - clip_width;
}
/* scroll for Y */
if (pango_pos.y + 2 < text->yofs_edit) {
ypos = pango_pos.y;
text->yofs_edit = ypos;
}
else {
ypos = pango_pos.y + pango_pos.height;
}
if ( text->clip_height < 0 )
clip_height = text->height;
else
clip_height = text->clip_height;
if (clip_height >= 0 && text->draw_borders) {
clip_height -= 6;
if (clip_height < 0)
clip_height = 0;
}
if (ypos - clip_height > text->yofs_edit) {
text->yofs_edit = ypos - clip_height;
}
}
pango_layout_iter_free (iter);
}
text->needs_redraw = 1;
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text));
}
/* Class initialization function for the text item */
static void
e_text_class_init (ETextClass *klass)
{
GObjectClass *gobject_class;
GnomeCanvasItemClass *item_class;
gobject_class = (GObjectClass *) klass;
item_class = (GnomeCanvasItemClass *) klass;
parent_class = g_type_class_ref (PARENT_TYPE);
gobject_class->dispose = e_text_dispose;
gobject_class->set_property = e_text_set_property;
gobject_class->get_property = e_text_get_property;
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->event = e_text_event;
klass->changed = NULL;
klass->activate = NULL;
e_text_signals[E_TEXT_CHANGED] =
g_signal_new ("changed",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, changed),
NULL, NULL,
e_marshal_NONE__NONE,
G_TYPE_NONE, 0);
e_text_signals[E_TEXT_ACTIVATE] =
g_signal_new ("activate",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, activate),
NULL, NULL,
e_marshal_NONE__NONE,
G_TYPE_NONE, 0);
e_text_signals[E_TEXT_KEYPRESS] =
g_signal_new ("keypress",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, keypress),
NULL, NULL,
e_marshal_NONE__INT_INT,
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
e_text_signals[E_TEXT_POPULATE_POPUP] =
g_signal_new ("populate_popup",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ETextClass, populate_popup),
NULL, NULL,
e_marshal_NONE__POINTER_INT_OBJECT,
G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_INT, GTK_TYPE_MENU);
g_object_class_install_property (gobject_class, PROP_MODEL,
g_param_spec_object ("model",
_( "Model" ),
_( "Model" ),
E_TYPE_TEXT_MODEL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_EVENT_PROCESSOR,
g_param_spec_object ("event_processor",
_( "Event Processor" ),
_( "Event Processor" ),
E_TEXT_EVENT_PROCESSOR_TYPE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TEXT,
g_param_spec_string ("text",
_( "Text" ),
_( "Text" ),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_BOLD,
g_param_spec_boolean ("bold",
_( "Bold" ),
_( "Bold" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_STRIKEOUT,
g_param_spec_boolean ("strikeout",
_( "Strikeout" ),
_( "Strikeout" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_ANCHOR,
g_param_spec_enum ("anchor",
_( "Anchor" ),
_( "Anchor" ),
GTK_TYPE_ANCHOR_TYPE, GTK_ANCHOR_CENTER,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_JUSTIFICATION,
g_param_spec_enum ("justification",
_( "Justification" ),
_( "Justification" ),
GTK_TYPE_JUSTIFICATION, GTK_JUSTIFY_LEFT,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CLIP_WIDTH,
g_param_spec_double ("clip_width",
_( "Clip Width" ),
_( "Clip Width" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CLIP_HEIGHT,
g_param_spec_double ("clip_height",
_( "Clip Height" ),
_( "Clip Height" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CLIP,
g_param_spec_boolean ("clip",
_( "Clip" ),
_( "Clip" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_CLIP_RECTANGLE,
g_param_spec_boolean ("fill_clip_rectangle",
_( "Fill clip rectangle" ),
_( "Fill clip rectangle" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_X_OFFSET,
g_param_spec_double ("x_offset",
_( "X Offset" ),
_( "X Offset" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_Y_OFFSET,
g_param_spec_double ("y_offset",
_( "Y Offset" ),
_( "Y Offset" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_COLOR,
g_param_spec_string ("fill_color",
_( "Fill color" ),
_( "Fill color" ),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_COLOR_GDK,
g_param_spec_boxed ("fill_color_gdk",
_( "GDK fill color" ),
_( "GDK fill color" ),
GDK_TYPE_COLOR,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_COLOR_RGBA,
g_param_spec_uint ("fill_color_rgba",
_( "GDK fill color" ),
_( "GDK fill color" ),
0, G_MAXUINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_FILL_STIPPLE,
g_param_spec_object ("fill_stipple",
_( "Fill stipple" ),
_( "Fill stipple" ),
GDK_TYPE_DRAWABLE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TEXT_WIDTH,
g_param_spec_double ("text_width",
_( "Text width" ),
_( "Text width" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_TEXT_HEIGHT,
g_param_spec_double ("text_height",
_( "Text height" ),
_( "Text height" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_EDITABLE,
g_param_spec_boolean ("editable",
_( "Editable" ),
_( "Editable" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_USE_ELLIPSIS,
g_param_spec_boolean ("use_ellipsis",
_( "Use ellipsis" ),
_( "Use ellipsis" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_ELLIPSIS,
g_param_spec_string ("ellipsis",
_( "Ellipsis" ),
_( "Ellipsis" ),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_LINE_WRAP,
g_param_spec_boolean ("line_wrap",
_( "Line wrap" ),
_( "Line wrap" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_BREAK_CHARACTERS,
g_param_spec_string ("break_characters",
_( "Break characters" ),
_( "Break characters" ),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_MAX_LINES,
g_param_spec_int ("max_lines",
_( "Max lines" ),
_( "Max lines" ),
0, G_MAXINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_WIDTH,
g_param_spec_double ("width",
_( "Width" ),
_( "Width" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_HEIGHT,
g_param_spec_double ("height",
_( "Height" ),
_( "Height" ),
0.0, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_DRAW_BORDERS,
g_param_spec_boolean ("draw_borders",
_( "Draw borders" ),
_( "Draw borders" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_ALLOW_NEWLINES,
g_param_spec_boolean ("allow_newlines",
_( "Allow newlines" ),
_( "Allow newlines" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_DRAW_BACKGROUND,
g_param_spec_boolean ("draw_background",
_( "Draw background" ),
_( "Draw background" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_DRAW_BUTTON,
g_param_spec_boolean ("draw_button",
_( "Draw button" ),
_( "Draw button" ),
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_CURSOR_POS,
g_param_spec_int ("cursor_pos",
_( "Cursor position" ),
_( "Cursor position" ),
0, G_MAXINT, 0,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_IM_CONTEXT,
g_param_spec_object ("im_context",
_( "IM Context" ),
_( "IM Context" ),
GTK_TYPE_IM_CONTEXT,
G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_HANDLE_POPUP,
g_param_spec_boolean ("handle_popup",
_( "Handle Popup" ),
_( "Handle Popup" ),
FALSE,
G_PARAM_READWRITE));
if (!clipboard_atom)
clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
}
/* Object initialization function for the text item */
static void
e_text_init (EText *text)
{
text->model = e_text_model_new ();
text->text = e_text_model_get_text (text->model);
text->layout = NULL;
text->revert = NULL;
text->model_changed_signal_id =
g_signal_connect (text->model,
"changed",
G_CALLBACK (e_text_text_model_changed),
text);
text->model_repos_signal_id =
g_signal_connect (text->model,
"reposition",
G_CALLBACK (e_text_text_model_reposition),
text);
text->anchor = GTK_ANCHOR_CENTER;
text->justification = GTK_JUSTIFY_LEFT;
text->clip_width = -1.0;
text->clip_height = -1.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->yofs_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->tep_command_id = 0;
text->has_selection = FALSE;
text->pointer_in = FALSE;
text->default_cursor_shown = TRUE;
text->line_wrap = FALSE;
text->break_characters = NULL;
text->max_lines = -1;
text->tooltip_timeout = 0;
text->tooltip_count = 0;
text->tooltip_owner = FALSE;
text->dbl_timeout = 0;
text->tpl_timeout = 0;
text->draw_background = FALSE;
text->draw_button = FALSE;
text->bold = FALSE;
text->strikeout = FALSE;
text->allow_newlines = TRUE;
text->last_type_request = -1;
d(g_print ("Setting last_type_request to %d at line %d\n", text->last_type_request, __LINE__));
text->last_time_request = 0;
text->queued_requests = NULL;
text->im_context = NULL;
text->need_im_reset = FALSE;
text->im_context_signals_registered = FALSE;
text->handle_popup = FALSE;
e_canvas_item_set_reflow_callback(GNOME_CANVAS_ITEM(text), e_text_reflow);
}
/**
* 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.
**/
E_MAKE_TYPE (e_text,
"EText",
EText,
e_text_class_init,
e_text_init,
PARENT_TYPE)
/* IM Context Callbacks */
static void
e_text_commit_cb (GtkIMContext *context,
const gchar *str,
EText *text)
{
if (g_utf8_validate (str, strlen (str), NULL)) {
if (text->selection_end != text->selection_start)
e_text_delete_selection (text);
e_text_insert (text, str);
g_signal_emit (text, e_text_signals[E_TEXT_KEYPRESS], 0,
0 /* XXX ugh */, 0 /* XXX ugh */);
}
}
static gboolean
e_text_retrieve_surrounding_cb (GtkIMContext *context,
EText *text)
{
printf ("e_text_retrieve_surrounding_cb\n");
gtk_im_context_set_surrounding (context,
text->text,
strlen (text->text),
g_utf8_offset_to_pointer (text->text, text->selection_start) - text->text);
return TRUE;
}
static gboolean
e_text_delete_surrounding_cb (GtkIMContext *context,
gint offset,
gint n_chars,
EText *text)
{
printf ("e_text_delete_surrounding_cb\n");
#if 0
gtk_editable_delete_text (GTK_EDITABLE (entry),
entry->current_pos + offset,
entry->current_pos + offset + n_chars);
#endif
return TRUE;
}