Files
evolution/widgets/table/e-cell-text.c
Tor Lillqvist 2decafb544 Port to Windows, initial commit:
2005-04-29  Tor Lillqvist  <tml@novell.com>

	Port to Windows, initial commit:

	* configure.in: Check for Win32, define Automake conditional
	OS_WIN32. Check for regexec() perhaps in separate -lregex, define
	REGEX_LIBS if so. Require glib-2.0 >= 2.6 (and thus also gtk+-2.0
	>= 2.6) so that we can use the gstdio wrappers for full support of
	non-ASCII filenames on Win32. Don't use -D_REENTRANT on Win32, has
	ne special meaning.

	* gal.pc.in: Require gtk+-2.0 >= 2.6 also here for consistency.

	* gal-zip.in: New file, used to build zipfile format distribution
	of gal for Win32.

	* configure.in
	* Makefile.am: Add gal-zip(.in).

	* */Makefile.am
	* */*.c: Harmonize -I and #include conventions. (Of course, this
	hasn't anything to do with Windows porting as such, I just got
	carried away...) Use only -I$(top_srcdir). Use paths to gal
	headers staring with "gal", except for headers from the same
	directory as the .c file, which are included as such. Include all
	gal headers using doublequotes. Sort #includes and remove
	duplicates and obvious redundancies. Include config.h first
	without any HAVE_CONFIG_H, then system headers, then other GNOME
	lib headers, than gal's own headers. Just include gtk.h instead of
	separate gtk*.h headers. Don't include gi18n.h, include e-i18n.h
	to use e_gettext() consistently.

	* gal/Makefile.am: Use -no-undefined on Win32 so that libtool
	agrees to build a shared library. Because of the bidirectional
	dependency between libgal and libgal-a11y we can build libgal-a11y
	sanely as a shared library on Win32, so we don't install any
	separate libgal-a11y at all. So, on Win32, link the stuff that
	goes into libgal-a11y also into libgal. Link with REGEX_LIBS.

	* gal/a11y/Makefile.am: See above. Just build a dummy static
	libgal-a11y on Win32 (can't convince Automake not to build the
	library at all on one platform using Automake ifdef,
	apparently). Then (this is a gross hack) explicitly remove the
	library after installation in the install-data-local rule.

	* gal/e-table/Makefile.am
	* gal/e-table/e-table-config.c: Rename ETABLE_GLADEDIR to
	GAL_GLADEDIR for consistency.

	* gal/e-table/e-cell-date.c: No localtime_r() in Microsoft's C
	library, but its localtime() *is* thread-safe.

	* gal/e-table/e-cell-text.c
	* gal/e-table/e-cell-tree.c
	* gal/e-table/e-cell-vbox.c
	* gal/e-text/e-text.c
	* gal/widgets/e-unicode.c: Remove unnecessary inclusion of gdkx.h.

	* gal/e-table/e-cell-tree.c (ect_realize): Instead of the Xlib
	macro None (whose value is zero), use the corresponding
	zero-valued enums from the appropriate GDK type.

	* gal/e-table/e-table-config.c
	* gal/e-table/e-table-field-chooser.c
	* gal/menus/gal-define-views-dialog.c
	* gal/menus/gal-view-instance-save-as-dialog.c
	* gal/menus/gal-view-new-dialog.c
	* gal/widgets/e-categories-master-list-array.c
	* gal/widgets/e-categories-master-list-dialog.c
	* gal/widgets/e-categories.c: Use g_build_filename() to construct
	pathnames at run-time instead of compile-time. On Windows the
	macros GAL_GLADEDIR and GAL_IMAGESDIR expand to function calls, in
	order to support installing in a freely chosen location.

	* gal/e-table/e-table-item.c
	* gal/e-table/e-cell-vbox.c: Instrad of the Xlib GrabSuccess, use
	GDK_GRAB_SUCCESS (which has the same value).

	* gal/e-table/e-table-specification.c (e_table_specification_load_from_file)
	* gal/e-table/e-table.c (e_table_load_specification)
	* gal/e-table/e-tree-table-adapter.c (open_file)
	* gal/menus/gal-view-instance.c (load_current_view)
	* gal/menus/gal-view-instance.c (load_current_view): On Win32,
	convert filename to the locale character set before passing to
	xmlParseFile() which doesn't use UTF-8 filenames. Use gstdio
	wrappers.

	* gal/util/Makefile.am: Define GAL_PREFIX as $prefix. Define
	GAL_LOCALEDIR, GAL_GLADEDIR and GAL_IMAGESDIR also here for
	e-win32-reloc.c. Include e-win32-reloc.c on Win32.

	* gal/util/e-iconv.c (e_iconv_init): Use g_win32_getlocale() on
	Windows.

	* gal/util/e-util.c
	* gal/util/e-xml-utils.c: Use g_mkstemp() instead of non-portable
	mkstemp(). Use GLib pathname manipulation functions. Use gstdio
	wrappers.

	* gal/util/e-util-private.h: New file. Contains just Win32 bits
	for now that redefine the directory names from the Makefile as
	functions calls.

	* gal/util/e-win32-reloc.c: New file. Contains a minimal DllMain()
	and functions to support freely chosen installation location on
	Windows.

	* gal/util/e-xml-utils.c: No fsync() in the Microsoft C library.

	* gal/windgets/Makefile.am: Add -I$(top_srcdir)/gal for
	consistency with the sibling Makefile.am files.

	* gal/widgets/e-canvas.c: Instead of the Xlib AlreadyGrabbed, use
	GDK_GRAB_ALREADY_GRABBED.

svn path=/trunk/; revision=29249
2005-04-29 14:18:18 +00:00

2869 lines
75 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* e-cell-text.c: Text cell renderer.
* Copyright 1999, 2000, 2001, Ximian, Inc.
*
* Authors:
* Miguel de Icaza <miguel@ximian.com>
* Chris Lahey <clahey@ximian.com>
*
* A lot 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 <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <libgnomecanvas/gnome-canvas.h>
#include <libgnomecanvas/gnome-canvas-rect-ellipse.h>
#include "gal/a11y/e-table/gal-a11y-e-cell-registry.h"
#include "gal/a11y/e-table/gal-a11y-e-cell-text.h"
#include "gal/e-text/e-text.h"
#include "gal/util/e-i18n.h"
#include "gal/util/e-text-event-processor.h"
#include "gal/util/e-text-event-processor-emacs-like.h"
#include "gal/util/e-util.h"
#include "gal/widgets/e-canvas.h"
#include "gal/widgets/e-unicode.h"
#include "e-cell-text.h"
#include "e-table-item.h"
#include "e-table-tooltip.h"
#define d(x)
#define DO_SELECTION 1
#define VIEW_TO_CELL(view) E_CELL_TEXT (((ECellView *)view)->ecell)
#if d(!)0
#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__))
#else
#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
#endif
#define ECT_CLASS(c) (E_CELL_TEXT_CLASS(GTK_OBJECT_GET_CLASS ((c))))
/* This defines a line of text */
struct line {
char *text; /* Line's text UTF-8, it is a pointer into the text->text string */
int length; /* Line's length in BYTES */
int width; /* Line's width in pixels */
int ellipsis_length; /* Length before adding ellipsis in BYTES */
};
/* Object argument IDs */
enum {
PROP_0,
PROP_STRIKEOUT_COLUMN,
PROP_UNDERLINE_COLUMN,
PROP_BOLD_COLUMN,
PROP_COLOR_COLUMN,
PROP_EDITABLE,
PROP_BG_COLOR_COLUMN
};
enum {
E_SELECTION_PRIMARY,
E_SELECTION_CLIPBOARD
};
/* signals */
enum {
TEXT_INSERTED,
TEXT_DELETED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0 };
static GdkAtom clipboard_atom = GDK_NONE;
#define PARENT_TYPE e_cell_get_type ()
#define UTF8_ATOM gdk_atom_intern ("UTF8_STRING", FALSE)
#define TEXT_PAD 4
typedef struct {
gpointer lines; /* Text split into lines (private field) */
int num_lines; /* Number of lines of text */
int max_width;
int ref_count;
} ECellTextLineBreaks;
typedef struct _CellEdit CellEdit;
typedef struct {
ECellView cell_view;
GdkGC *gc;
GdkCursor *i_cursor;
GdkBitmap *stipple; /* Stipple for text */
GnomeCanvas *canvas;
/*
* During editing.
*/
CellEdit *edit;
int xofs, yofs; /* This gets added to the x
and y for the cell text. */
double ellipsis_width[2]; /* The width of the ellipsis. */
} ECellTextView;
struct _CellEdit {
ECellTextView *text_view;
int model_col, view_col, row;
int cell_width;
PangoLayout *layout;
char *text;
char *old_text;
/*
* Where the editing is taking place
*/
int xofs_edit, yofs_edit; /* Offset because of editing.
This is negative compared
to the other offsets. */
/* This needs to be reworked a bit once we get line wrapping. */
int selection_start; /* Start of selection - IN BYTES */
int selection_end; /* End of selection - IN BYTES */
gboolean select_by_word; /* Current selection is by word */
/* This section is for drag scrolling and blinking cursor. */
/* Cursor handling. */
gint timeout_id; /* Current timeout id for scrolling */
GTimer *timer; /* Timer for blinking cursor and scrolling */
gint lastx, lasty; /* Last x and y motion events */
gint last_state; /* Last state */
gulong scroll_start; /* Starting time for scroll (microseconds) */
gint show_cursor; /* Is cursor currently shown */
gboolean button_down; /* Is mouse button 1 down */
ETextEventProcessor *tep; /* Text Event Processor */
GtkWidget *invisible; /* For selection handling */
gboolean has_selection; /* TRUE if we have the selection */
gchar *primary_selection; /* Primary selection text */
gint primary_length; /* Primary selection text length in BYTES */
gchar *clipboard_selection; /* Clipboard selection text */
gint clipboard_length; /* Clipboard selection text length in BYTES */
guint pointer_in : 1;
guint default_cursor_shown : 1;
GtkIMContext *im_context;
gboolean need_im_reset;
gboolean im_context_signals_registered;
guint16 preedit_length; /* length of preedit string, in bytes */
ECellActions actions;
};
static void e_cell_text_view_command (ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data);
static void e_cell_text_view_get_selection (CellEdit *edit, GdkAtom selection, guint32 time);
static void e_cell_text_view_supply_selection (CellEdit *edit, guint time, GdkAtom selection, char *data, gint length);
static void _get_tep (CellEdit *edit);
static gint get_position_from_xy (CellEdit *edit, gint x, gint y);
static gboolean _blink_scroll_timeout (gpointer data);
static void ect_free_color (gchar *color_spec, GdkColor *color, GdkColormap *colormap);
static GdkColor* e_cell_text_get_color (ECellTextView *cell_view, gchar *color_spec);
static void e_cell_text_preedit_changed_cb (GtkIMContext *context, ECellTextView *text_view);
static void e_cell_text_commit_cb (GtkIMContext *context, const gchar *str, ECellTextView *text_view);
static gboolean e_cell_text_retrieve_surrounding_cb (GtkIMContext *context, ECellTextView *text_view);
static gboolean e_cell_text_delete_surrounding_cb (GtkIMContext *context, gint offset, gint n_chars, ECellTextView *text_view);
static void _insert (ECellTextView *text_view, char *string, int value);
static void _delete_selection (ECellTextView *text_view);
static PangoAttrList* build_attr_list (ECellTextView *text_view, int row, int text_length);
static ECellClass *parent_class;
char *
e_cell_text_get_text (ECellText *cell, ETableModel *model, int col, int row)
{
if (ECT_CLASS(cell)->get_text)
return ECT_CLASS(cell)->get_text (cell, model, col, row);
else
return NULL;
}
void
e_cell_text_free_text (ECellText *cell, char *text)
{
if (ECT_CLASS(cell)->free_text)
ECT_CLASS(cell)->free_text (cell, text);
}
void
e_cell_text_set_value (ECellText *cell, ETableModel *model, int col, int row,
const char *text)
{
if (ECT_CLASS(cell)->set_value)
ECT_CLASS(cell)->set_value (cell, model, col, row, text);
}
static char *
ect_real_get_text (ECellText *cell, ETableModel *model, int col, int row)
{
return e_table_model_value_at(model, col, row);
}
static void
ect_real_free_text (ECellText *cell, char *text)
{
}
/* This is the default method for setting the ETableModel value based on
the text in the ECellText. This simply uses the text as it is - it assumes
the value in the model is a char*. Subclasses may parse the text into
data structures to pass to the model. */
static void
ect_real_set_value (ECellText *cell, ETableModel *model, int col, int row,
const char *text)
{
e_table_model_set_value_at (model, col, row, text);
}
static void
ect_queue_redraw (ECellTextView *text_view, int view_col, int view_row)
{
e_table_item_redraw_range (
text_view->cell_view.e_table_item_view,
view_col, view_row, view_col, view_row);
}
/*
* Shuts down the editing process
*/
static void
ect_stop_editing (ECellTextView *text_view, gboolean commit)
{
CellEdit *edit = text_view->edit;
int row, view_col, model_col;
char *old_text, *text;
if (!edit)
return;
row = edit->row;
view_col = edit->view_col;
model_col = edit->model_col;
old_text = edit->old_text;
text = edit->text;
if (edit->invisible)
gtk_widget_destroy (edit->invisible);
if (edit->tep)
g_object_unref (edit->tep);
if (edit->primary_selection)
g_free (edit->primary_selection);
if (edit->clipboard_selection)
g_free (edit->clipboard_selection);
if (! edit->default_cursor_shown){
gdk_window_set_cursor (GTK_WIDGET(text_view->canvas)->window, NULL);
edit->default_cursor_shown = TRUE;
}
if (edit->timeout_id) {
g_source_remove (edit->timeout_id);
edit->timeout_id = 0;
}
if (edit->timer) {
g_timer_stop (edit->timer);
g_timer_destroy (edit->timer);
edit->timer = NULL;
}
g_signal_handlers_disconnect_matched (
edit->im_context,
G_SIGNAL_MATCH_DATA, 0, 0,
NULL, NULL, text_view);
if (edit->layout)
g_object_unref (edit->layout);
g_free (edit);
text_view->edit = NULL;
if (commit) {
/*
* Accept the currently edited text. if it's the same as what's in the cell, do nothing.
*/
ECellView *ecell_view = (ECellView *) text_view;
ECellText *ect = (ECellText *) ecell_view->ecell;
if (strcmp (old_text, text)) {
e_cell_text_set_value (ect, ecell_view->e_table_model,
model_col, row, text);
}
}
g_free (text);
g_free (old_text);
ect_queue_redraw (text_view, view_col, row);
}
/*
* Cancels the edits
*/
static void
ect_cancel_edit (ECellTextView *text_view)
{
ect_stop_editing (text_view, FALSE);
e_table_item_leave_edit_ (text_view->cell_view.e_table_item_view);
}
/*
* ECell::new_view method
*/
static ECellView *
ect_new_view (ECell *ecell, ETableModel *table_model, void *e_table_item_view)
{
ECellTextView *text_view = g_new0 (ECellTextView, 1);
GnomeCanvas *canvas = GNOME_CANVAS_ITEM (e_table_item_view)->canvas;
text_view->cell_view.ecell = ecell;
text_view->cell_view.e_table_model = table_model;
text_view->cell_view.e_table_item_view = e_table_item_view;
text_view->canvas = canvas;
text_view->xofs = 0.0;
text_view->yofs = 0.0;
return (ECellView *)text_view;
}
/*
* ECell::kill_view method
*/
static void
ect_kill_view (ECellView *ecv)
{
ECellTextView *text_view = (ECellTextView *) ecv;
g_free (text_view);
}
/*
* ECell::realize method
*/
static void
ect_realize (ECellView *ecell_view)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
text_view->gc = gdk_gc_new (GTK_WIDGET (text_view->canvas)->window);
text_view->i_cursor = gdk_cursor_new (GDK_XTERM);
if (parent_class->realize)
(* parent_class->realize) (ecell_view);
}
/*
* ECell::unrealize method
*/
static void
ect_unrealize (ECellView *ecv)
{
ECellTextView *text_view = (ECellTextView *) ecv;
ECellText *ect = (ECellText*) ecv->ecell;
GdkColormap *colormap;
gdk_gc_unref (text_view->gc);
text_view->gc = NULL;
if (text_view->edit){
ect_cancel_edit (text_view);
}
if (text_view->stipple)
gdk_bitmap_unref (text_view->stipple);
gdk_cursor_destroy (text_view->i_cursor);
if (ect->colors) {
colormap = gtk_widget_get_colormap (GTK_WIDGET (text_view->canvas));
g_hash_table_foreach (ect->colors, (GHFunc) ect_free_color,
colormap);
g_hash_table_destroy (ect->colors);
ect->colors = NULL;
}
if (parent_class->unrealize)
(* parent_class->unrealize) (ecv);
}
static void
ect_free_color (gchar *color_spec, GdkColor *color, GdkColormap *colormap)
{
g_free (color_spec);
/* This frees the color. Note we don't free it if it is the special
value. */
if (color != (GdkColor*) 1) {
gulong pix = color->pixel;
gdk_colors_free (colormap, &pix, 1, 0);
/* This frees the memory for the GdkColor. */
gdk_color_free (color);
}
}
static PangoAttrList*
build_attr_list (ECellTextView *text_view, int row, int text_length)
{
ECellView *ecell_view = (ECellView *) text_view;
ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
PangoAttrList *attrs = pango_attr_list_new ();
gboolean bold, strikeout, underline;
bold = ect->bold_column >= 0 &&
row >= 0 &&
e_table_model_value_at(ecell_view->e_table_model, ect->bold_column, row);
strikeout = ect->strikeout_column >= 0 &&
row >= 0 &&
e_table_model_value_at(ecell_view->e_table_model, ect->strikeout_column, row);
underline = ect->underline_column >= 0 &&
row >= 0 &&
e_table_model_value_at(ecell_view->e_table_model, ect->underline_column, row);
if (bold || strikeout || underline) {
if (bold) {
PangoAttribute *attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
attr->start_index = 0;
attr->end_index = text_length;
pango_attr_list_insert_before (attrs, attr);
}
if (strikeout) {
PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
attr->start_index = 0;
attr->end_index = text_length;
pango_attr_list_insert_before (attrs, attr);
}
if (underline) {
PangoAttribute *attr = pango_attr_underline_new (TRUE);
attr->start_index = 0;
attr->end_index = text_length;
pango_attr_list_insert_before (attrs, attr);
}
}
return attrs;
}
static PangoLayout *
layout_with_preedit (ECellTextView *text_view, int row, const char *text, gint width)
{
CellEdit *edit = text_view->edit;
PangoAttrList *attrs ;
PangoLayout *layout;
GString *tmp_string = g_string_new (NULL);
PangoAttrList *preedit_attrs = NULL;
gchar *preedit_string = NULL;
gint preedit_length = 0;
gint text_length = strlen (text);
gint mlen = MIN(edit->selection_start,text_length);
gtk_im_context_get_preedit_string (edit->im_context,
&preedit_string,&preedit_attrs,
NULL);
preedit_length = edit->preedit_length = strlen (preedit_string);;
layout = edit->layout;
g_string_prepend_len (tmp_string, text,text_length);
if (preedit_length) {
/* mlen is the text_length in bytes, not chars
* check whether we are not inserting into
* the middle of a utf8 character
*/
if (mlen < text_length) {
if (!g_utf8_validate (text+mlen, -1, NULL)) {
gchar *tc;
tc = g_utf8_find_next_char (text+mlen,NULL);
if (tc) {
mlen = (gint) (tc - text);
}
}
}
g_string_insert (tmp_string, mlen, preedit_string);
}
pango_layout_set_text (layout, tmp_string->str, tmp_string->len);
attrs = (PangoAttrList *) build_attr_list (text_view, row, text_length);
if (preedit_length)
pango_attr_list_splice (attrs, preedit_attrs, mlen, preedit_length);
pango_layout_set_attributes (layout, attrs);
g_string_free (tmp_string, TRUE);
if (preedit_string)
g_free (preedit_string);
if (preedit_attrs)
pango_attr_list_unref (preedit_attrs);
pango_attr_list_unref (attrs);
return layout;
}
static PangoLayout *
build_layout (ECellTextView *text_view, int row, const char *text, gint width)
{
ECellView *ecell_view = (ECellView *) text_view;
ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
PangoAttrList *attrs ;
PangoLayout *layout;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (((GnomeCanvasItem *)ecell_view->e_table_item_view)->canvas), text);
attrs = (PangoAttrList *) build_attr_list (text_view, row, text ? strlen (text) : 0);
pango_layout_set_attributes (layout, attrs);
pango_attr_list_unref (attrs);
if (text_view->edit || width <= 0)
return layout;
pango_layout_set_width (layout, width * PANGO_SCALE);
pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
if (pango_layout_get_line_count (layout) > 1) {
PangoLayoutLine *line = pango_layout_get_line (layout, 0);
gchar *line_text = g_strdup (pango_layout_get_text (layout));
gchar *last_char = g_utf8_find_prev_char (line_text, line_text + line->length - 1);
while (last_char && pango_layout_get_line_count (layout) > 1) {
gchar *new_text;
last_char = g_utf8_find_prev_char (line_text, last_char);
if (last_char)
*last_char = '\0';
new_text = g_strconcat (line_text, "...", NULL);
pango_layout_set_text (layout, new_text, -1);
g_free (new_text);
}
g_free (line_text);
}
switch (ect->justify) {
case GTK_JUSTIFY_RIGHT:
pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
break;
case GTK_JUSTIFY_CENTER:
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
break;
case GTK_JUSTIFY_LEFT:
default:
break;
}
return layout;
}
static PangoLayout *
generate_layout (ECellTextView *text_view, int model_col, int view_col, int row, int width)
{
ECellView *ecell_view = (ECellView *) text_view;
ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
PangoLayout *layout;
CellEdit *edit = text_view->edit;
if (edit && edit->layout && edit->model_col == model_col && edit->row == row) {
g_object_ref (edit->layout);
return edit->layout;
}
if (row >= 0) {
char *temp = e_cell_text_get_text(ect, ecell_view->e_table_model, model_col, row);
layout = build_layout (text_view, row, temp ? temp : "?", width);
e_cell_text_free_text(ect, temp);
} else
layout = build_layout (text_view, row, "Mumbo Jumbo", width);
return layout;
}
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 (CellEdit *edit, PangoRectangle rect)
{
int x1 = rect.x / PANGO_SCALE;
int x2 = (rect.x + rect.width) / PANGO_SCALE;
#if 0
int y1 = rect.y / PANGO_SCALE;
int y2 = (rect.y + rect.height) / PANGO_SCALE;
#endif
int new_xofs_edit = edit->xofs_edit;
int new_yofs_edit = edit->yofs_edit;
if (x1 < new_xofs_edit)
new_xofs_edit = x1;
if (2 + x2 - edit->cell_width > new_xofs_edit)
new_xofs_edit = 2 + x2 - edit->cell_width;
if (new_xofs_edit < 0)
new_xofs_edit = 0;
#if 0
if (y1 < new_yofs_edit)
new_yofs_edit = y1;
if (2 + y2 - edit->cell_height > new_yofs_edit)
new_yofs_edit = 2 + y2 - edit->cell_height;
if (new_yofs_edit < 0)
new_yofs_edit = 0;
#endif
if (new_xofs_edit != edit->xofs_edit ||
new_yofs_edit != edit->yofs_edit) {
edit->xofs_edit = new_xofs_edit;
edit->yofs_edit = new_yofs_edit;
return TRUE;
}
return FALSE;
}
/*
* ECell::draw method
*/
static void
ect_draw (ECellView *ecell_view, GdkDrawable *drawable,
int model_col, int view_col, int row, ECellFlags flags,
int x1, int y1, int x2, int y2)
{
PangoLayout *layout;
ECellTextView *text_view = (ECellTextView *) ecell_view;
ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
CellEdit *edit = text_view->edit;
gboolean selected;
GdkColor *foreground, *cursor_color;
GtkWidget *canvas = GTK_WIDGET (text_view->canvas);
GdkRectangle clip_rect;
int x_origin, y_origin;
selected = flags & E_CELL_SELECTED;
if (selected) {
if (flags & E_CELL_FOCUSED)
foreground = &canvas->style->fg [GTK_STATE_SELECTED];
else
foreground = &canvas->style->fg [GTK_STATE_ACTIVE];
cursor_color = foreground;
} else {
foreground = &canvas->style->text [GTK_STATE_NORMAL];
cursor_color = foreground;
if (ect->color_column != -1) {
char *color_spec;
GdkColor *cell_foreground;
color_spec = e_table_model_value_at (ecell_view->e_table_model,
ect->color_column, row);
cell_foreground = e_cell_text_get_color (text_view,
color_spec);
if (cell_foreground)
foreground = cell_foreground;
}
}
gdk_gc_set_foreground (text_view->gc, foreground);
x1 += 4;
y1 += 1;
x2 -= 4;
y2 -= 1;
x_origin = x1 + ect->x + text_view->xofs - (edit ? edit->xofs_edit : 0);
y_origin = y1 + ect->y + text_view->yofs - (edit ? edit->yofs_edit : 0);
clip_rect.x = x1;
clip_rect.y = y1;
clip_rect.width = x2 - x1;
clip_rect.height = y2 - y1;
gdk_gc_set_clip_rectangle (text_view->gc, &clip_rect);
/* clip_rect = &rect;*/
layout = generate_layout (text_view, model_col, view_col, row, x2 - x1);
if (edit && edit->view_col == view_col && edit->row == row) {
layout = layout_with_preedit (text_view, row, edit->text ? edit->text : "?", x2 - x1);
}
gdk_draw_layout (drawable, text_view->gc,
x_origin, y_origin,
layout);
if (edit && edit->view_col == view_col && edit->row == row) {
if (edit->selection_start != edit->selection_end) {
int start_index, end_index;
PangoLayoutLine *line;
gint *ranges;
gint n_ranges, i;
PangoRectangle logical_rect;
GdkRegion *clip_region = gdk_region_new ();
GdkRegion *rect_region;
GdkGC *selection_gc;
GdkGC *text_gc;
start_index = MIN (edit->selection_start, edit->selection_end);
end_index = edit->selection_start ^ edit->selection_end ^ start_index;
if (edit->has_selection) {
selection_gc = canvas->style->base_gc [GTK_STATE_SELECTED];
text_gc = canvas->style->text_gc[GTK_STATE_SELECTED];
} else {
selection_gc = canvas->style->base_gc [GTK_STATE_ACTIVE];
text_gc = canvas->style->text_gc[GTK_STATE_ACTIVE];
}
gdk_gc_set_clip_rectangle (selection_gc, &clip_rect);
line = pango_layout_get_lines (layout)->data;
pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);
pango_layout_get_extents (layout, NULL, &logical_rect);
for (i=0; i < n_ranges; i++) {
GdkRectangle sel_rect;
sel_rect.x = x_origin + ranges[2*i] / PANGO_SCALE;
sel_rect.y = y_origin;
sel_rect.width = (ranges[2*i + 1] - ranges[2*i]) / PANGO_SCALE;
sel_rect.height = logical_rect.height / 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);
}
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,
x_origin, y_origin,
layout);
gdk_gc_set_clip_region (text_gc, NULL);
gdk_gc_set_clip_region (selection_gc, NULL);
gdk_region_destroy (clip_region);
g_free (ranges);
} else {
if (edit->show_cursor) {
PangoRectangle strong_pos, weak_pos;
pango_layout_get_cursor_pos (layout, edit->selection_start + edit->preedit_length, &strong_pos, &weak_pos);
draw_pango_rectangle (drawable, text_view->gc, x_origin, y_origin, 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, text_view->gc, x_origin, y_origin, weak_pos);
}
}
}
g_object_unref (layout);
}
/*
* Get the background color
*/
static gchar *
ect_get_bg_color(ECellView *ecell_view, int row)
{
ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
gchar *color_spec;
if (ect->bg_color_column == -1)
return NULL;
color_spec = e_table_model_value_at (ecell_view->e_table_model,
ect->bg_color_column, row);
return color_spec;
}
/*
* Selects the entire string
*/
static void
ect_edit_select_all (ECellTextView *text_view)
{
g_assert (text_view->edit);
text_view->edit->selection_start = 0;
text_view->edit->selection_end = strlen (text_view->edit->text);
}
static gboolean
key_begins_editing (GdkEventKey *event)
{
if (event->length == 0)
return FALSE;
return TRUE;
}
/*
* ECell::event method
*/
static gint
ect_event (ECellView *ecell_view, GdkEvent *event, int model_col, int view_col, int row, ECellFlags flags, ECellActions *actions)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
ETextEventProcessorEvent e_tep_event;
gboolean edit_display = FALSE;
gint preedit_len;
CellEdit *edit = text_view->edit;
GtkWidget *canvas = GTK_WIDGET (text_view->canvas);
gint return_val = 0;
d(gboolean press = FALSE);
if (!(flags & E_CELL_EDITING))
return 0;
if ( edit && !edit->preedit_length && flags & E_CELL_PREEDIT)
return TRUE;
if (edit && edit->view_col == view_col && edit->row == row) {
edit_display = TRUE;
}
e_tep_event.type = event->type;
switch (event->type) {
case GDK_FOCUS_CHANGE:
break;
case GDK_KEY_PRESS: /* Fall Through */
if (edit_display) {
if (edit->im_context &&
!edit->im_context_signals_registered) {
g_signal_connect (edit->im_context,
"preedit_changed",
G_CALLBACK (\
e_cell_text_preedit_changed_cb),
text_view);
g_signal_connect (edit->im_context,
"commit",
G_CALLBACK (\
e_cell_text_commit_cb),
text_view);
g_signal_connect (edit->im_context,
"retrieve_surrounding",
G_CALLBACK (\
e_cell_text_retrieve_surrounding_cb),
text_view);
g_signal_connect (edit->im_context,
"delete_surrounding",
G_CALLBACK (\
e_cell_text_delete_surrounding_cb),
text_view);
edit->im_context_signals_registered = TRUE;
}
edit->show_cursor = FALSE;
} else {
if (edit->im_context) {
g_signal_handlers_disconnect_matched (
edit->im_context,
G_SIGNAL_MATCH_DATA, 0, 0,
NULL, NULL, edit);
edit->im_context_signals_registered = FALSE;
}
ect_stop_editing (text_view, TRUE);
if (edit->timeout_id) {
g_source_remove(edit->timeout_id);
edit->timeout_id = 0;
}
}
return_val = TRUE;
/* Fallthrough */
case GDK_KEY_RELEASE:
preedit_len = edit->preedit_length;
if (edit_display && edit->im_context &&
gtk_im_context_filter_keypress (\
edit->im_context,
(GdkEventKey*)event)) {
edit->need_im_reset = TRUE;
if (preedit_len && flags & E_CELL_PREEDIT)
return FALSE;
else
return TRUE;
}
if (event->key.keyval == GDK_Escape){
ect_cancel_edit (text_view);
return_val = TRUE;
break;
}
if ((!edit_display) &&
e_table_model_is_cell_editable (ecell_view->e_table_model, model_col, row) &&
key_begins_editing (&event->key)) {
e_table_item_enter_edit (text_view->cell_view.e_table_item_view, view_col, row);
ect_edit_select_all (text_view);
edit = text_view->edit;
edit_display = TRUE;
}
if (edit_display) {
GdkEventKey key = event->key;
if (key.keyval == GDK_KP_Enter || key.keyval == GDK_Return){
e_table_item_leave_edit_ (text_view->cell_view.e_table_item_view);
} else {
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 (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 (edit);
return_val = e_text_event_processor_handle_event (edit->tep, &e_tep_event);
if (e_tep_event.key.string)
g_free (e_tep_event.key.string);
break;
}
}
break;
case GDK_BUTTON_PRESS: /* Fall Through */
d(press = TRUE);
case GDK_BUTTON_RELEASE:
d(g_print ("%s: %s\n", __FUNCTION__, press ? "GDK_BUTTON_PRESS" : "GDK_BUTTON_RELEASE"));
event->button.x -= 4;
event->button.y -= 1;
if ((!edit_display)
&& e_table_model_is_cell_editable (ecell_view->e_table_model, model_col, row)
&& event->type == GDK_BUTTON_RELEASE
&& event->button.button == 1) {
GdkEventButton button = event->button;
e_table_item_enter_edit (text_view->cell_view.e_table_item_view, view_col, row);
edit = text_view->edit;
edit_display = TRUE;
e_tep_event.button.type = GDK_BUTTON_PRESS;
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 (edit, event->button.x, event->button.y);
_get_tep (edit);
edit->actions = 0;
return_val = e_text_event_processor_handle_event (edit->tep,
&e_tep_event);
*actions = edit->actions;
if (event->button.button == 1) {
if (event->type == GDK_BUTTON_PRESS)
edit->button_down = TRUE;
else
edit->button_down = FALSE;
}
edit->lastx = button.x;
edit->lasty = button.y;
edit->last_state = button.state;
e_tep_event.button.type = GDK_BUTTON_RELEASE;
}
if (edit_display) {
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 (edit, event->button.x, event->button.y);
_get_tep (edit);
edit->actions = 0;
return_val = e_text_event_processor_handle_event (edit->tep,
&e_tep_event);
*actions = edit->actions;
if (event->button.button == 1) {
if (event->type == GDK_BUTTON_PRESS)
edit->button_down = TRUE;
else
edit->button_down = FALSE;
}
edit->lastx = button.x;
edit->lasty = button.y;
edit->last_state = button.state;
}
break;
case GDK_MOTION_NOTIFY:
event->motion.x -= 4;
event->motion.y -= 1;
if (edit_display) {
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 (edit, event->motion.x, event->motion.y);
_get_tep (edit);
edit->actions = 0;
return_val = e_text_event_processor_handle_event (edit->tep,
&e_tep_event);
*actions = edit->actions;
edit->lastx = motion.x;
edit->lasty = motion.y;
edit->last_state = motion.state;
}
break;
case GDK_ENTER_NOTIFY:
#if 0
edit->pointer_in = TRUE;
#endif
if (edit_display) {
if (edit->default_cursor_shown){
gdk_window_set_cursor (canvas->window, text_view->i_cursor);
edit->default_cursor_shown = FALSE;
}
}
break;
case GDK_LEAVE_NOTIFY:
#if 0
text_view->pointer_in = FALSE;
#endif
if (edit_display) {
if (! edit->default_cursor_shown){
gdk_window_set_cursor (canvas->window, NULL);
edit->default_cursor_shown = TRUE;
}
}
break;
default:
break;
}
return return_val;
}
/*
* ECell::height method
*/
static int
ect_height (ECellView *ecell_view, int model_col, int view_col, int row)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
gint height;
PangoLayout *layout;
layout = generate_layout (text_view, model_col, view_col, row, 0);
pango_layout_get_pixel_size (layout, NULL, &height);
g_object_unref (layout);
return height + 2;
}
/*
* ECellView::enter_edit method
*/
static void *
ect_enter_edit (ECellView *ecell_view, int model_col, int view_col, int row)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
CellEdit *edit;
ECellText *ect = E_CELL_TEXT(ecell_view->ecell);
char *temp;
edit = g_new0 (CellEdit, 1);
text_view->edit = edit;
edit->im_context = E_CANVAS (text_view->canvas)->im_context;
edit->need_im_reset = FALSE;
edit->im_context_signals_registered = FALSE;
edit->view_col = -1;
edit->model_col = -1;
edit->row = -1;
edit->text_view = text_view;
edit->model_col = model_col;
edit->view_col = view_col;
edit->row = row;
edit->cell_width = e_table_header_get_column (
((ETableItem *)ecell_view->e_table_item_view)->header,
view_col)->width - 8;
edit->layout = generate_layout (text_view, model_col, view_col, row, edit->cell_width);
edit->xofs_edit = 0.0;
edit->yofs_edit = 0.0;
edit->selection_start = 0;
edit->selection_end = 0;
edit->select_by_word = FALSE;
edit->timeout_id = g_timeout_add (10, _blink_scroll_timeout, text_view);
edit->timer = g_timer_new ();
g_timer_elapsed (edit->timer, &(edit->scroll_start));
g_timer_start (edit->timer);
edit->lastx = 0;
edit->lasty = 0;
edit->last_state = 0;
edit->scroll_start = 0;
edit->show_cursor = TRUE;
edit->button_down = FALSE;
edit->tep = NULL;
edit->has_selection = FALSE;
edit->invisible = NULL;
edit->primary_selection = NULL;
edit->primary_length = 0;
edit->clipboard_selection = NULL;
edit->clipboard_length = 0;
edit->pointer_in = FALSE;
edit->default_cursor_shown = TRUE;
temp = e_cell_text_get_text(ect, ecell_view->e_table_model, model_col, row);
edit->old_text = g_strdup (temp);
e_cell_text_free_text(ect, temp);
edit->text = g_strdup (edit->old_text);
#if 0
if (edit->pointer_in){
if (edit->default_cursor_shown){
gdk_window_set_cursor (GTK_WIDGET(item->canvas)->window, text_view->i_cursor);
edit->default_cursor_shown = FALSE;
}
}
#endif
ect_queue_redraw (text_view, view_col, row);
return NULL;
}
/*
* ECellView::leave_edit method
*/
static void
ect_leave_edit (ECellView *ecell_view, int model_col, int view_col, int row, void *edit_context)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
CellEdit *edit = text_view->edit;
if (edit){
ect_stop_editing (text_view, TRUE);
} else {
/*
* We did invoke this leave edit internally
*/
}
}
/*
* ECellView::save_state method
*/
static void *
ect_save_state (ECellView *ecell_view, int model_col, int view_col, int row, void *edit_context)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
CellEdit *edit = text_view->edit;
int *save_state = g_new (int, 2);
save_state[0] = edit->selection_start;
save_state[1] = edit->selection_end;
return save_state;
}
/*
* ECellView::load_state method
*/
static void
ect_load_state (ECellView *ecell_view, int model_col, int view_col, int row, void *edit_context, void *save_state)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
CellEdit *edit = text_view->edit;
int length;
int *selection = save_state;
length = strlen (edit->text);
edit->selection_start = MIN (selection[0], length);
edit->selection_end = MIN (selection[1], length);
ect_queue_redraw (text_view, view_col, row);
}
/*
* ECellView::free_state method
*/
static void
ect_free_state (ECellView *ecell_view, int model_col, int view_col, int row, void *save_state)
{
g_free (save_state);
}
#define FONT_NAME "Sans Regular"
static GnomeFont *
get_font_for_size (double h)
{
GnomeFontFace *face;
GnomeFont *font;
double asc, desc, size;
face = gnome_font_face_find (FONT_NAME);
asc = gnome_font_face_get_ascender (face);
desc = abs (gnome_font_face_get_descender (face));
size = h * 1000 / (asc + desc);
font = gnome_font_find_closest (FONT_NAME, size);
g_object_unref (face);
return font;
}
static void
ect_print (ECellView *ecell_view, GnomePrintContext *context,
int model_col, int view_col, int row,
double width, double height)
{
GnomeFont *font = get_font_for_size (16);
char *string;
ECellText *ect = E_CELL_TEXT(ecell_view->ecell);
double ty, ly, text_width;
gboolean strikeout, underline;
string = e_cell_text_get_text(ect, ecell_view->e_table_model, model_col, row);
gnome_print_gsave(context);
if (gnome_print_moveto(context, 2, 2) == -1)
/* FIXME */;
if (gnome_print_lineto(context, width - 2, 2) == -1)
/* FIXME */;
if (gnome_print_lineto(context, width - 2, height - 2) == -1)
/* FIXME */;
if (gnome_print_lineto(context, 2, height - 2) == -1)
/* FIXME */;
if (gnome_print_lineto(context, 2, 2) == -1)
/* FIXME */;
if (gnome_print_clip(context) == -1)
/* FIXME */;
ty = (height - gnome_font_get_ascender(font) - gnome_font_get_descender(font)) / 2;
text_width = gnome_font_get_width_utf8 (font, string);
strikeout = ect->strikeout_column >= 0 && row >= 0 &&
e_table_model_value_at (ecell_view->e_table_model, ect->strikeout_column, row);
underline = ect->underline_column >= 0 && row >= 0 &&
e_table_model_value_at(ecell_view->e_table_model, ect->underline_column, row);
if (underline) {
ly = ty + gnome_font_get_underline_position (font);
gnome_print_newpath (context);
gnome_print_moveto (context, 2, ly);
gnome_print_lineto (context, MIN (2 + text_width, width - 2), ly);
gnome_print_setlinewidth (context, gnome_font_get_underline_thickness (font));
gnome_print_stroke (context);
}
if (strikeout) {
ly = ty + (gnome_font_get_ascender (font) - gnome_font_get_underline_thickness (font))/ 2.0;
gnome_print_newpath (context);
gnome_print_moveto (context, 2, ly);
gnome_print_lineto (context, MIN (2 + text_width, width - 2), ly);
gnome_print_setlinewidth (context, gnome_font_get_underline_thickness (font));
gnome_print_stroke (context);
}
gnome_print_moveto(context, 2, ty);
gnome_print_setfont(context, font);
gnome_print_show(context, string);
gnome_print_grestore(context);
e_cell_text_free_text(ect, string);
g_object_unref (font);
}
static gdouble
ect_print_height (ECellView *ecell_view, GnomePrintContext *context,
int model_col, int view_col, int row,
double width)
{
return 16;
}
static int
ect_max_width (ECellView *ecell_view,
int model_col,
int view_col)
{
/* New ECellText */
ECellTextView *text_view = (ECellTextView *) ecell_view;
int row;
int number_of_rows;
int max_width = 0;
number_of_rows = e_table_model_row_count (ecell_view->e_table_model);
for (row = 0; row < number_of_rows; row++) {
PangoLayout *layout = generate_layout (text_view, model_col, view_col, row, 0);
int width;
pango_layout_get_pixel_size (layout, &width, NULL);
max_width = MAX (max_width, width);
g_object_unref (layout);
}
return max_width + 8;
}
static int
ect_max_width_by_row (ECellView *ecell_view,
int model_col,
int view_col,
int row)
{
/* New ECellText */
ECellTextView *text_view = (ECellTextView *) ecell_view;
int width;
PangoLayout *layout;
if (row >= e_table_model_row_count (ecell_view->e_table_model))
return 0;
layout = generate_layout (text_view, model_col, view_col, row, 0);
pango_layout_get_pixel_size (layout, &width, NULL);
g_object_unref (layout);
return width + 8;
}
static gint
tooltip_event (GtkWidget *window,
GdkEvent *event,
ETableTooltip *tooltip)
{
gint ret_val = FALSE;
switch (event->type) {
case GDK_LEAVE_NOTIFY:
e_canvas_hide_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(tooltip->eti)->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(tooltip->eti)->canvas));
}
event->button.x = tooltip->cx;
event->button.y = tooltip->cy;
g_signal_emit_by_name (tooltip->eti, "event",
event, &ret_val);
if (!ret_val)
gtk_propagate_event (GTK_WIDGET(GNOME_CANVAS_ITEM(tooltip->eti)->canvas), event);
ret_val = TRUE;
break;
case GDK_KEY_PRESS:
e_canvas_hide_tooltip (E_CANVAS(GNOME_CANVAS_ITEM(tooltip->eti)->canvas));
g_signal_emit_by_name (tooltip->eti, "event",
event, &ret_val);
if (!ret_val)
gtk_propagate_event (GTK_WIDGET(GNOME_CANVAS_ITEM(tooltip->eti)->canvas), event);
ret_val = TRUE;
break;
default:
break;
}
return ret_val;
}
static void
ect_show_tooltip (ECellView *ecell_view,
int model_col,
int view_col,
int row,
int col_width,
ETableTooltip *tooltip)
{
ECellTextView *text_view = (ECellTextView *) ecell_view;
GtkWidget *canvas;
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;
GnomeCanvasItem *rect;
ECellText *ect = E_CELL_TEXT(ecell_view->ecell);
GtkWidget *window;
PangoLayout *layout;
int width, height;
tooltip->timer = 0;
layout = generate_layout (text_view, model_col, view_col, row, col_width);
pango_layout_get_pixel_size (layout, &width, &height);
if (width < col_width - 8) {
return;
}
gnome_canvas_item_i2c_affine (GNOME_CANVAS_ITEM (tooltip->eti), i2c);
art_affine_point (&pixel_origin, &origin, i2c);
gdk_window_get_origin (GTK_WIDGET (text_view->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 (text_view->canvas))->value;
pixel_origin.y -= (int) gtk_layout_get_vadjustment (GTK_LAYOUT (text_view->canvas))->value;
window = gtk_window_new (GTK_WINDOW_POPUP);
gtk_container_set_border_width (GTK_CONTAINER (window), 1);
canvas = e_canvas_new ();
gtk_container_add (GTK_CONTAINER (window), canvas);
GTK_WIDGET_UNSET_FLAGS (canvas, GTK_CAN_FOCUS);
GTK_WIDGET_UNSET_FLAGS (window, GTK_CAN_FOCUS);
rect = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)),
gnome_canvas_rect_get_type (),
"x1", (double) 0.0,
"y1", (double) 0.0,
"x2", (double) width + 4,
"y2", (double) height,
"fill_color_gdk", tooltip->background,
NULL);
tooltip_text = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (canvas)),
e_text_get_type (),
"anchor", GTK_ANCHOR_NW,
"bold", (gboolean) ect->bold_column >= 0 && e_table_model_value_at(ecell_view->e_table_model, ect->bold_column, row),
"strikeout", (gboolean) ect->strikeout_column >= 0 && e_table_model_value_at(ecell_view->e_table_model, ect->strikeout_column, row),
"underline", (gboolean) ect->underline_column >= 0 && e_table_model_value_at(ecell_view->e_table_model, ect->underline_column, row),
"fill_color_gdk", tooltip->foreground,
"text", pango_layout_get_text (layout),
"editable", FALSE,
"clip_width", (double) width,
"clip_height", (double) height,
"clip", TRUE,
"line_wrap", FALSE,
"justification", E_CELL_TEXT (text_view->cell_view.ecell)->justify,
"draw_background", FALSE,
NULL);
tooltip_width = width;
tooltip_height = height;
tooltip_y = tooltip->y;
switch (E_CELL_TEXT (text_view->cell_view.ecell)->justify) {
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 = tooltip->x;
break;
}
gnome_canvas_item_move (tooltip_text, 3.0, 1.0);
gnome_canvas_item_set (rect,
"x2", (double) tooltip_width + 6,
"y2", (double) tooltip->row_height + 1,
NULL);
gtk_widget_set_usize (window, tooltip_width + 6,
tooltip->row_height + 1);
gnome_canvas_set_scroll_region (GNOME_CANVAS (canvas), 0.0, 0.0,
(double) tooltip_width + 6,
(double) tooltip_height);
gtk_widget_show (canvas);
gtk_widget_realize (window);
g_signal_connect (window, "event",
G_CALLBACK (tooltip_event), tooltip);
e_canvas_popup_tooltip (E_CANVAS(text_view->canvas), window, pixel_origin.x + tooltip->x,
pixel_origin.y + tooltip->y - 1);
return;
}
/*
* GtkObject::destroy method
*/
static void
ect_finalize (GObject *object)
{
ECellText *ect = E_CELL_TEXT (object);
g_free (ect->font_name);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/* Set_arg handler for the text item */
static void
ect_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ECellText *text;
text = E_CELL_TEXT (object);
switch (prop_id) {
case PROP_STRIKEOUT_COLUMN:
text->strikeout_column = g_value_get_int (value);
break;
case PROP_UNDERLINE_COLUMN:
text->underline_column = g_value_get_int (value);
break;
case PROP_BOLD_COLUMN:
text->bold_column = g_value_get_int (value);
break;
case PROP_COLOR_COLUMN:
text->color_column = g_value_get_int (value);
break;
case PROP_EDITABLE:
text->editable = g_value_get_boolean (value);
break;
case PROP_BG_COLOR_COLUMN:
text->bg_color_column = g_value_get_int (value);
break;
default:
return;
}
}
/* Get_arg handler for the text item */
static void
ect_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ECellText *text;
text = E_CELL_TEXT (object);
switch (prop_id) {
case PROP_STRIKEOUT_COLUMN:
g_value_set_int (value, text->strikeout_column);
break;
case PROP_UNDERLINE_COLUMN:
g_value_set_int (value, text->underline_column);
break;
case PROP_BOLD_COLUMN:
g_value_set_int (value, text->bold_column);
break;
case PROP_COLOR_COLUMN:
g_value_set_int (value, text->color_column);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, text->editable);
break;
case PROP_BG_COLOR_COLUMN:
g_value_set_int (value, text->bg_color_column);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static char *ellipsis_default = NULL;
static gboolean use_ellipsis_default = TRUE;
static void
e_cell_text_class_init (GObjectClass *object_class)
{
ECellClass *ecc = (ECellClass *) object_class;
ECellTextClass *ectc = (ECellTextClass *) object_class;
const char *ellipsis_env;
G_OBJECT_CLASS (object_class)->finalize = ect_finalize;
ecc->new_view = ect_new_view;
ecc->kill_view = ect_kill_view;
ecc->realize = ect_realize;
ecc->unrealize = ect_unrealize;
ecc->draw = ect_draw;
ecc->event = ect_event;
ecc->height = ect_height;
ecc->enter_edit = ect_enter_edit;
ecc->leave_edit = ect_leave_edit;
ecc->save_state = ect_save_state;
ecc->load_state = ect_load_state;
ecc->free_state = ect_free_state;
ecc->print = ect_print;
ecc->print_height = ect_print_height;
ecc->max_width = ect_max_width;
ecc->max_width_by_row = ect_max_width_by_row;
ecc->show_tooltip = ect_show_tooltip;
ecc->get_bg_color = ect_get_bg_color;
ectc->get_text = ect_real_get_text;
ectc->free_text = ect_real_free_text;
ectc->set_value = ect_real_set_value;
object_class->get_property = ect_get_property;
object_class->set_property = ect_set_property;
parent_class = g_type_class_ref (PARENT_TYPE);
signals [TEXT_INSERTED] =
g_signal_new ("text_inserted",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (ECellTextClass, text_inserted),
NULL, NULL,
e_marshal_VOID__POINTER_INT_INT_INT_INT,
G_TYPE_NONE, 5,
G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT,
G_TYPE_INT, G_TYPE_INT);
signals [TEXT_DELETED] =
g_signal_new ("text_deleted",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (ECellTextClass, text_deleted),
NULL, NULL,
e_marshal_VOID__POINTER_INT_INT_INT_INT,
G_TYPE_NONE, 5,
G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT,
G_TYPE_INT, G_TYPE_INT);
g_object_class_install_property (object_class, PROP_STRIKEOUT_COLUMN,
g_param_spec_int ("strikeout_column",
_("Strikeout Column"),
/*_( */"XXX blurb" /*)*/,
-1, G_MAXINT, -1,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_UNDERLINE_COLUMN,
g_param_spec_int ("underline_column",
_("Underline Column"),
/*_( */"XXX blurb" /*)*/,
-1, G_MAXINT, -1,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_BOLD_COLUMN,
g_param_spec_int ("bold_column",
_("Bold Column"),
/*_( */"XXX blurb" /*)*/,
-1, G_MAXINT, -1,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_COLOR_COLUMN,
g_param_spec_int ("color_column",
_("Color Column"),
/*_( */"XXX blurb" /*)*/,
-1, G_MAXINT, -1,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_EDITABLE,
g_param_spec_boolean ("editable",
_("Editable"),
/*_( */"XXX blurb" /*)*/,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_BG_COLOR_COLUMN,
g_param_spec_int ("bg_color_column",
_("BG Color Column"),
/*_( */"XXX blurb" /*)*/,
-1, G_MAXINT, -1,
G_PARAM_READWRITE));
if (!clipboard_atom)
clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
ellipsis_env = g_getenv ("GAL_ELLIPSIS");
if (ellipsis_env) {
if (*ellipsis_env) {
ellipsis_default = g_strdup (ellipsis_env);
} else {
use_ellipsis_default = FALSE;
}
}
gal_a11y_e_cell_registry_add_cell_type (NULL, E_CELL_TEXT_TYPE, gal_a11y_e_cell_text_new);
}
/* IM Context Callbacks */
static void
e_cell_text_preedit_changed_cb (GtkIMContext *context,
ECellTextView *tv)
{
gchar *preedit_string;
gint cursor_pos;
CellEdit *edit=tv->edit;
gtk_im_context_get_preedit_string (edit->im_context, &preedit_string,
NULL, &cursor_pos);
edit->preedit_length = strlen (preedit_string);
cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
g_free (preedit_string);
ect_queue_redraw (tv, edit->view_col, edit->row);
}
static void
e_cell_text_commit_cb (GtkIMContext *context,
const gchar *str,
ECellTextView *tv)
{
CellEdit *edit = tv->edit;
ETextEventProcessorCommand command;
if (g_utf8_validate (str, strlen (str), NULL)) {
command.action = E_TEP_INSERT;
command.position = E_TEP_SELECTION;
command.string = (gchar *)str;
command.value = strlen(str);
e_cell_text_view_command (edit->tep, &command, edit);
}
}
static gboolean
e_cell_text_retrieve_surrounding_cb (GtkIMContext *context,
ECellTextView *tv)
{
CellEdit *edit = tv->edit;
gtk_im_context_set_surrounding (context,
edit->text,
strlen (edit->text),
MIN (edit->selection_start, edit->selection_end)
);
return TRUE;
}
static gboolean
e_cell_text_delete_surrounding_cb (GtkIMContext *context,
gint offset,
gint n_chars,
ECellTextView *tv)
{
int begin_pos, end_pos;
glong text_len;
CellEdit *edit = tv->edit;
text_len = g_utf8_strlen (edit->text, -1);
begin_pos = g_utf8_pointer_to_offset (edit->text,
edit->text + MIN (edit->selection_start, edit->selection_end));
begin_pos += offset;
end_pos = begin_pos + n_chars;
if(begin_pos < 0 || text_len < begin_pos)
return FALSE;
if(end_pos > text_len)
end_pos = text_len;
edit->selection_start = g_utf8_offset_to_pointer (edit->text, begin_pos)
- edit->text;
edit->selection_end = g_utf8_offset_to_pointer (edit->text, end_pos)
- edit->text;
_delete_selection (tv);
return TRUE;
}
static void
e_cell_text_init (ECellText *ect)
{
ect->ellipsis = g_strdup (ellipsis_default);
ect->use_ellipsis = use_ellipsis_default;
ect->strikeout_column = -1;
ect->underline_column = -1;
ect->bold_column = -1;
ect->color_column = -1;
ect->bg_color_column = -1;
ect->editable = TRUE;
}
E_MAKE_TYPE(e_cell_text, "ECellText", ECellText, e_cell_text_class_init, e_cell_text_init, PARENT_TYPE)
/**
* e_cell_text_construct:
* @cell: The cell to construct
* @fontname: this param is no longer used, but left here for api stability
* @justify: Justification of the string in the cell
*
* constructs the ECellText. To be used by subclasses and language
* bindings.
*
* Returns: The ECellText.
*/
ECell *
e_cell_text_construct (ECellText *cell, const char *fontname, GtkJustification justify)
{
if(!cell)
return E_CELL(NULL);
if(fontname)
cell->font_name = g_strdup (fontname);
cell->justify = justify;
return E_CELL(cell);
}
/**
* e_cell_text_new:
* @fontname: this param is no longer used, but left here for api stability
* @justify: Justification of the string in the cell.
*
* Creates a new ECell renderer that can be used to render strings that
* that come from the model. The value returned from the model is
* interpreted as being a char *.
*
* The ECellText object support a large set of properties that can be
* configured through the Gtk argument system and allows the user to have
* a finer control of the way the string is displayed. The arguments supported
* allow the control of strikeout, underline, bold, and color.
*
* The arguments "strikeout_column", "underline_column", "bold_column"
* and "color_column" set and return an integer that points to a
* column in the model that controls these settings. So controlling
* the way things are rendered is achieved by having special columns
* in the model that will be used to flag whether the text should be
* rendered with strikeout, or bolded. In the case of the
* "color_column" argument, the column in the model is expected to
* have a string that can be parsed by gdk_color_parse().
*
* Returns: an ECell object that can be used to render strings.
*/
ECell *
e_cell_text_new (const char *fontname, GtkJustification justify)
{
ECellText *ect = g_object_new (E_CELL_TEXT_TYPE, NULL);
e_cell_text_construct(ect, fontname, justify);
return (ECell *) ect;
}
/* fixme: Handle Font attributes */
/* position is in BYTES */
static gint
get_position_from_xy (CellEdit *edit, gint x, gint y)
{
int index;
int trailing;
const char *text;
PangoLayout *layout = generate_layout (edit->text_view, edit->model_col, edit->view_col, edit->row, edit->cell_width);
ECellTextView *text_view = edit->text_view;
ECellText *ect = (ECellText *) ((ECellView *)text_view)->ecell;
x -= (ect->x + text_view->xofs - edit->xofs_edit);
y -= (ect->y + text_view->yofs - edit->yofs_edit);
pango_layout_xy_to_index (layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, &trailing);
text = pango_layout_get_text (layout);
return g_utf8_offset_to_pointer (text + index, trailing) - text;
}
#define SCROLL_WAIT_TIME 30000
static gboolean
_blink_scroll_timeout (gpointer data)
{
ECellTextView *text_view = (ECellTextView *) data;
ECellText *ect = E_CELL_TEXT (((ECellView *)text_view)->ecell);
CellEdit *edit = text_view->edit;
gulong current_time;
gboolean scroll = FALSE;
gboolean redraw = FALSE;
int width, height;
g_timer_elapsed (edit->timer, &current_time);
if (edit->scroll_start + SCROLL_WAIT_TIME > 1000000) {
if (current_time > edit->scroll_start - (1000000 - SCROLL_WAIT_TIME) &&
current_time < edit->scroll_start)
scroll = TRUE;
} else {
if (current_time > edit->scroll_start + SCROLL_WAIT_TIME ||
current_time < edit->scroll_start)
scroll = TRUE;
}
pango_layout_get_pixel_size (edit->layout, &width, &height);
if (scroll && edit->button_down) {
/* FIXME: Copy this for y. */
if (edit->lastx - ect->x > edit->cell_width) {
if (edit->xofs_edit < width - edit->cell_width) {
edit->xofs_edit += 4;
if (edit->xofs_edit > width - edit->cell_width + 1)
edit->xofs_edit = width - edit->cell_width + 1;
redraw = TRUE;
}
}
if (edit->lastx - ect->x < 0 &&
edit->xofs_edit > 0) {
edit->xofs_edit -= 4;
if (edit->xofs_edit < 0)
edit->xofs_edit = 0;
redraw = TRUE;
}
if (redraw) {
ETextEventProcessorEvent e_tep_event;
e_tep_event.type = GDK_MOTION_NOTIFY;
e_tep_event.motion.state = edit->last_state;
e_tep_event.motion.time = 0;
e_tep_event.motion.position = get_position_from_xy (edit, edit->lastx, edit->lasty);
_get_tep (edit);
e_text_event_processor_handle_event (edit->tep,
&e_tep_event);
edit->scroll_start = current_time;
}
}
if (!((current_time / 500000) % 2)) {
if (!edit->show_cursor)
redraw = TRUE;
edit->show_cursor = TRUE;
} else {
if (edit->show_cursor)
redraw = TRUE;
edit->show_cursor = FALSE;
}
if (redraw){
ect_queue_redraw (text_view, edit->view_col, edit->row);
}
return TRUE;
}
static int
next_word (CellEdit *edit, int start)
{
char *p;
int length;
length = strlen (edit->text);
if (start >= length)
return length;
p = g_utf8_next_char (edit->text + start);
while (*p && g_unichar_validate (g_utf8_get_char (p))) {
gunichar unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival))
return p - edit->text;
p = g_utf8_next_char (p);
}
return p - edit->text;
}
static int
_get_position (ECellTextView *text_view, ETextEventProcessorCommand *command)
{
int length;
CellEdit *edit = text_view->edit;
gchar *p;
int unival;
int index;
int trailing;
switch (command->position) {
case E_TEP_VALUE:
return command->value;
case E_TEP_SELECTION:
return edit->selection_end;
case E_TEP_START_OF_BUFFER:
return 0;
/* fixme: this probably confuses TEP */
case E_TEP_END_OF_BUFFER:
return strlen (edit->text);
case E_TEP_START_OF_LINE:
if (edit->selection_end < 1) return 0;
p = g_utf8_find_prev_char (edit->text, edit->text + edit->selection_end);
if (p == edit->text) return 0;
p = g_utf8_find_prev_char (edit->text, p);
while (p && p > edit->text) {
if (*p == '\n') return p - edit->text + 1;
p = g_utf8_find_prev_char (edit->text, p);
}
return 0;
case E_TEP_END_OF_LINE:
length = strlen (edit->text);
if (edit->selection_end >= length) return length;
p = g_utf8_next_char (edit->text + edit->selection_end);
while (*p && g_unichar_validate (g_utf8_get_char (p))) {
if (*p == '\n') return p - edit->text;
p = g_utf8_next_char (p);
}
return p - edit->text;
case E_TEP_FORWARD_CHARACTER:
length = strlen (edit->text);
if (edit->selection_end >= length) return length;
p = g_utf8_next_char (edit->text + edit->selection_end);
return p - edit->text;
case E_TEP_BACKWARD_CHARACTER:
if (edit->selection_end < 1) return 0;
p = g_utf8_find_prev_char (edit->text, edit->text + edit->selection_end);
if (p == NULL) return 0;
return p - edit->text;
case E_TEP_FORWARD_WORD:
return next_word (edit, edit->selection_end);
case E_TEP_BACKWARD_WORD:
if (edit->selection_end < 1) return 0;
p = g_utf8_find_prev_char (edit->text, edit->text + edit->selection_end);
if (p == edit->text) return 0;
p = g_utf8_find_prev_char (edit->text, p);
while (p && p > edit->text && g_unichar_validate (g_utf8_get_char (p))) {
unival = g_utf8_get_char (p);
if (g_unichar_isspace (unival)) {
return (g_utf8_next_char (p) - edit->text);
}
p = g_utf8_find_prev_char (edit->text, p);
}
return 0;
case E_TEP_FORWARD_LINE:
pango_layout_move_cursor_visually (edit->layout,
TRUE,
edit->selection_end,
0,
TRUE,
&index,
&trailing);
index = g_utf8_offset_to_pointer (edit->text + index, trailing) - edit->text;
if (index < 0)
return 0;
length = strlen (edit->text);
if (index >= length)
return length;
return index;
case E_TEP_BACKWARD_LINE:
pango_layout_move_cursor_visually (edit->layout,
TRUE,
edit->selection_end,
0,
TRUE,
&index,
&trailing);
index = g_utf8_offset_to_pointer (edit->text + index, trailing) - edit->text;
if (index < 0)
return 0;
length = strlen (edit->text);
if (index >= length)
return length;
return index;
case E_TEP_FORWARD_PARAGRAPH:
case E_TEP_BACKWARD_PARAGRAPH:
case E_TEP_FORWARD_PAGE:
case E_TEP_BACKWARD_PAGE:
return edit->selection_end;
default:
return edit->selection_end;
}
g_assert_not_reached ();
return 0; /* Kill warning */
}
static void
_delete_selection (ECellTextView *text_view)
{
CellEdit *edit = text_view->edit;
gint length;
gchar *sp, *ep;
if (edit->selection_end == edit->selection_start) return;
if (edit->selection_end < edit->selection_start) {
edit->selection_end ^= edit->selection_start;
edit->selection_start ^= edit->selection_end;
edit->selection_end ^= edit->selection_start;
}
sp = edit->text + edit->selection_start;
ep = edit->text + edit->selection_end;
length = strlen (ep) + 1;
memmove (sp, ep, length);
edit->selection_end = edit->selection_start;
g_signal_emit (VIEW_TO_CELL (text_view), signals[TEXT_DELETED], 0, text_view, edit->selection_start, ep-sp, edit->row, edit->model_col);
}
/* fixme: */
/* NB! We expect value to be length IN BYTES */
static void
_insert (ECellTextView *text_view, char *string, int value)
{
CellEdit *edit = text_view->edit;
char *temp;
if (value <= 0) return;
edit->selection_start = MIN (strlen(edit->text), edit->selection_start);
temp = g_new (gchar, strlen (edit->text) + value + 1);
strncpy (temp, edit->text, edit->selection_start);
strncpy (temp + edit->selection_start, string, value);
strcpy (temp + edit->selection_start + value, edit->text + edit->selection_end);
g_free (edit->text);
edit->text = temp;
edit->selection_start += value;
edit->selection_end = edit->selection_start;
g_signal_emit (VIEW_TO_CELL (text_view), signals[TEXT_INSERTED], 0, text_view, edit->selection_end-value, value, edit->row, edit->model_col);
}
static void
capitalize (CellEdit *edit, int start, int end, ETextEventProcessorCaps type)
{
ECellTextView *text_view = edit->text_view;
gboolean first = TRUE;
int character_length = g_utf8_strlen (edit->text + start, start - end);
const char *p = edit->text + start;
const char *text_end = edit->text + end;
char *new_text = g_new0 (char, character_length * 6 + 1);
char *output = new_text;
while (p && *p && p < text_end && g_unichar_validate (g_utf8_get_char (p))) {
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;
edit->selection_end = end;
edit->selection_start = start;
_delete_selection (text_view);
_insert (text_view, new_text, output - new_text);
g_free (new_text);
}
static void
e_cell_text_view_command (ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data)
{
CellEdit *edit = (CellEdit *) data;
ECellTextView *text_view = edit->text_view;
ECellText *ect = E_CELL_TEXT (text_view->cell_view.ecell);
gboolean change = FALSE;
gboolean redraw = FALSE;
int sel_start, sel_end;
/* If the EText isn't editable, then ignore any commands that would
modify the text. */
if (!ect->editable && (command->action == E_TEP_DELETE
|| command->action == E_TEP_INSERT
|| command->action == E_TEP_PASTE
|| command->action == E_TEP_GET_SELECTION))
return;
switch (command->action) {
case E_TEP_MOVE:
edit->selection_start = _get_position (text_view, command);
edit->selection_end = edit->selection_start;
if (edit->timer) {
g_timer_reset (edit->timer);
}
redraw = TRUE;
break;
case E_TEP_SELECT:
edit->selection_end = _get_position (text_view, command);
sel_start = MIN(edit->selection_start, edit->selection_end);
sel_end = MAX(edit->selection_start, edit->selection_end);
if (sel_start != sel_end) {
e_cell_text_view_supply_selection (edit, command->time, GDK_SELECTION_PRIMARY,
edit->text + sel_start,
sel_end - sel_start);
} else if (edit->timer) {
g_timer_reset (edit->timer);
}
redraw = TRUE;
break;
case E_TEP_DELETE:
if (edit->selection_end == edit->selection_start) {
edit->selection_end = _get_position (text_view, command);
}
_delete_selection (text_view);
if (edit->timer) {
g_timer_reset (edit->timer);
}
redraw = TRUE;
change = TRUE;
break;
case E_TEP_INSERT:
if (!edit->preedit_length && edit->selection_end != edit->selection_start) {
_delete_selection (text_view);
}
_insert (text_view, command->string, command->value);
if (edit->timer) {
g_timer_reset (edit->timer);
}
redraw = TRUE;
change = TRUE;
break;
case E_TEP_COPY:
sel_start = MIN(edit->selection_start, edit->selection_end);
sel_end = MAX(edit->selection_start, edit->selection_end);
if (sel_start != sel_end) {
e_cell_text_view_supply_selection (edit, command->time, clipboard_atom,
edit->text + sel_start,
sel_end - sel_start);
}
if (edit->timer) {
g_timer_reset (edit->timer);
}
break;
case E_TEP_PASTE:
e_cell_text_view_get_selection (edit, clipboard_atom, command->time);
if (edit->timer) {
g_timer_reset (edit->timer);
}
redraw = TRUE;
change = TRUE;
break;
case E_TEP_GET_SELECTION:
e_cell_text_view_get_selection (edit, GDK_SELECTION_PRIMARY, command->time);
break;
case E_TEP_ACTIVATE:
e_table_item_leave_edit_ (text_view->cell_view.e_table_item_view);
break;
case E_TEP_SET_SELECT_BY_WORD:
edit->select_by_word = command->value;
break;
case E_TEP_GRAB:
edit->actions = E_CELL_GRAB;
break;
case E_TEP_UNGRAB:
edit->actions = E_CELL_UNGRAB;
break;
case E_TEP_CAPS:
if (edit->selection_start == edit->selection_end) {
capitalize (edit, edit->selection_start, next_word (edit, edit->selection_start), command->value);
} else {
int selection_start = MIN (edit->selection_start, edit->selection_end);
int selection_end = edit->selection_start + edit->selection_end - selection_start; /* Slightly faster than MAX */
capitalize (edit, selection_start, selection_end, command->value);
}
if (edit->timer) {
g_timer_reset (edit->timer);
}
redraw = TRUE;
change = TRUE;
break;
case E_TEP_NOP:
break;
}
if (change) {
if (edit->layout)
g_object_unref (edit->layout);
edit->layout = build_layout (text_view, edit->row, edit->text, edit->cell_width);
}
if (!edit->button_down) {
PangoRectangle strong_pos, weak_pos;
pango_layout_get_cursor_pos (edit->layout, edit->selection_end, &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) {
if (show_pango_rectangle (edit, weak_pos))
redraw = TRUE;
}
if (show_pango_rectangle (edit, strong_pos)) {
redraw = TRUE;
}
}
if (redraw){
ect_queue_redraw (text_view, edit->view_col, edit->row);
}
}
#ifdef DO_SELECTION
static void
_selection_clear_event (GtkInvisible *invisible,
GdkEventSelection *event,
CellEdit *edit)
{
if (event->selection == GDK_SELECTION_PRIMARY) {
g_free (edit->primary_selection);
edit->primary_selection = NULL;
edit->primary_length = 0;
edit->has_selection = FALSE;
#if 0
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(text));
#endif
} else if (event->selection == clipboard_atom) {
g_free (edit->clipboard_selection);
edit->clipboard_selection = NULL;
edit->clipboard_length = 0;
}
}
static void
_selection_get (GtkInvisible *invisible,
GtkSelectionData *selection_data,
guint info,
guint time_stamp,
CellEdit *edit)
{
switch (info) {
case E_SELECTION_PRIMARY:
gtk_selection_data_set (selection_data, UTF8_ATOM,
8, edit->primary_selection,
edit->primary_length);
break;
case E_SELECTION_CLIPBOARD:
gtk_selection_data_set (selection_data, UTF8_ATOM,
8, edit->clipboard_selection,
edit->clipboard_length);
break;
}
}
/* fixme: What happens, if delivered string is not UTF-8? */
static void
_selection_received (GtkInvisible *invisible,
GtkSelectionData *selection_data,
guint time,
CellEdit *edit)
{
if (selection_data->length < 0 ||
!(selection_data->type == UTF8_ATOM ||
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_cell_text_view_command (edit->tep, &command, edit);
}
}
static GtkWidget *e_cell_text_view_get_invisible (CellEdit *edit)
{
if (edit->invisible == NULL) {
GtkWidget *invisible = gtk_invisible_new ();
edit->invisible = invisible;
gtk_selection_add_target (invisible,
GDK_SELECTION_PRIMARY,
UTF8_ATOM,
E_SELECTION_PRIMARY);
gtk_selection_add_target (invisible,
clipboard_atom,
UTF8_ATOM,
E_SELECTION_CLIPBOARD);
g_signal_connect (invisible, "selection_get",
G_CALLBACK (_selection_get),
edit);
g_signal_connect (invisible, "selection_clear_event",
G_CALLBACK (_selection_clear_event),
edit);
g_signal_connect (invisible, "selection_received",
G_CALLBACK (_selection_received),
edit);
}
return edit->invisible;
}
#endif
static void
e_cell_text_view_supply_selection (CellEdit *edit, guint time, GdkAtom selection, char *data, gint length)
{
#if DO_SELECTION
gboolean successful;
GtkWidget *invisible;
invisible = e_cell_text_view_get_invisible (edit);
if (selection == GDK_SELECTION_PRIMARY){
if (edit->primary_selection) {
g_free (edit->primary_selection);
}
edit->primary_selection = g_strndup (data, length);
edit->primary_length = length;
} else if (selection == clipboard_atom) {
if (edit->clipboard_selection) {
g_free (edit->clipboard_selection);
}
edit->clipboard_selection = g_strndup (data, length);
edit->clipboard_length = length;
}
successful = gtk_selection_owner_set (invisible,
selection,
time);
if (selection == GDK_SELECTION_PRIMARY)
edit->has_selection = successful;
#endif
}
static void
e_cell_text_view_get_selection (CellEdit *edit, GdkAtom selection, guint32 time)
{
#if DO_SELECTION
GtkWidget *invisible;
invisible = e_cell_text_view_get_invisible (edit);
gtk_selection_convert (invisible,
selection,
UTF8_ATOM,
time);
#endif
}
static void
_get_tep (CellEdit *edit)
{
if (!edit->tep) {
edit->tep = e_text_event_processor_emacs_like_new ();
g_signal_connect (edit->tep,
"command",
G_CALLBACK(e_cell_text_view_command),
(gpointer) edit);
}
}
static GdkColor*
e_cell_text_get_color (ECellTextView *cell_view, gchar *color_spec)
{
ECellText *ect = E_CELL_TEXT (((ECellView*) cell_view)->ecell);
GdkColormap *colormap;
GdkColor *color, tmp_color;
/* If the color spec is NULL we use the default color. */
if (color_spec == NULL)
return NULL;
/* Create the hash table if we haven't already. */
if (!ect->colors)
ect->colors = g_hash_table_new (g_str_hash, g_str_equal);
/* See if we've already allocated the color. Note that we use a
special value of (GdkColor*) 1 in the hash to indicate that we've
already tried and failed to allocate the color, so we don't keep
trying to allocate it. */
color = g_hash_table_lookup (ect->colors, color_spec);
if (color == (GdkColor*) 1)
return NULL;
if (color)
return color;
/* Try to parse the color. */
if (gdk_color_parse (color_spec, &tmp_color)) {
colormap = gtk_widget_get_colormap (GTK_WIDGET (cell_view->canvas));
/* Try to allocate the color. */
if (gdk_color_alloc (colormap, &tmp_color))
color = gdk_color_copy (&tmp_color);
}
g_hash_table_insert (ect->colors, g_strdup (color_spec),
color ? color : (GdkColor*) 1);
return color;
}
/**
* e_cell_text_set_selection:
* @cell_view: the given cell view
* @col: column of the given cell in the view
* @row: row of the given cell in the view
* @start: start offset of the selection
* @end: end offset of the selection
*
* Sets the selection of given text cell.
* If the current editing cell is not the given cell, this function
* will return FALSE;
*
* If success, the [start, end) part of the text will be selected.
*
* This API is most likely to be used by a11y implementations.
*
* Returns: whether the action is successful.
*/
gboolean
e_cell_text_set_selection (ECellView *cell_view,
gint col,
gint row,
gint start,
gint end)
{
ECellTextView *ectv;
CellEdit *edit;
ETextEventProcessorCommand command1, command2;
ectv = (ECellTextView *)cell_view;
edit = ectv->edit;
if (!edit)
return FALSE;
if (edit->view_col != col || edit->row != row)
return FALSE;
command1.action = E_TEP_MOVE;
command1.position = E_TEP_VALUE;
command1.value = start;
e_cell_text_view_command (edit->tep, &command1, edit);
command2.action = E_TEP_SELECT;
command2.position = E_TEP_VALUE;
command2.value = end;
e_cell_text_view_command (edit->tep, &command2, edit);
return TRUE;
}
/**
* e_cell_text_get_selection:
* @cell_view: the given cell view
* @col: column of the given cell in the view
* @row: row of the given cell in the view
* @start: a pointer to an int value indicates the start offset of the selection
* @end: a pointer to an int value indicates the end offset of the selection
*
* Gets the selection of given text cell.
* If the current editing cell is not the given cell, this function
* will return FALSE;
*
* This API is most likely to be used by a11y implementations.
*
* Returns: whether the action is successful.
*/
gboolean
e_cell_text_get_selection (ECellView *cell_view,
gint col,
gint row,
gint *start,
gint *end)
{
ECellTextView *ectv;
CellEdit *edit;
ectv = (ECellTextView *)cell_view;
edit = ectv->edit;
if (!edit)
return FALSE;
if (edit->view_col != col || edit->row != row)
return FALSE;
if (start)
*start = edit->selection_start;
if (end)
*end = edit->selection_end;
return TRUE;
}
/**
* e_cell_text_copy_clipboard:
* @cell_view: the given cell view
* @col: column of the given cell in the view
* @row: row of the given cell in the view
*
* Copys the selected text to clipboard.
*
* This API is most likely to be used by a11y implementations.
*/
void
e_cell_text_copy_clipboard (ECellView *cell_view, gint col, gint row)
{
ECellTextView *ectv;
CellEdit *edit;
ETextEventProcessorCommand command;
ectv = (ECellTextView *)cell_view;
edit = ectv->edit;
if (!edit)
return;
if (edit->view_col != col || edit->row != row)
return;
command.action = E_TEP_COPY;
command.time = GDK_CURRENT_TIME;
e_cell_text_view_command (edit->tep, &command, edit);
}
/**
* e_cell_text_paste_clipboard:
* @cell_view: the given cell view
* @col: column of the given cell in the view
* @row: row of the given cell in the view
*
* Pastes the text from the clipboardt.
*
* This API is most likely to be used by a11y implementations.
*/
void
e_cell_text_paste_clipboard (ECellView *cell_view, gint col, gint row)
{
ECellTextView *ectv;
CellEdit *edit;
ETextEventProcessorCommand command;
ectv = (ECellTextView *)cell_view;
edit = ectv->edit;
if (!edit)
return;
if (edit->view_col != col || edit->row != row)
return;
command.action = E_TEP_PASTE;
command.time = GDK_CURRENT_TIME;
e_cell_text_view_command (edit->tep, &command, edit);
}
/**
* e_cell_text_delete_selection:
* @cell_view: the given cell view
* @col: column of the given cell in the view
* @row: row of the given cell in the view
*
* Deletes the selected text of the cell.
*
* This API is most likely to be used by a11y implementations.
*/
void
e_cell_text_delete_selection (ECellView *cell_view, gint col, gint row)
{
ECellTextView *ectv;
CellEdit *edit;
ETextEventProcessorCommand command;
ectv = (ECellTextView *)cell_view;
edit = ectv->edit;
if (!edit)
return;
if (edit->view_col != col || edit->row != row)
return;
command.action = E_TEP_DELETE;
command.position = E_TEP_SELECTION;
e_cell_text_view_command (edit->tep, &command, edit);
}
/**
* e_cell_text_get_text_by_view:
* @cell_view: the given cell view
* @col: column of the given cell in the model
* @row: row of the given cell in the model
*
* Get the cell's text directly from CellEdit,
* during editting this cell, the cell's text value maybe inconsistant
* with the text got from table_model.
* The caller should free the text after using it.
*
* This API is most likely to be used by a11y implementations.
*/
char *
e_cell_text_get_text_by_view (ECellView *cell_view, gint col, gint row)
{
ECellTextView *ectv;
CellEdit *edit;
gchar *ret, *model_text;
ectv = (ECellTextView *)cell_view;
edit = ectv->edit;
if (edit && ectv->edit->row == row && ectv->edit->model_col == col) { /* being editted now */
ret = g_strdup (edit->text);
} else{
model_text = e_cell_text_get_text (E_CELL_TEXT (cell_view->ecell),
cell_view->e_table_model, col, row);
ret = g_strdup (model_text);
e_cell_text_free_text (E_CELL_TEXT (cell_view->ecell), model_text);
}
return ret;
}