/* Editable GnomeCanvas text item based on GtkTextLayout, borrowed heavily
* from GtkTextView.
*
* Copyright (c) 2000 Red Hat, Inc.
* Copyright (c) 2001 Joe Shaw
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API
#include <gtk/gtktextdisplay.h>
#include "gnome-canvas.h"
#include "gnome-canvas-util.h"
#include "gnome-canvas-rich-text.h"
#include "gnome-canvas-i18n.h"
#define GNOME_CANVAS_RICH_TEXT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), GNOME_TYPE_CANVAS_RICH_TEXT, GnomeCanvasRichTextPrivate))
struct _GnomeCanvasRichTextPrivate {
GtkTextLayout *layout;
GtkTextBuffer *buffer;
gchar *text;
/* Position at anchor */
gdouble x, y;
/* Dimensions */
gdouble width, height;
/* Top-left canvas coordinates for text */
gint cx, cy;
gboolean cursor_visible;
gboolean cursor_blink;
gboolean editable;
gboolean visible;
gboolean grow_height;
GtkWrapMode wrap_mode;
GtkJustification justification;
GtkTextDirection direction;
gint pixels_above_lines;
gint pixels_below_lines;
gint pixels_inside_wrap;
gint left_margin;
gint right_margin;
gint indent;
guint preblink_timeout;
guint blink_timeout;
guint selection_drag_handler;
gint drag_start_x;
gint drag_start_y;
gboolean just_selected_element;
gint clicks;
guint click_timeout;
};
enum {
PROP_0,
PROP_TEXT,
PROP_X,
PROP_Y,
PROP_WIDTH,
PROP_HEIGHT,
PROP_EDITABLE,
PROP_VISIBLE,
PROP_CURSOR_VISIBLE,
PROP_CURSOR_BLINK,
PROP_GROW_HEIGHT,
PROP_WRAP_MODE,
PROP_JUSTIFICATION,
PROP_DIRECTION,
PROP_ANCHOR,
PROP_PIXELS_ABOVE_LINES,
PROP_PIXELS_BELOW_LINES,
PROP_PIXELS_INSIDE_WRAP,
PROP_LEFT_MARGIN,
PROP_RIGHT_MARGIN,
PROP_INDENT
};
enum {
TAG_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void gnome_canvas_rich_text_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec);
static void gnome_canvas_rich_text_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec);
static void gnome_canvas_rich_text_update (GnomeCanvasItem *item,
const cairo_matrix_t *matrix, gint flags);
static void gnome_canvas_rich_text_realize (GnomeCanvasItem *item);
static void gnome_canvas_rich_text_unrealize (GnomeCanvasItem *item);
static GnomeCanvasItem * gnome_canvas_rich_text_point (GnomeCanvasItem *item,
gdouble x, gdouble y,
gint cx, gint cy);
static void gnome_canvas_rich_text_draw (GnomeCanvasItem *item,
cairo_t *cr,
gint x, gint y, gint width, gint height);
static gint gnome_canvas_rich_text_event (GnomeCanvasItem *item,
GdkEvent *event);
static void gnome_canvas_rich_text_get_bounds (GnomeCanvasItem *text,
gdouble *px1, gdouble *py1,
gdouble *px2, gdouble *py2);
static void gnome_canvas_rich_text_ensure_layout (GnomeCanvasRichText *text);
static void gnome_canvas_rich_text_destroy_layout (GnomeCanvasRichText *text);
static void gnome_canvas_rich_text_start_cursor_blink (GnomeCanvasRichText *text,
gboolean delay);
static void gnome_canvas_rich_text_stop_cursor_blink (GnomeCanvasRichText *text);
static void gnome_canvas_rich_text_move_cursor (GnomeCanvasRichText *text,
GtkMovementStep step,
gint count,
gboolean extend_selection);
static GtkTextBuffer *get_buffer (GnomeCanvasRichText *text);
static gint blink_cb (gpointer data);
#define PREBLINK_TIME 300
#define CURSOR_ON_TIME 800
#define CURSOR_OFF_TIME 400
G_DEFINE_TYPE (
GnomeCanvasRichText,
gnome_canvas_rich_text,
GNOME_TYPE_CANVAS_ITEM)
static void
gnome_canvas_rich_text_class_init (GnomeCanvasRichTextClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
g_type_class_add_private (class, sizeof (GnomeCanvasRichTextPrivate));
gobject_class->set_property = gnome_canvas_rich_text_set_property;
gobject_class->get_property = gnome_canvas_rich_text_get_property;
g_object_class_install_property (
gobject_class,
PROP_TEXT,
g_param_spec_string ("text",
"Text",
"Text to display",
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_X,
g_param_spec_double ("x",
"X",
"X position",
-G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_Y,
g_param_spec_double ("y",
"Y",
"Y position",
-G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_WIDTH,
g_param_spec_double ("width",
"Width",
"Width for text box",
-G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_HEIGHT,
g_param_spec_double ("height",
"Height",
"Height for text box",
-G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_EDITABLE,
g_param_spec_boolean ("editable",
"Editable",
"Is this rich text item editable?",
TRUE,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_VISIBLE,
g_param_spec_boolean ("visible",
"Visible",
"Is this rich text item visible?",
TRUE,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_CURSOR_VISIBLE,
g_param_spec_boolean ("cursor_visible",
"Cursor Visible",
"Is the cursor visible in this rich text item?",
TRUE,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_CURSOR_BLINK,
g_param_spec_boolean ("cursor_blink",
"Cursor Blink",
"Does the cursor blink in this rich text item?",
TRUE,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_GROW_HEIGHT,
g_param_spec_boolean ("grow_height",
"Grow Height",
"Should the text box height grow if the text does not fit?",
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_WRAP_MODE,
g_param_spec_enum ("wrap_mode",
"Wrap Mode",
"Wrap mode for multiline text",
GTK_TYPE_WRAP_MODE,
GTK_WRAP_WORD,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_JUSTIFICATION,
g_param_spec_enum ("justification",
"Justification",
"Justification mode",
GTK_TYPE_JUSTIFICATION,
GTK_JUSTIFY_LEFT,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_DIRECTION,
g_param_spec_enum ("direction",
"Direction",
"Text direction",
GTK_TYPE_DIRECTION_TYPE,
gtk_widget_get_default_direction (),
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_PIXELS_ABOVE_LINES,
g_param_spec_int ("pixels_above_lines",
"Pixels Above Lines",
"Number of pixels to put above lines",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_PIXELS_BELOW_LINES,
g_param_spec_int ("pixels_below_lines",
"Pixels Below Lines",
"Number of pixels to put below lines",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_PIXELS_INSIDE_WRAP,
g_param_spec_int ("pixels_inside_wrap",
"Pixels Inside Wrap",
"Number of pixels to put inside the wrap",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_LEFT_MARGIN,
g_param_spec_int ("left_margin",
"Left Margin",
"Number of pixels in the left margin",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_RIGHT_MARGIN,
g_param_spec_int ("right_margin",
"Right Margin",
"Number of pixels in the right margin",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE));
g_object_class_install_property (
gobject_class,
PROP_INDENT,
g_param_spec_int ("indent",
"Indentation",
"Number of pixels for indentation",
G_MININT, G_MAXINT,
0,
G_PARAM_READWRITE));
/* Signals */
signals[TAG_CHANGED] = g_signal_new (
"tag_changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GnomeCanvasRichTextClass, tag_changed),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
G_TYPE_OBJECT);
item_class->update = gnome_canvas_rich_text_update;
item_class->realize = gnome_canvas_rich_text_realize;
item_class->unrealize = gnome_canvas_rich_text_unrealize;
item_class->draw = gnome_canvas_rich_text_draw;
item_class->point = gnome_canvas_rich_text_point;
item_class->event = gnome_canvas_rich_text_event;
item_class->bounds = gnome_canvas_rich_text_get_bounds;
} /* gnome_canvas_rich_text_class_init */
static void
gnome_canvas_rich_text_init (GnomeCanvasRichText *text)
{
text->_priv = GNOME_CANVAS_RICH_TEXT_GET_PRIVATE (text);
/* Try to set some sane defaults */
text->_priv->cursor_visible = TRUE;
text->_priv->cursor_blink = TRUE;
text->_priv->editable = TRUE;
text->_priv->visible = TRUE;
text->_priv->grow_height = FALSE;
text->_priv->wrap_mode = GTK_WRAP_WORD;
text->_priv->justification = GTK_JUSTIFY_LEFT;
text->_priv->direction = gtk_widget_get_default_direction ();
text->_priv->blink_timeout = 0;
text->_priv->preblink_timeout = 0;
text->_priv->clicks = 0;
text->_priv->click_timeout = 0;
} /* gnome_canvas_rich_text_init */
static void
gnome_canvas_rich_text_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (object);
switch (property_id) {
case PROP_TEXT:
if (text->_priv->text)
g_free (text->_priv->text);
text->_priv->text = g_value_dup_string (value);
gtk_text_buffer_set_text (
get_buffer (text), text->_priv->text, strlen (text->_priv->text));
break;
case PROP_X:
text->_priv->x = g_value_get_double (value);
break;
case PROP_Y:
text->_priv->y = g_value_get_double (value);
break;
case PROP_WIDTH:
text->_priv->width = g_value_get_double (value);
break;
case PROP_HEIGHT:
text->_priv->height = g_value_get_double (value);
break;
case PROP_EDITABLE:
text->_priv->editable = g_value_get_boolean (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->editable =
text->_priv->editable;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_VISIBLE:
text->_priv->visible = g_value_get_boolean (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->invisible =
!text->_priv->visible;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_CURSOR_VISIBLE:
text->_priv->cursor_visible = g_value_get_boolean (value);
if (text->_priv->layout) {
gtk_text_layout_set_cursor_visible (
text->_priv->layout, text->_priv->cursor_visible);
if (text->_priv->cursor_visible && text->_priv->cursor_blink) {
gnome_canvas_rich_text_start_cursor_blink (
text, FALSE);
}
else
gnome_canvas_rich_text_stop_cursor_blink (text);
}
break;
case PROP_CURSOR_BLINK:
text->_priv->cursor_blink = g_value_get_boolean (value);
if (text->_priv->layout && text->_priv->cursor_visible) {
if (text->_priv->cursor_blink && !text->_priv->blink_timeout) {
gnome_canvas_rich_text_start_cursor_blink (
text, FALSE);
}
else if (!text->_priv->cursor_blink && text->_priv->blink_timeout) {
gnome_canvas_rich_text_stop_cursor_blink (text);
gtk_text_layout_set_cursor_visible (
text->_priv->layout, TRUE);
}
}
break;
case PROP_GROW_HEIGHT:
text->_priv->grow_height = g_value_get_boolean (value);
/* FIXME: Recalc here */
break;
case PROP_WRAP_MODE:
text->_priv->wrap_mode = g_value_get_enum (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->wrap_mode =
text->_priv->wrap_mode;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_JUSTIFICATION:
text->_priv->justification = g_value_get_enum (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->justification =
text->_priv->justification;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_DIRECTION:
text->_priv->direction = g_value_get_enum (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->direction =
text->_priv->direction;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_PIXELS_ABOVE_LINES:
text->_priv->pixels_above_lines = g_value_get_int (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->pixels_above_lines =
text->_priv->pixels_above_lines;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_PIXELS_BELOW_LINES:
text->_priv->pixels_below_lines = g_value_get_int (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->pixels_below_lines =
text->_priv->pixels_below_lines;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_PIXELS_INSIDE_WRAP:
text->_priv->pixels_inside_wrap = g_value_get_int (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->pixels_inside_wrap =
text->_priv->pixels_inside_wrap;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_LEFT_MARGIN:
text->_priv->left_margin = g_value_get_int (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->left_margin =
text->_priv->left_margin;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_RIGHT_MARGIN:
text->_priv->right_margin = g_value_get_int (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->right_margin =
text->_priv->right_margin;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
case PROP_INDENT:
text->_priv->pixels_above_lines = g_value_get_int (value);
if (text->_priv->layout) {
text->_priv->layout->default_style->indent = text->_priv->indent;
gtk_text_layout_default_style_changed (text->_priv->layout);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
}
static void
gnome_canvas_rich_text_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (object);
switch (property_id) {
case PROP_TEXT:
g_value_set_string (value, text->_priv->text);
break;
case PROP_X:
g_value_set_double (value, text->_priv->x);
break;
case PROP_Y:
g_value_set_double (value, text->_priv->y);
break;
case PROP_HEIGHT:
g_value_set_double (value, text->_priv->height);
break;
case PROP_WIDTH:
g_value_set_double (value, text->_priv->width);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, text->_priv->editable);
break;
case PROP_CURSOR_VISIBLE:
g_value_set_boolean (value, text->_priv->cursor_visible);
break;
case PROP_CURSOR_BLINK:
g_value_set_boolean (value, text->_priv->cursor_blink);
break;
case PROP_GROW_HEIGHT:
g_value_set_boolean (value, text->_priv->grow_height);
break;
case PROP_WRAP_MODE:
g_value_set_enum (value, text->_priv->wrap_mode);
break;
case PROP_JUSTIFICATION:
g_value_set_enum (value, text->_priv->justification);
break;
case PROP_DIRECTION:
g_value_set_enum (value, text->_priv->direction);
break;
case PROP_PIXELS_ABOVE_LINES:
g_value_set_enum (value, text->_priv->pixels_above_lines);
break;
case PROP_PIXELS_BELOW_LINES:
g_value_set_int (value, text->_priv->pixels_below_lines);
break;
case PROP_PIXELS_INSIDE_WRAP:
g_value_set_int (value, text->_priv->pixels_inside_wrap);
break;
case PROP_LEFT_MARGIN:
g_value_set_int (value, text->_priv->left_margin);
break;
case PROP_RIGHT_MARGIN:
g_value_set_int (value, text->_priv->right_margin);
break;
case PROP_INDENT:
g_value_set_int (value, text->_priv->indent);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gnome_canvas_rich_text_realize (GnomeCanvasItem *item)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
(* GNOME_CANVAS_ITEM_CLASS (gnome_canvas_rich_text_parent_class)->realize)(item);
gnome_canvas_rich_text_ensure_layout (text);
} /* gnome_canvas_rich_text_realize */
static void
gnome_canvas_rich_text_unrealize (GnomeCanvasItem *item)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
gnome_canvas_rich_text_destroy_layout (text);
(* GNOME_CANVAS_ITEM_CLASS (gnome_canvas_rich_text_parent_class)->unrealize)(item);
} /* gnome_canvas_rich_text_unrealize */
static void
gnome_canvas_rich_text_move_iter_by_lines (GnomeCanvasRichText *text,
GtkTextIter *newplace,
gint count)
{
while (count < 0) {
gtk_text_layout_move_iter_to_previous_line (
text->_priv->layout, newplace);
count++;
}
while (count > 0) {
gtk_text_layout_move_iter_to_next_line (
text->_priv->layout, newplace);
count--;
}
} /* gnome_canvas_rich_text_move_iter_by_lines */
static gint
gnome_canvas_rich_text_get_cursor_x_position (GnomeCanvasRichText *text)
{
GtkTextIter insert;
GdkRectangle rect;
gtk_text_buffer_get_iter_at_mark (
get_buffer (text), &insert,
gtk_text_buffer_get_mark(get_buffer(text), "insert"));
gtk_text_layout_get_cursor_locations (
text->_priv->layout, &insert, &rect, NULL);
return rect.x;
} /* gnome_canvas_rich_text_get_cursor_x_position */
static void
gnome_canvas_rich_text_move_cursor (GnomeCanvasRichText *text,
GtkMovementStep step,
gint count,
gboolean extend_selection)
{
GtkTextIter insert, newplace;
gtk_text_buffer_get_iter_at_mark (
get_buffer (text), &insert,
gtk_text_buffer_get_mark(get_buffer(text), "insert"));
newplace = insert;
switch (step) {
case GTK_MOVEMENT_LOGICAL_POSITIONS:
gtk_text_iter_forward_cursor_positions (&newplace, count);
break;
case GTK_MOVEMENT_VISUAL_POSITIONS:
gtk_text_layout_move_iter_visually (
text->_priv->layout, &newplace, count);
break;
case GTK_MOVEMENT_WORDS:
if (count < 0)
gtk_text_iter_backward_word_starts (&newplace, -count);
else if (count > 0)
gtk_text_iter_forward_word_ends (&newplace, count);
break;
case GTK_MOVEMENT_DISPLAY_LINES:
gnome_canvas_rich_text_move_iter_by_lines (
text, &newplace, count);
gtk_text_layout_move_iter_to_x (
text->_priv->layout, &newplace,
gnome_canvas_rich_text_get_cursor_x_position (text));
break;
case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
if (count > 1) {
gnome_canvas_rich_text_move_iter_by_lines (
text, &newplace, --count);
}
else if (count < -1) {
gnome_canvas_rich_text_move_iter_by_lines (
text, &newplace, ++count);
}
if (count != 0) {
gtk_text_layout_move_iter_to_line_end (
text->_priv->layout, &newplace, count);
}
break;
case GTK_MOVEMENT_PARAGRAPHS:
/* FIXME: Busted in gtktextview.c too */
break;
case GTK_MOVEMENT_PARAGRAPH_ENDS:
if (count > 0)
gtk_text_iter_forward_to_line_end (&newplace);
else if (count < 0)
gtk_text_iter_set_line_offset (&newplace, 0);
break;
case GTK_MOVEMENT_BUFFER_ENDS:
if (count > 0) {
gtk_text_buffer_get_end_iter (
get_buffer (text), &newplace);
}
else if (count < 0) {
gtk_text_buffer_get_iter_at_offset (
get_buffer (text), &newplace, 0);
}
break;
default:
break;
}
if (!gtk_text_iter_equal (&insert, &newplace)) {
if (extend_selection) {
gtk_text_buffer_move_mark (
get_buffer (text),
gtk_text_buffer_get_mark (
get_buffer(text), "insert"),
&newplace);
}
else {
gtk_text_buffer_place_cursor (
get_buffer (text), &newplace);
}
}
gnome_canvas_rich_text_start_cursor_blink (text, TRUE);
} /* gnome_canvas_rich_text_move_cursor */
static gboolean
whitespace (gunichar ch,
gpointer user_data)
{
return (ch == ' ' || ch == '\t');
} /* whitespace */
static gboolean
not_whitespace (gunichar ch,
gpointer user_data)
{
return !whitespace (ch, user_data);
} /* not_whitespace */
static gboolean
find_whitespace_region (const GtkTextIter *center,
GtkTextIter *start,
GtkTextIter *end)
{
*start = *center;
*end = *center;
if (gtk_text_iter_backward_find_char (start, not_whitespace, NULL, NULL))
gtk_text_iter_forward_char (start);
if (whitespace (gtk_text_iter_get_char (end), NULL))
gtk_text_iter_forward_find_char (end, not_whitespace, NULL, NULL);
return !gtk_text_iter_equal (start, end);
} /* find_whitespace_region */
static void
gnome_canvas_rich_text_delete_from_cursor (GnomeCanvasRichText *text,
GtkDeleteType type,
gint count)
{
GtkTextIter insert, start, end;
/* Special case: If the user wants to delete a character and there is
* a selection, then delete the selection and return */
if (type == GTK_DELETE_CHARS) {
if (gtk_text_buffer_delete_selection (get_buffer (text), TRUE,
text->_priv->editable))
return;
}
gtk_text_buffer_get_iter_at_mark (
get_buffer (text), &insert,
gtk_text_buffer_get_mark(get_buffer(text), "insert"));
start = insert;
end = insert;
switch (type) {
case GTK_DELETE_CHARS:
gtk_text_iter_forward_cursor_positions (&end, count);
break;
case GTK_DELETE_WORD_ENDS:
if (count > 0)
gtk_text_iter_forward_word_ends (&end, count);
else if (count < 0)
gtk_text_iter_backward_word_starts (&start, -count);
break;
case GTK_DELETE_WORDS:
break;
case GTK_DELETE_DISPLAY_LINE_ENDS:
break;
case GTK_DELETE_PARAGRAPH_ENDS:
if (gtk_text_iter_ends_line (&end)) {
gtk_text_iter_forward_line (&end);
--count;
}
while (count > 0) {
if (!gtk_text_iter_forward_to_line_end (&end))
break;
--count;
}
break;
case GTK_DELETE_PARAGRAPHS:
if (count > 0) {
gtk_text_iter_set_line_offset (&start, 0);
gtk_text_iter_forward_to_line_end (&end);
/* Do the lines beyond the first. */
while (count > 1) {
gtk_text_iter_forward_to_line_end (&end);
--count;
}
}
break;
case GTK_DELETE_WHITESPACE:
find_whitespace_region (&insert, &start, &end);
break;
default:
break;
}
if (!gtk_text_iter_equal (&start, &end)) {
gtk_text_buffer_begin_user_action (get_buffer (text));
gtk_text_buffer_delete_interactive (
get_buffer (text), &start, &end, text->_priv->editable);
gtk_text_buffer_end_user_action (get_buffer (text));
}
} /* gnome_canvas_rich_text_delete_from_cursor */
static gint
selection_motion_event_handler (GnomeCanvasRichText *text,
GdkEvent *event,
gpointer data)
{
GtkTextIter newplace;
GtkTextMark *mark;
gdouble newx, newy;
/* We only want to handle motion events... */
if (event->type != GDK_MOTION_NOTIFY)
return FALSE;
newx = (event->motion.x - text->_priv->x);
newy = (event->motion.y - text->_priv->y);
gtk_text_layout_get_iter_at_pixel (text->_priv->layout, &newplace, newx, newy);
mark = gtk_text_buffer_get_mark(get_buffer(text), "insert");
gtk_text_buffer_move_mark (get_buffer (text), mark, &newplace);
return TRUE;
} /* selection_motion_event_handler */
static void
gnome_canvas_rich_text_start_selection_drag (GnomeCanvasRichText *text,
const GtkTextIter *iter,
GdkEventButton *button)
{
GtkTextIter newplace;
g_return_if_fail (text->_priv->selection_drag_handler == 0);
#if 0
gnome_canvas_item_grab_focus (GNOME_CANVAS_ITEM (text));
#endif
newplace = *iter;
gtk_text_buffer_place_cursor (get_buffer (text), &newplace);
text->_priv->selection_drag_handler = g_signal_connect (
text, "event",
G_CALLBACK (selection_motion_event_handler),
NULL);
} /* gnome_canvas_rich_text_start_selection_drag */
static gboolean
gnome_canvas_rich_text_end_selection_drag (GnomeCanvasRichText *text,
GdkEventButton *event)
{
if (text->_priv->selection_drag_handler == 0)
return FALSE;
g_signal_handler_disconnect (text, text->_priv->selection_drag_handler);
text->_priv->selection_drag_handler = 0;
#if 0
gnome_canvas_item_grab (NULL);
#endif
return TRUE;
} /* gnome_canvas_rich_text_end_selection_drag */
static void
gnome_canvas_rich_text_emit_tag_changed (GnomeCanvasRichText *text,
GtkTextTag *tag)
{
g_signal_emit (G_OBJECT (text), signals[TAG_CHANGED], 0, tag);
} /* gnome_canvas_rich_text_emit_tag_changed */
static gint
gnome_canvas_rich_text_key_press_event (GnomeCanvasItem *item,
GdkEventKey *event)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
gboolean extend_selection = FALSE;
gboolean handled = FALSE;
#if 0
printf("Key press event\n");
#endif
if (!text->_priv->layout || !text->_priv->buffer)
return FALSE;
if (event->state & GDK_SHIFT_MASK)
extend_selection = TRUE;
switch (event->keyval) {
case GDK_KEY_Return:
case GDK_KEY_KP_Enter:
gtk_text_buffer_delete_selection (
get_buffer (text), TRUE, text->_priv->editable);
gtk_text_buffer_insert_interactive_at_cursor (
get_buffer(text), "\n", 1, text->_priv->editable);
handled = TRUE;
break;
case GDK_KEY_Tab:
gtk_text_buffer_insert_interactive_at_cursor (
get_buffer(text), "\t", 1, text->_priv->editable);
handled = TRUE;
break;
/* MOVEMENT */
case GDK_KEY_Right:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_WORDS, 1,
extend_selection);
handled = TRUE;
}
else {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_VISUAL_POSITIONS, 1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_Left:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_WORDS, -1,
extend_selection);
handled = TRUE;
}
else {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_VISUAL_POSITIONS, -1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_f:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_LOGICAL_POSITIONS, 1,
extend_selection);
handled = TRUE;
}
else if (event->state & GDK_MOD1_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_WORDS, 1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_b:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_LOGICAL_POSITIONS, -1,
extend_selection);
handled = TRUE;
}
else if (event->state & GDK_MOD1_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_WORDS, -1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_Up:
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_DISPLAY_LINES, -1,
extend_selection);
handled = TRUE;
break;
case GDK_KEY_Down:
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_DISPLAY_LINES, 1,
extend_selection);
handled = TRUE;
break;
case GDK_KEY_p:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_DISPLAY_LINES, -1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_n:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_DISPLAY_LINES, 1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_Home:
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_PARAGRAPH_ENDS, -1,
extend_selection);
handled = TRUE;
break;
case GDK_KEY_End:
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_PARAGRAPH_ENDS, 1,
extend_selection);
handled = TRUE;
break;
case GDK_KEY_a:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_PARAGRAPH_ENDS, -1,
extend_selection);
handled = TRUE;
}
break;
case GDK_KEY_e:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_move_cursor (
text, GTK_MOVEMENT_PARAGRAPH_ENDS, 1,
extend_selection);
handled = TRUE;
}
break;
/* DELETING TEXT */
case GDK_KEY_Delete:
case GDK_KEY_KP_Delete:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_WORD_ENDS, 1);
handled = TRUE;
}
else {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_CHARS, 1);
handled = TRUE;
}
break;
case GDK_KEY_d:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_CHARS, 1);
handled = TRUE;
}
else if (event->state & GDK_MOD1_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_WORD_ENDS, 1);
handled = TRUE;
}
break;
case GDK_KEY_BackSpace:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_WORD_ENDS, -1);
handled = TRUE;
}
else {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_CHARS, -1);
}
handled = TRUE;
break;
case GDK_KEY_k:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_PARAGRAPH_ENDS, 1);
handled = TRUE;
}
break;
case GDK_KEY_u:
if (event->state & GDK_CONTROL_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_PARAGRAPHS, 1);
handled = TRUE;
}
break;
case GDK_KEY_space:
if (event->state & GDK_MOD1_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_WHITESPACE, 1);
handled = TRUE;
}
break;
case GDK_KEY_backslash:
if (event->state & GDK_MOD1_MASK) {
gnome_canvas_rich_text_delete_from_cursor (
text, GTK_DELETE_WHITESPACE, 1);
handled = TRUE;
}
break;
default:
break;
}
/* An empty string, click just pressing "Alt" by itself or whatever. */
if (!event->length)
return FALSE;
if (!handled) {
gtk_text_buffer_delete_selection (
get_buffer (text), TRUE, text->_priv->editable);
gtk_text_buffer_insert_interactive_at_cursor (
get_buffer (text), event->string, event->length,
text->_priv->editable);
}
gnome_canvas_rich_text_start_cursor_blink (text, TRUE);
return TRUE;
} /* gnome_canvas_rich_text_key_press_event */
static gint
gnome_canvas_rich_text_key_release_event (GnomeCanvasItem *item,
GdkEventKey *event)
{
return FALSE;
} /* gnome_canvas_rich_text_key_release_event */
static gboolean
_click (gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
text->_priv->clicks = 0;
text->_priv->click_timeout = 0;
return FALSE;
} /* _click */
static gint
gnome_canvas_rich_text_button_press_event (GnomeCanvasItem *item,
GdkEventButton *event)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
GtkTextIter iter;
GdkEventType event_type;
gdouble newx, newy;
newx = (event->x - text->_priv->x);
newy = (event->y - text->_priv->y);
gtk_text_layout_get_iter_at_pixel (text->_priv->layout, &iter, newx, newy);
/* The canvas doesn't give us double- or triple-click events, so
* we have to synthesize them ourselves. Yay. */
event_type = event->type;
if (event_type == GDK_BUTTON_PRESS) {
text->_priv->clicks++;
text->_priv->click_timeout = g_timeout_add (400, _click, text);
if (text->_priv->clicks > 3)
text->_priv->clicks = text->_priv->clicks % 3;
if (text->_priv->clicks == 1)
event_type = GDK_BUTTON_PRESS;
else if (text->_priv->clicks == 2)
event_type = GDK_2BUTTON_PRESS;
else if (text->_priv->clicks == 3)
event_type = GDK_3BUTTON_PRESS;
else
printf("ZERO CLICKS!\n");
}
if (event->button == 1 && event_type == GDK_BUTTON_PRESS) {
GtkTextIter start, end;
if (gtk_text_buffer_get_selection_bounds (
get_buffer (text), &start, &end) &&
gtk_text_iter_in_range (&iter, &start, &end)) {
text->_priv->drag_start_x = event->x;
text->_priv->drag_start_y = event->y;
}
else {
gnome_canvas_rich_text_start_selection_drag (
text, &iter, event);
}
return TRUE;
}
else if (event->button == 1 && event_type == GDK_2BUTTON_PRESS) {
GtkTextIter start, end;
#if 0
printf("double-click\n");
#endif
gnome_canvas_rich_text_end_selection_drag (text, event);
start = iter;
end = start;
if (gtk_text_iter_inside_word (&start)) {
if (!gtk_text_iter_starts_word (&start))
gtk_text_iter_backward_word_start (&start);
if (!gtk_text_iter_ends_word (&end))
gtk_text_iter_forward_word_end (&end);
}
gtk_text_buffer_move_mark (
get_buffer (text),
gtk_text_buffer_get_selection_bound (get_buffer (text)),
&start);
gtk_text_buffer_move_mark (
get_buffer (text),
gtk_text_buffer_get_insert (get_buffer (text)), &end);
text->_priv->just_selected_element = TRUE;
return TRUE;
}
else if (event->button == 1 && event_type == GDK_3BUTTON_PRESS) {
GtkTextIter start, end;
#if 0
printf("triple-click\n");
#endif
gnome_canvas_rich_text_end_selection_drag (text, event);
start = iter;
end = start;
if (gtk_text_layout_iter_starts_line (text->_priv->layout, &start)) {
gtk_text_layout_move_iter_to_line_end (
text->_priv->layout, &start, -1);
}
else {
gtk_text_layout_move_iter_to_line_end (
text->_priv->layout, &start, -1);
if (!gtk_text_layout_iter_starts_line (
text->_priv->layout, &end)) {
gtk_text_layout_move_iter_to_line_end (
text->_priv->layout, &end, 1);
}
}
gtk_text_buffer_move_mark (
get_buffer (text),
gtk_text_buffer_get_selection_bound (get_buffer (text)),
&start);
gtk_text_buffer_move_mark (
get_buffer (text),
gtk_text_buffer_get_insert (get_buffer (text)), &end);
text->_priv->just_selected_element = TRUE;
return TRUE;
}
else if (event->button == 2 && event_type == GDK_BUTTON_PRESS) {
gtk_text_buffer_paste_clipboard (
get_buffer (text),
gtk_clipboard_get (GDK_SELECTION_PRIMARY),
&iter, text->_priv->editable);
}
return FALSE;
} /* gnome_canvas_rich_text_button_press_event */
static gint
gnome_canvas_rich_text_button_release_event (GnomeCanvasItem *item,
GdkEventButton *event)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
gdouble newx, newy;
newx = (event->x - text->_priv->x);
newy = (event->y - text->_priv->y);
if (event->button == 1) {
if (text->_priv->drag_start_x >= 0) {
text->_priv->drag_start_x = -1;
text->_priv->drag_start_y = -1;
}
if (gnome_canvas_rich_text_end_selection_drag (text, event))
return TRUE;
else if (text->_priv->just_selected_element) {
text->_priv->just_selected_element = FALSE;
return FALSE;
}
else {
GtkTextIter iter;
gtk_text_layout_get_iter_at_pixel (
text->_priv->layout, &iter, newx, newy);
gtk_text_buffer_place_cursor (get_buffer (text), &iter);
return FALSE;
}
}
return FALSE;
} /* gnome_canvas_rich_text_button_release_event */
static gint
gnome_canvas_rich_text_focus_in_event (GnomeCanvasItem *item,
GdkEventFocus *event)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
if (text->_priv->cursor_visible && text->_priv->layout) {
gtk_text_layout_set_cursor_visible (text->_priv->layout, TRUE);
gnome_canvas_rich_text_start_cursor_blink (text, FALSE);
}
return FALSE;
} /* gnome_canvas_rich_text_focus_in_event */
static gint
gnome_canvas_rich_text_focus_out_event (GnomeCanvasItem *item,
GdkEventFocus *event)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
if (text->_priv->cursor_visible && text->_priv->layout) {
gtk_text_layout_set_cursor_visible (text->_priv->layout, FALSE);
gnome_canvas_rich_text_stop_cursor_blink (text);
}
return FALSE;
} /* gnome_canvas_rich_text_focus_out_event */
static gboolean
get_event_coordinates (GdkEvent *event,
gint *x,
gint *y)
{
g_return_val_if_fail (event, FALSE);
switch (event->type) {
case GDK_MOTION_NOTIFY:
*x = event->motion.x;
*y = event->motion.y;
return TRUE;
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
*x = event->button.x;
*y = event->button.y;
return TRUE;
default:
return FALSE;
}
} /* get_event_coordinates */
static void
emit_event_on_tags (GnomeCanvasRichText *text,
GdkEvent *event,
GtkTextIter *iter)
{
GSList *tags;
GSList *i;
tags = gtk_text_iter_get_tags (iter);
i = tags;
while (i) {
GtkTextTag *tag = i->data;
gtk_text_tag_event (tag, G_OBJECT (text), event, iter);
/* The cursor has been moved to within this tag. Emit the
* tag_changed signal */
if (event->type == GDK_BUTTON_RELEASE ||
event->type == GDK_KEY_PRESS ||
event->type == GDK_KEY_RELEASE) {
gnome_canvas_rich_text_emit_tag_changed (
text, tag);
}
i = g_slist_next (i);
}
g_slist_free (tags);
} /* emit_event_on_tags */
static gint
gnome_canvas_rich_text_event (GnomeCanvasItem *item,
GdkEvent *event)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
gint x, y;
if (get_event_coordinates (event, &x, &y)) {
GtkTextIter iter;
x -= text->_priv->x;
y -= text->_priv->y;
gtk_text_layout_get_iter_at_pixel (text->_priv->layout, &iter, x, y);
emit_event_on_tags (text, event, &iter);
}
else if (event->type == GDK_KEY_PRESS ||
event->type == GDK_KEY_RELEASE) {
GtkTextMark *insert;
GtkTextIter iter;
insert = gtk_text_buffer_get_mark(get_buffer(text), "insert");
gtk_text_buffer_get_iter_at_mark (
get_buffer (text), &iter, insert);
emit_event_on_tags (text, event, &iter);
}
switch (event->type) {
case GDK_KEY_PRESS:
return gnome_canvas_rich_text_key_press_event (
item, (GdkEventKey *) event);
case GDK_KEY_RELEASE:
return gnome_canvas_rich_text_key_release_event (
item, (GdkEventKey *) event);
case GDK_BUTTON_PRESS:
return gnome_canvas_rich_text_button_press_event (
item, (GdkEventButton *) event);
case GDK_BUTTON_RELEASE:
return gnome_canvas_rich_text_button_release_event (
item, (GdkEventButton *) event);
case GDK_FOCUS_CHANGE:
{
GtkLayout *layout;
GdkWindow *bin_window;
layout = GTK_LAYOUT (item->canvas);
bin_window = gtk_layout_get_bin_window (layout);
if (((GdkEventFocus *) event)->window != bin_window)
return FALSE;
if (((GdkEventFocus *) event)->in)
return gnome_canvas_rich_text_focus_in_event (
item, (GdkEventFocus *) event);
else
return gnome_canvas_rich_text_focus_out_event (
item, (GdkEventFocus *) event);
}
default:
return FALSE;
}
} /* gnome_canvas_rich_text_event */
/* Cut/Copy/Paste */
/**
* gnome_canvas_rich_text_cut_clipboard:
* @text: a #GnomeCanvasRichText.
*
* Copies the currently selected @text to clipboard, then deletes said text
* if it's editable.
**/
void
gnome_canvas_rich_text_cut_clipboard (GnomeCanvasRichText *text)
{
g_return_if_fail (text);
g_return_if_fail (get_buffer (text));
gtk_text_buffer_cut_clipboard (get_buffer (text),
gtk_clipboard_get (GDK_SELECTION_PRIMARY),
text->_priv->editable);
} /* gnome_canvas_rich_text_cut_clipboard */
/**
* gnome_canvas_rich_text_copy_clipboard:
* @text: a #GnomeCanvasRichText.
*
* Copies the currently selected @text to clipboard.
**/
void
gnome_canvas_rich_text_copy_clipboard (GnomeCanvasRichText *text)
{
g_return_if_fail (text);
g_return_if_fail (get_buffer (text));
gtk_text_buffer_copy_clipboard (get_buffer (text),
gtk_clipboard_get (GDK_SELECTION_PRIMARY));
} /* gnome_canvas_rich_text_cut_clipboard */
/**
* gnome_canvas_rich_text_paste_clipboard:
* @text: a #GnomeCanvasRichText.
*
* Pastes the contents of the clipboard at the insertion point.
**/
void
gnome_canvas_rich_text_paste_clipboard (GnomeCanvasRichText *text)
{
g_return_if_fail (text);
g_return_if_fail (get_buffer (text));
gtk_text_buffer_paste_clipboard (get_buffer (text),
gtk_clipboard_get (GDK_SELECTION_PRIMARY),
NULL,
text->_priv->editable);
} /* gnome_canvas_rich_text_cut_clipboard */
static gint
preblink_cb (gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
text->_priv->preblink_timeout = 0;
gnome_canvas_rich_text_start_cursor_blink (text, FALSE);
/* Remove ourselves */
return FALSE;
} /* preblink_cb */
static gboolean
blink_cb (gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
gboolean visible;
g_return_val_if_fail (text->_priv->layout, FALSE);
g_return_val_if_fail (text->_priv->cursor_visible, FALSE);
visible = gtk_text_layout_get_cursor_visible (text->_priv->layout);
if (visible)
text->_priv->blink_timeout = g_timeout_add (
CURSOR_OFF_TIME, blink_cb, text);
else
text->_priv->blink_timeout = g_timeout_add (
CURSOR_ON_TIME, blink_cb, text);
gtk_text_layout_set_cursor_visible (text->_priv->layout, !visible);
/* Remove ourself */
return FALSE;
} /* blink_cb */
static void
gnome_canvas_rich_text_start_cursor_blink (GnomeCanvasRichText *text,
gboolean with_delay)
{
if (!text->_priv->layout)
return;
if (!text->_priv->cursor_visible || !text->_priv->cursor_blink)
return;
if (text->_priv->preblink_timeout != 0) {
g_source_remove (text->_priv->preblink_timeout);
text->_priv->preblink_timeout = 0;
}
if (with_delay) {
if (text->_priv->blink_timeout != 0) {
g_source_remove (text->_priv->blink_timeout);
text->_priv->blink_timeout = 0;
}
gtk_text_layout_set_cursor_visible (text->_priv->layout, TRUE);
text->_priv->preblink_timeout = g_timeout_add (
PREBLINK_TIME, preblink_cb, text);
}
else {
if (text->_priv->blink_timeout == 0) {
gtk_text_layout_set_cursor_visible (text->_priv->layout, TRUE);
text->_priv->blink_timeout = g_timeout_add (
CURSOR_ON_TIME, blink_cb, text);
}
}
} /* gnome_canvas_rich_text_start_cursor_blink */
static void
gnome_canvas_rich_text_stop_cursor_blink (GnomeCanvasRichText *text)
{
if (text->_priv->blink_timeout) {
g_source_remove (text->_priv->blink_timeout);
text->_priv->blink_timeout = 0;
}
} /* gnome_canvas_rich_text_stop_cursor_blink */
/* We have to request updates this way because the update cycle is not
* re-entrant. This will fire off a request in an idle loop. */
static gboolean
request_update (gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
return FALSE;
} /* request_update */
static void
invalidated_handler (GtkTextLayout *layout,
gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
#if 0
printf("Text is being invalidated.\n");
#endif
gtk_text_layout_validate (text->_priv->layout, 2000);
/* We are called from the update cycle; gotta put this in an idle
* loop. */
g_idle_add (request_update, text);
} /* invalidated_handler */
static void
scale_fonts (GtkTextTag *tag,
gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
/* XXX GtkTextTag::values is sealed with apparently no way
* to access it. This looks like a small optimization
* anyway. */
#if 0
if (!tag->values)
return;
#endif
g_object_set (
G_OBJECT(tag), "scale",
text->_priv->layout->default_style->font_scale, NULL);
} /* scale_fonts */
static void
changed_handler (GtkTextLayout *layout,
gint start_y,
gint old_height,
gint new_height,
gpointer data)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (data);
#if 0
printf("Layout %p is being changed.\n", text->_priv->layout);
#endif
if (text->_priv->layout->default_style->font_scale != 1.0) {
GtkTextTagTable *tag_table;
text->_priv->layout->default_style->font_scale = 1.0;
tag_table = gtk_text_buffer_get_tag_table (get_buffer (text));
gtk_text_tag_table_foreach (tag_table, scale_fonts, text);
gtk_text_layout_default_style_changed (text->_priv->layout);
}
if (text->_priv->grow_height) {
gint width, height;
gtk_text_layout_get_size (text->_priv->layout, &width, &height);
if (height > text->_priv->height)
text->_priv->height = height;
}
/* We are called from the update cycle; gotta put this in an idle
* loop. */
g_idle_add (request_update, text);
} /* changed_handler */
/**
* gnome_canvas_rich_text_set_buffer:
* @text: a #GnomeCanvasRichText.
* @buffer: a #GtkTextBuffer.
*
* Sets the buffer field of the @text to @buffer.
**/
void
gnome_canvas_rich_text_set_buffer (GnomeCanvasRichText *text,
GtkTextBuffer *buffer)
{
g_return_if_fail (GNOME_IS_CANVAS_RICH_TEXT (text));
g_return_if_fail (buffer == NULL || GTK_IS_TEXT_BUFFER (buffer));
if (text->_priv->buffer == buffer)
return;
if (text->_priv->buffer != NULL) {
g_object_unref (G_OBJECT (text->_priv->buffer));
}
text->_priv->buffer = buffer;
if (buffer) {
g_object_ref (G_OBJECT (buffer));
if (text->_priv->layout)
gtk_text_layout_set_buffer (text->_priv->layout, buffer);
}
gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
} /* gnome_canvas_rich_text_set_buffer */
static GtkTextBuffer *
get_buffer (GnomeCanvasRichText *text)
{
if (!text->_priv->buffer) {
GtkTextBuffer *b;
b = gtk_text_buffer_new (NULL);
gnome_canvas_rich_text_set_buffer (text, b);
g_object_unref (G_OBJECT (b));
}
return text->_priv->buffer;
} /* get_buffer */
/**
* gnome_canvas_rich_text_get_buffer:
* @text: a #GnomeCanvasRichText.
*
* Returns a #GtkTextBuffer associated with the #GnomeCanvasRichText.
* This function creates a new #GtkTextBuffer if the text buffer is NULL.
*
* Return value: the #GtkTextBuffer.
**/
GtkTextBuffer *
gnome_canvas_rich_text_get_buffer (GnomeCanvasRichText *text)
{
g_return_val_if_fail (GNOME_IS_CANVAS_RICH_TEXT (text), NULL);
return get_buffer (text);
} /* gnome_canvas_rich_text_get_buffer */
/**
* gnome_canvas_rich_text_get_iter_location:
* @text: a #GnomeCanvasRichText.
* @iter: a #GtkTextIter.
* @location: a #GdkRectangle containing the bounds of the character at @iter.
*
* Gets a rectangle which roughly contains the character at @iter.
**/
void
gnome_canvas_rich_text_get_iter_location (GnomeCanvasRichText *text,
const GtkTextIter *iter,
GdkRectangle *location)
{
g_return_if_fail (GNOME_IS_CANVAS_RICH_TEXT (text));
g_return_if_fail (gtk_text_iter_get_buffer (iter) == text->_priv->buffer);
gtk_text_layout_get_iter_location (text->_priv->layout, iter, location);
}
/**
* gnome_canvas_rich_text_get_iter_at_location:
* @text: a #GnomeCanvasRichText.
* @iter: a #GtkTextIter.
* @x: x position, in buffer coordinates.
* @y: y position, in buffer coordinates.
*
* Retrieves the iterator at the buffer coordinates x and y.
**/
void
gnome_canvas_rich_text_get_iter_at_location (GnomeCanvasRichText *text,
GtkTextIter *iter,
gint x,
gint y)
{
g_return_if_fail (GNOME_IS_CANVAS_RICH_TEXT (text));
g_return_if_fail (iter != NULL);
g_return_if_fail (text->_priv->layout != NULL);
gtk_text_layout_get_iter_at_pixel (text->_priv->layout,
iter,
x,
y);
}
static void
gnome_canvas_rich_text_set_attributes_from_style (GnomeCanvasRichText *text,
GtkTextAttributes *values,
GtkStyle *style)
{
values->appearance.bg_color = style->base[GTK_STATE_NORMAL];
values->appearance.fg_color = style->fg[GTK_STATE_NORMAL];
if (values->font)
pango_font_description_free (values->font);
values->font = pango_font_description_copy (style->font_desc);
} /* gnome_canvas_rich_text_set_attributes_from_style */
static void
gnome_canvas_rich_text_ensure_layout (GnomeCanvasRichText *text)
{
if (!text->_priv->layout) {
GtkWidget *canvas;
GtkTextAttributes *style;
PangoContext *ltr_context, *rtl_context;
text->_priv->layout = gtk_text_layout_new ();
gtk_text_layout_set_screen_width (text->_priv->layout, text->_priv->width);
if (get_buffer (text)) {
gtk_text_layout_set_buffer (
text->_priv->layout, get_buffer (text));
}
/* Setup the cursor stuff */
gtk_text_layout_set_cursor_visible (
text->_priv->layout, text->_priv->cursor_visible);
if (text->_priv->cursor_visible && text->_priv->cursor_blink)
gnome_canvas_rich_text_start_cursor_blink (text, FALSE);
else
gnome_canvas_rich_text_stop_cursor_blink (text);
canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas);
ltr_context = gtk_widget_create_pango_context (canvas);
pango_context_set_base_dir (ltr_context, PANGO_DIRECTION_LTR);
rtl_context = gtk_widget_create_pango_context (canvas);
pango_context_set_base_dir (rtl_context, PANGO_DIRECTION_RTL);
gtk_text_layout_set_contexts (
text->_priv->layout, ltr_context, rtl_context);
g_object_unref (G_OBJECT (ltr_context));
g_object_unref (G_OBJECT (rtl_context));
style = gtk_text_attributes_new ();
gnome_canvas_rich_text_set_attributes_from_style (
text, style, gtk_widget_get_style (canvas));
style->pixels_above_lines = text->_priv->pixels_above_lines;
style->pixels_below_lines = text->_priv->pixels_below_lines;
style->pixels_inside_wrap = text->_priv->pixels_inside_wrap;
style->left_margin = text->_priv->left_margin;
style->right_margin = text->_priv->right_margin;
style->indent = text->_priv->indent;
style->tabs = NULL;
style->wrap_mode = text->_priv->wrap_mode;
style->justification = text->_priv->justification;
style->direction = text->_priv->direction;
style->editable = text->_priv->editable;
style->invisible = !text->_priv->visible;
gtk_text_layout_set_default_style (text->_priv->layout, style);
gtk_text_attributes_unref (style);
g_signal_connect (
G_OBJECT(text->_priv->layout), "invalidated",
G_CALLBACK (invalidated_handler), text);
g_signal_connect (
G_OBJECT(text->_priv->layout), "changed",
G_CALLBACK (changed_handler), text);
}
} /* gnome_canvas_rich_text_ensure_layout */
static void
gnome_canvas_rich_text_destroy_layout (GnomeCanvasRichText *text)
{
if (text->_priv->layout) {
g_signal_handlers_disconnect_by_func (
G_OBJECT (text->_priv->layout), invalidated_handler, text);
g_signal_handlers_disconnect_by_func (
G_OBJECT (text->_priv->layout), changed_handler, text);
g_object_unref (G_OBJECT (text->_priv->layout));
text->_priv->layout = NULL;
}
} /* gnome_canvas_rich_text_destroy_layout */
static void
get_bounds (GnomeCanvasRichText *text,
gdouble *px1,
gdouble *py1,
gdouble *px2,
gdouble *py2)
{
GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
gdouble x, y;
gdouble x1, x2, y1, y2;
gint cx1, cx2, cy1, cy2;
x = text->_priv->x;
y = text->_priv->y;
x1 = x;
y1 = y;
x2 = x + text->_priv->width;
y2 = y + text->_priv->height;
gnome_canvas_item_i2w (item, &x1, &y1);
gnome_canvas_item_i2w (item, &x2, &y2);
gnome_canvas_w2c (item->canvas, x1, y1, &cx1, &cy1);
gnome_canvas_w2c (item->canvas, x2, y2, &cx2, &cy2);
*px1 = cx1;
*py1 = cy1;
*px2 = cx2;
*py2 = cy2;
} /* get_bounds */
static void
gnome_canvas_rich_text_get_bounds (GnomeCanvasItem *item,
gdouble *px1,
gdouble *py1,
gdouble *px2,
gdouble *py2)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
get_bounds (text, px1, py1, px2, py2);
}
static void
gnome_canvas_rich_text_update (GnomeCanvasItem *item,
const cairo_matrix_t *matrix,
gint flags)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
gdouble x1, y1, x2, y2;
GtkTextIter start;
(* GNOME_CANVAS_ITEM_CLASS (gnome_canvas_rich_text_parent_class)->
update)(item, matrix, flags);
get_bounds (text, &x1, &y1, &x2, &y2);
gtk_text_buffer_get_iter_at_offset (text->_priv->buffer, &start, 0);
if (text->_priv->layout)
gtk_text_layout_validate_yrange (
text->_priv->layout, &start, 0, y2 - y1);
gnome_canvas_update_bbox (item, x1, y1, x2, y2);
} /* gnome_canvas_rich_text_update */
static GnomeCanvasItem *
gnome_canvas_rich_text_point (GnomeCanvasItem *item,
gdouble x,
gdouble y,
gint cx,
gint cy)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
gdouble ax, ay;
gdouble x1, x2, y1, y2;
/* This is a lame cop-out. Anywhere inside of the bounding box. */
ax = text->_priv->x;
ay = text->_priv->y;
x1 = ax;
y1 = ay;
x2 = ax + text->_priv->width;
y2 = ay + text->_priv->height;
if ((x > x1) && (y > y1) && (x < x2) && (y < y2))
return item;
return NULL;
} /* gnome_canvas_rich_text_point */
static void
gnome_canvas_rich_text_draw (GnomeCanvasItem *item,
cairo_t *cr,
gint x,
gint y,
gint width,
gint height)
{
GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT (item);
GtkWidget *widget;
cairo_matrix_t i2c;
gdouble ax, ay, ax2, ay2;
gint x1, x2;
gnome_canvas_item_i2c_matrix (item, &i2c);
ax = text->_priv->x;
ay = text->_priv->y;
ax2 = ax + text->_priv->width;
ay2 = ay + text->_priv->height;
cairo_matrix_transform_point (&i2c, &ax, &ay);
cairo_matrix_transform_point (&i2c, &ax2, &ay2);
x1 = ax;
x2 = ax2;
gtk_text_layout_set_screen_width (text->_priv->layout, x2 - x1);
widget = GTK_WIDGET (item->canvas);
/* FIXME: should last arg be NULL? */
gtk_text_layout_draw (text->_priv->layout, widget, cr, NULL);
} /* gnome_canvas_rich_text_draw */
#if 0
static GtkTextTag *
gnome_canvas_rich_text_add_tag (GnomeCanvasRichText *text,
gchar *tag_name,
gint start_offset,
gint end_offset,
const gchar *first_property_name,
...)
{
GtkTextTag *tag;
GtkTextIter start, end;
va_list var_args;
g_return_val_if_fail (text, NULL);
g_return_val_if_fail (start_offset >= 0, NULL);
g_return_val_if_fail (end_offset >= 0, NULL);
if (tag_name) {
GtkTextTagTable *tag_table;
tag_table = gtk_text_buffer_get_tag_table (get_buffer (text));
g_return_val_if_fail (
gtk_text_tag_table_lookup (
tag_table, tag_name) == NULL, NULL);
}
tag = gtk_text_buffer_create_tag (
get_buffer (text), tag_name, NULL);
va_start (var_args, first_property_name);
g_object_set_valist (G_OBJECT (tag), first_property_name, var_args);
va_end (var_args);
gtk_text_buffer_get_iter_at_offset (
get_buffer (text), &start, start_offset);
gtk_text_buffer_get_iter_at_offset (
get_buffer (text), &end, end_offset);
gtk_text_buffer_apply_tag (get_buffer (text), tag, &start, &end);
return tag;
} /* gnome_canvas_rich_text_add_tag */
#endif