diff options
Diffstat (limited to 'widgets/text')
-rw-r--r-- | widgets/text/Makefile.am | 17 | ||||
-rw-r--r-- | widgets/text/e-reflow-model.c | 350 | ||||
-rw-r--r-- | widgets/text/e-reflow-model.h | 108 | ||||
-rw-r--r-- | widgets/text/e-reflow.c | 1534 | ||||
-rw-r--r-- | widgets/text/e-reflow.h | 141 | ||||
-rw-r--r-- | widgets/text/e-text.c | 4 | ||||
-rw-r--r-- | widgets/text/gal-a11y-e-text-factory.c | 101 | ||||
-rw-r--r-- | widgets/text/gal-a11y-e-text-factory.h | 50 | ||||
-rw-r--r-- | widgets/text/gal-a11y-e-text.c | 1134 | ||||
-rw-r--r-- | widgets/text/gal-a11y-e-text.h | 57 |
10 files changed, 3489 insertions, 7 deletions
diff --git a/widgets/text/Makefile.am b/widgets/text/Makefile.am index ad106158d9..ee426f4791 100644 --- a/widgets/text/Makefile.am +++ b/widgets/text/Makefile.am @@ -15,22 +15,29 @@ privsolib_LTLIBRARIES = libetext.la libetext_la_SOURCES = \ e-text-model-repos.c \ e-text-model.c \ - e-text.c + e-text.c \ + e-reflow.c \ + e-reflow-model.c \ + gal-a11y-e-text-factory.c \ + gal-a11y-e-text.c libetextincludedir = $(privincludedir)/text libetextinclude_HEADERS = \ e-text-model-repos.h \ e-text-model.h \ - e-text.h + e-text.h \ + e-reflow.h \ + e-reflow-model.h \ + gal-a11y-e-text-factory.h \ + gal-a11y-e-text.h libetext_la_LDFLAGS = $(NO_UNDEFINED) libetext_la_LIBADD = \ - $(WIN32_BOOTSTRAP_LIBS) \ - $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/a11y/libevolution-a11y.la \ - $(top_builddir)/widgets/table/libetable.la \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(E_UTIL_LIBS) \ $(GNOME_PLATFORM_LIBS) \ $(REGEX_LIBS) diff --git a/widgets/text/e-reflow-model.c b/widgets/text/e-reflow-model.c new file mode 100644 index 0000000000..2bf5e4990d --- /dev/null +++ b/widgets/text/e-reflow-model.c @@ -0,0 +1,350 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Chris Lahey <clahey@ximian.com> + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ +#include <config.h> + +#include "e-util/e-util.h" + +#include "e-reflow-model.h" + +G_DEFINE_TYPE (EReflowModel, e_reflow_model, G_TYPE_OBJECT) + +#define d(x) + +d(static gint depth = 0;) + + +enum { + MODEL_CHANGED, + COMPARISON_CHANGED, + MODEL_ITEMS_INSERTED, + MODEL_ITEM_CHANGED, + MODEL_ITEM_REMOVED, + LAST_SIGNAL +}; + +static guint e_reflow_model_signals [LAST_SIGNAL] = { 0, }; + +/** + * e_reflow_model_set_width: + * @e_reflow_model: The e-reflow-model to operate on + * @width: The new value for the width of each item. + */ +void +e_reflow_model_set_width (EReflowModel *e_reflow_model, gint width) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + E_REFLOW_MODEL_GET_CLASS (e_reflow_model)->set_width (e_reflow_model, width); +} + +/** + * e_reflow_model_count: + * @e_reflow_model: The e-reflow-model to operate on + * + * Returns: the number of items in the reflow model. + */ +gint +e_reflow_model_count (EReflowModel *e_reflow_model) +{ + g_return_val_if_fail (e_reflow_model != NULL, 0); + g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), 0); + + return E_REFLOW_MODEL_GET_CLASS (e_reflow_model)->count (e_reflow_model); +} + +/** + * e_reflow_model_height: + * @e_reflow_model: The e-reflow-model to operate on + * @n: The item number to get the height of. + * @parent: The parent GnomeCanvasItem. + * + * Returns: the height of the nth item. + */ +gint +e_reflow_model_height (EReflowModel *e_reflow_model, gint n, GnomeCanvasGroup *parent) +{ + g_return_val_if_fail (e_reflow_model != NULL, 0); + g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), 0); + + return E_REFLOW_MODEL_GET_CLASS (e_reflow_model)->height (e_reflow_model, n, parent); +} + +/** + * e_reflow_model_incarnate: + * @e_reflow_model: The e-reflow-model to operate on + * @n: The item to create. + * @parent: The parent GnomeCanvasItem to create a child of. + * + * Create a GnomeCanvasItem to represent the nth piece of data. + * + * Returns: the new GnomeCanvasItem. + */ +GnomeCanvasItem * +e_reflow_model_incarnate (EReflowModel *e_reflow_model, gint n, GnomeCanvasGroup *parent) +{ + g_return_val_if_fail (e_reflow_model != NULL, NULL); + g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), NULL); + + return E_REFLOW_MODEL_GET_CLASS (e_reflow_model)->incarnate (e_reflow_model, n, parent); +} + +/** + * e_reflow_model_compare: + * @e_reflow_model: The e-reflow-model to operate on + * @n1: The first item to compare + * @n2: The second item to compare + * + * Compares item n1 and item n2 to see which should come first. + * + * Returns: strcmp like semantics for the comparison value. + */ +gint +e_reflow_model_compare (EReflowModel *e_reflow_model, gint n1, gint n2) +{ +#if 0 + g_return_val_if_fail (e_reflow_model != NULL, 0); + g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), 0); +#endif + + return E_REFLOW_MODEL_GET_CLASS (e_reflow_model)->compare (e_reflow_model, n1, n2); +} + +/** + * e_reflow_model_reincarnate: + * @e_reflow_model: The e-reflow-model to operate on + * @n: The item to create. + * @item: The item to reuse. + * + * Update item to represent the nth piece of data. + */ +void +e_reflow_model_reincarnate (EReflowModel *e_reflow_model, gint n, GnomeCanvasItem *item) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + E_REFLOW_MODEL_GET_CLASS (e_reflow_model)->reincarnate (e_reflow_model, n, item); +} + +static void +e_reflow_model_class_init (EReflowModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + e_reflow_model_signals [MODEL_CHANGED] = + g_signal_new ("model_changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowModelClass, model_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + e_reflow_model_signals [COMPARISON_CHANGED] = + g_signal_new ("comparison_changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowModelClass, comparison_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + e_reflow_model_signals [MODEL_ITEMS_INSERTED] = + g_signal_new ("model_items_inserted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowModelClass, model_items_inserted), + NULL, NULL, + e_marshal_NONE__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + + e_reflow_model_signals [MODEL_ITEM_CHANGED] = + g_signal_new ("model_item_changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowModelClass, model_item_changed), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + + e_reflow_model_signals [MODEL_ITEM_REMOVED] = + g_signal_new ("model_item_removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowModelClass, model_item_removed), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + + klass->set_width = NULL; + klass->count = NULL; + klass->height = NULL; + klass->incarnate = NULL; + klass->reincarnate = NULL; + + klass->model_changed = NULL; + klass->comparison_changed = NULL; + klass->model_items_inserted = NULL; + klass->model_item_removed = NULL; + klass->model_item_changed = NULL; +} + +static void +e_reflow_model_init (EReflowModel *e_reflow_model) +{ +} + +#if d(!)0 +static void +print_tabs (void) +{ + gint i; + for (i = 0; i < depth; i++) + g_print("\t"); +} +#endif + +/** + * e_reflow_model_changed: + * @e_reflow_model: the reflow model to notify of the change + * + * Use this function to notify any views of this reflow model that + * the contents of the reflow model have changed. This will emit + * the signal "model_changed" on the @e_reflow_model object. + * + * It is preferable to use the e_reflow_model_item_changed() signal to + * notify of smaller changes than to invalidate the entire model, as + * the views might have ways of caching the information they render + * from the model. + */ +void +e_reflow_model_changed (EReflowModel *e_reflow_model) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + d(print_tabs()); + d(g_print("Emitting model_changed on model 0x%p.\n", e_reflow_model)); + d(depth++); + g_signal_emit (e_reflow_model, + e_reflow_model_signals [MODEL_CHANGED], 0); + d(depth--); +} + +/** + * e_reflow_model_comparison_changed: + * @e_reflow_model: the reflow model to notify of the change + * + * Use this function to notify any views of this reflow model that the + * sorting has changed. The actual contents of the items hasn't, so + * there's no need to re-query the model for the heights of the + * individual items. + */ +void +e_reflow_model_comparison_changed (EReflowModel *e_reflow_model) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + d(print_tabs()); + d(g_print("Emitting comparison_changed on model 0x%p.\n", e_reflow_model)); + d(depth++); + g_signal_emit (e_reflow_model, + e_reflow_model_signals [COMPARISON_CHANGED], 0); + d(depth--); +} + +/** + * e_reflow_model_items_inserted: + * @e_reflow_model: The model changed. + * @position: The position the items were insert in. + * @count: The number of items inserted. + * + * Use this function to notify any views of the reflow model that a number of items have been inserted. + **/ +void +e_reflow_model_items_inserted (EReflowModel *e_reflow_model, gint position, gint count) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + d(print_tabs()); + d(g_print("Emitting items_inserted on model 0x%p, position=%d, count=%d.\n", e_reflow_model, position, count)); + d(depth++); + g_signal_emit (e_reflow_model, + e_reflow_model_signals [MODEL_ITEMS_INSERTED], 0, + position, count); + d(depth--); +} + +/** + * e_reflow_model_item_removed: + * @e_reflow_model: The model changed. + * @n: The position from which the items were removed. + * + * Use this function to notify any views of the reflow model that an + * item has been removed. + **/ +void +e_reflow_model_item_removed (EReflowModel *e_reflow_model, + gint n) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + d(print_tabs()); + d(g_print("Emitting item_removed on model 0x%p, n=%d.\n", e_reflow_model, n)); + d(depth++); + g_signal_emit (e_reflow_model, + e_reflow_model_signals [MODEL_ITEM_REMOVED], 0, + n); + d(depth--); +} + + +/** + * e_reflow_model_item_changed: + * @e_reflow_model: the reflow model to notify of the change + * @item: the item that was changed in the model. + * + * Use this function to notify any views of the reflow model that the + * contents of item @item have changed in model such that the height + * has changed or the item needs to be reincarnated. This function + * will emit the "model_item_changed" signal on the @e_reflow_model + * object + */ +void +e_reflow_model_item_changed (EReflowModel *e_reflow_model, gint n) +{ + g_return_if_fail (e_reflow_model != NULL); + g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model)); + + d(print_tabs()); + d(g_print("Emitting item_changed on model 0x%p, n=%d.\n", e_reflow_model, n)); + d(depth++); + g_signal_emit (e_reflow_model, + e_reflow_model_signals [MODEL_ITEM_CHANGED], 0, + n); + d(depth--); +} diff --git a/widgets/text/e-reflow-model.h b/widgets/text/e-reflow-model.h new file mode 100644 index 0000000000..ebbf3c1f75 --- /dev/null +++ b/widgets/text/e-reflow-model.h @@ -0,0 +1,108 @@ +/* + * + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef _E_REFLOW_MODEL_H_ +#define _E_REFLOW_MODEL_H_ + +#include <glib-object.h> +#include <libgnomecanvas/gnome-canvas.h> + +G_BEGIN_DECLS + +#define E_REFLOW_MODEL_TYPE (e_reflow_model_get_type ()) +#define E_REFLOW_MODEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), E_REFLOW_MODEL_TYPE, EReflowModel)) +#define E_REFLOW_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), E_REFLOW_MODEL_TYPE, EReflowModelClass)) +#define E_IS_REFLOW_MODEL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_REFLOW_MODEL_TYPE)) +#define E_IS_REFLOW_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_REFLOW_MODEL_TYPE)) +#define E_REFLOW_MODEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_REFLOW_MODEL_TYPE, EReflowModelClass)) + +typedef struct { + GObject base; +} EReflowModel; + +typedef struct { + GObjectClass parent_class; + + /* + * Virtual methods + */ + void (*set_width) (EReflowModel *etm, gint width); + + gint (*count) (EReflowModel *etm); + gint (*height) (EReflowModel *etm, gint n, GnomeCanvasGroup *parent); + GnomeCanvasItem *(*incarnate) (EReflowModel *etm, gint n, GnomeCanvasGroup *parent); + gint (*compare) (EReflowModel *etm, gint n1, gint n2); + void (*reincarnate) (EReflowModel *etm, gint n, GnomeCanvasItem *item); + + /* + * Signals + */ + + /* + * These all come after the change has been made. + * Major structural changes: model_changed + * Changes to the sorting of elements: comparison_changed + * Changes only in an item: item_changed + */ + void (*model_changed) (EReflowModel *etm); + void (*comparison_changed) (EReflowModel *etm); + void (*model_items_inserted) (EReflowModel *etm, gint position, gint count); + void (*model_item_removed) (EReflowModel *etm, gint position); + void (*model_item_changed) (EReflowModel *etm, gint n); +} EReflowModelClass; + +GType e_reflow_model_get_type (void); + +/**/ +void e_reflow_model_set_width (EReflowModel *e_reflow_model, + gint width); +gint e_reflow_model_count (EReflowModel *e_reflow_model); +gint e_reflow_model_height (EReflowModel *e_reflow_model, + gint n, + GnomeCanvasGroup *parent); +GnomeCanvasItem *e_reflow_model_incarnate (EReflowModel *e_reflow_model, + gint n, + GnomeCanvasGroup *parent); +gint e_reflow_model_compare (EReflowModel *e_reflow_model, + gint n1, + gint n2); +void e_reflow_model_reincarnate (EReflowModel *e_reflow_model, + gint n, + GnomeCanvasItem *item); + +/* + * Routines for emitting signals on the e_reflow + */ +void e_reflow_model_changed (EReflowModel *e_reflow_model); +void e_reflow_model_comparison_changed (EReflowModel *e_reflow_model); +void e_reflow_model_items_inserted (EReflowModel *e_reflow_model, + gint position, + gint count); +void e_reflow_model_item_removed (EReflowModel *e_reflow_model, + gint n); +void e_reflow_model_item_changed (EReflowModel *e_reflow_model, + gint n); + +G_END_DECLS + +#endif /* _E_REFLOW_MODEL_H_ */ diff --git a/widgets/text/e-reflow.c b/widgets/text/e-reflow.c new file mode 100644 index 0000000000..441102ac0f --- /dev/null +++ b/widgets/text/e-reflow.c @@ -0,0 +1,1534 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + */ +#include <config.h> + +#include <math.h> +#include <string.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include "text/e-text.h" +#include <glib/gi18n.h> +#include "e-util/e-util.h" +#include "e-util/e-unicode.h" + +#include "misc/e-canvas.h" +#include "misc/e-canvas-utils.h" +#include "e-reflow.h" +#include "misc/e-selection-model-simple.h" + +static gboolean e_reflow_event (GnomeCanvasItem *item, GdkEvent *event); +static void e_reflow_realize (GnomeCanvasItem *item); +static void e_reflow_unrealize (GnomeCanvasItem *item); +static void e_reflow_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + gint x, gint y, gint width, gint height); +static void e_reflow_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags); +static double e_reflow_point (GnomeCanvasItem *item, double x, double y, gint cx, gint cy, GnomeCanvasItem **actual_item); +static void e_reflow_reflow (GnomeCanvasItem *item, gint flags); +static void set_empty(EReflow *reflow); + +static void e_reflow_resize_children (GnomeCanvasItem *item); + +#define E_REFLOW_DIVIDER_WIDTH 2 +#define E_REFLOW_BORDER_WIDTH 7 +#define E_REFLOW_FULL_GUTTER (E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH * 2) + +G_DEFINE_TYPE (EReflow, e_reflow, GNOME_TYPE_CANVAS_GROUP) + +/* The arguments we take */ +enum { + PROP_0, + PROP_MINIMUM_WIDTH, + PROP_WIDTH, + PROP_HEIGHT, + PROP_EMPTY_MESSAGE, + PROP_MODEL, + PROP_COLUMN_WIDTH +}; + +enum { + SELECTION_EVENT, + COLUMN_WIDTH_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = {0, }; + +static gint +er_compare (gint i1, gint i2, gpointer user_data) +{ + EReflow *reflow = user_data; + return e_reflow_model_compare (reflow->model, i1, i2); +} + +static gint +e_reflow_pick_line (EReflow *reflow, double x) +{ + x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; + x /= reflow->column_width + E_REFLOW_FULL_GUTTER; + return x; +} + +static gint +er_find_item (EReflow *reflow, GnomeCanvasItem *item) +{ + gint i; + for (i = 0; i < reflow->count; i++) { + if (reflow->items[i] == item) + return i; + } + return -1; +} + +static void +e_reflow_resize_children (GnomeCanvasItem *item) +{ + EReflow *reflow; + gint i; + gint count; + + reflow = E_REFLOW (item); + + count = reflow->count; + for (i = 0; i < count; i++) { + if (reflow->items[i]) + gnome_canvas_item_set(reflow->items[i], + "width", (double) reflow->column_width, + NULL); + } +} + +static inline void +e_reflow_update_selection_row (EReflow *reflow, gint row) +{ + if (reflow->items[row]) { + g_object_set(reflow->items[row], + "selected", e_selection_model_is_row_selected(E_SELECTION_MODEL(reflow->selection), row), + NULL); + } else if (e_selection_model_is_row_selected (E_SELECTION_MODEL (reflow->selection), row)) { + reflow->items[row] = e_reflow_model_incarnate (reflow->model, row, GNOME_CANVAS_GROUP (reflow)); + g_object_set (reflow->items[row], + "selected", e_selection_model_is_row_selected(E_SELECTION_MODEL(reflow->selection), row), + "width", (double) reflow->column_width, + NULL); + } +} + +static void +e_reflow_update_selection (EReflow *reflow) +{ + gint i; + gint count; + + count = reflow->count; + for (i = 0; i < count; i++) { + e_reflow_update_selection_row (reflow, i); + } +} + +static void +selection_changed (ESelectionModel *selection, EReflow *reflow) +{ + e_reflow_update_selection (reflow); +} + +static void +selection_row_changed (ESelectionModel *selection, gint row, EReflow *reflow) +{ + e_reflow_update_selection_row (reflow, row); +} + +static gboolean +do_adjustment (gpointer user_data) +{ + gint row; + GtkAdjustment *adj; + gfloat value, min_value, max_value; + EReflow *reflow = user_data; + + row = reflow->cursor_row; + if (row == -1) + return FALSE; + + adj = gtk_layout_get_hadjustment (GTK_LAYOUT (GNOME_CANVAS_ITEM (reflow)->canvas)); + value = adj->value; + + if ((!reflow->items) || (!reflow->items[row])) + return TRUE; + min_value = reflow->items[row]->x2 - adj->page_size; + max_value = reflow->items[row]->x1; + + if (value < min_value) + value = min_value; + + if (value > max_value) + value = max_value; + + if (value != adj->value) { + adj->value = value; + gtk_adjustment_value_changed (adj); + } + + reflow->do_adjustment_idle_id = 0; + + return FALSE; +} + +static void +cursor_changed (ESelectionModel *selection, gint row, gint col, EReflow *reflow) +{ + gint count = reflow->count; + gint old_cursor = reflow->cursor_row; + + if (old_cursor < count && old_cursor >= 0) { + if (reflow->items[old_cursor]) { + g_object_set (reflow->items[old_cursor], + "has_cursor", FALSE, + NULL); + } + } + + reflow->cursor_row = row; + + if (row < count && row >= 0) { + if (reflow->items[row]) { + g_object_set (reflow->items[row], + "has_cursor", TRUE, + NULL); + } else { + reflow->items[row] = e_reflow_model_incarnate (reflow->model, row, GNOME_CANVAS_GROUP (reflow)); + g_object_set (reflow->items[row], + "has_cursor", TRUE, + "width", (double) reflow->column_width, + NULL); + } + } + + if (reflow->do_adjustment_idle_id == 0) + reflow->do_adjustment_idle_id = g_idle_add (do_adjustment, reflow); + +} + + +static void +incarnate (EReflow *reflow) +{ + gint column_width; + gint first_column; + gint last_column; + gint first_cell; + gint last_cell; + gint i; + GtkAdjustment *adjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (GNOME_CANVAS_ITEM (reflow)->canvas)); + + column_width = reflow->column_width; + + first_column = adjustment->value - 1 + E_REFLOW_BORDER_WIDTH; + first_column /= column_width + E_REFLOW_FULL_GUTTER; + + last_column = adjustment->value + adjustment->page_size + 1 - E_REFLOW_BORDER_WIDTH - E_REFLOW_DIVIDER_WIDTH; + last_column /= column_width + E_REFLOW_FULL_GUTTER; + last_column ++; + + if (first_column >= 0 && first_column < reflow->column_count) + first_cell = reflow->columns[first_column]; + else + first_cell = 0; + + if (last_column >= 0 && last_column < reflow->column_count) + last_cell = reflow->columns[last_column]; + else + last_cell = reflow->count; + + for (i = first_cell; i < last_cell; i++) { + gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); + if (reflow->items[unsorted] == NULL) { + if (reflow->model) { + reflow->items[unsorted] = e_reflow_model_incarnate (reflow->model, unsorted, GNOME_CANVAS_GROUP (reflow)); + g_object_set (reflow->items[unsorted], + "selected", e_selection_model_is_row_selected(E_SELECTION_MODEL(reflow->selection), unsorted), + "width", (double) reflow->column_width, + NULL); + } + } + } + reflow->incarnate_idle_id = 0; +} + +static gboolean +invoke_incarnate (gpointer user_data) +{ + EReflow *reflow = user_data; + incarnate (reflow); + return FALSE; +} + +static void +queue_incarnate (EReflow *reflow) +{ + if (reflow->incarnate_idle_id == 0) + reflow->incarnate_idle_id = + g_idle_add_full (25, invoke_incarnate, reflow, NULL); +} + +static void +reflow_columns (EReflow *reflow) +{ + GSList *list; + gint count; + gint start; + gint i; + gint column_count, column_start; + double running_height; + + if (reflow->reflow_from_column <= 1) { + start = 0; + column_count = 1; + column_start = 0; + } + else { + /* we start one column before the earliest new entry, + so we can handle the case where the new entry is + inserted at the start of the column */ + column_start = reflow->reflow_from_column - 1; + start = reflow->columns[column_start]; + column_count = column_start + 1; + } + + list = NULL; + + running_height = E_REFLOW_BORDER_WIDTH; + + count = reflow->count - start; + for (i = start; i < count; i++) { + gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); + if (i != 0 && running_height + reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH > reflow->height) { + list = g_slist_prepend (list, GINT_TO_POINTER(i)); + column_count ++; + running_height = E_REFLOW_BORDER_WIDTH * 2 + reflow->heights[unsorted]; + } else + running_height += reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH; + } + + reflow->column_count = column_count; + reflow->columns = g_renew (int, reflow->columns, column_count); + column_count --; + + for (; column_count > column_start; column_count--) { + GSList *to_free; + reflow->columns[column_count] = GPOINTER_TO_INT(list->data); + to_free = list; + list = list->next; + g_slist_free_1 (to_free); + } + reflow->columns[column_start] = start; + + queue_incarnate (reflow); + + reflow->need_reflow_columns = FALSE; + reflow->reflow_from_column = -1; +} + +static void +item_changed (EReflowModel *model, gint i, EReflow *reflow) +{ + if (i < 0 || i >= reflow->count) + return; + + reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow)); + if (reflow->items[i] != NULL) + e_reflow_model_reincarnate (model, i, reflow->items[i]); + e_sorter_array_clean (reflow->sorter); + reflow->reflow_from_column = -1; + reflow->need_reflow_columns = TRUE; + e_canvas_item_request_reflow(GNOME_CANVAS_ITEM (reflow)); +} + +static void +item_removed (EReflowModel *model, gint i, EReflow *reflow) +{ + gint c; + gint sorted; + + if (i < 0 || i >= reflow->count) + return; + + sorted = e_sorter_model_to_sorted (E_SORTER (reflow->sorter), i); + for (c = reflow->column_count - 1; c >= 0; c--) { + gint start_of_column = reflow->columns[c]; + + if (start_of_column <= sorted) { + if (reflow->reflow_from_column == -1 + || reflow->reflow_from_column > c) { + reflow->reflow_from_column = c; + } + break; + } + } + + if (reflow->items[i]) + gtk_object_destroy (GTK_OBJECT (reflow->items[i])); + + memmove (reflow->heights + i, reflow->heights + i + 1, (reflow->count - i - 1) * sizeof (gint)); + memmove (reflow->items + i, reflow->items + i + 1, (reflow->count - i - 1) * sizeof (GnomeCanvasItem *)); + + reflow->count --; + + reflow->heights [reflow->count] = 0; + reflow->items [reflow->count] = NULL; + + reflow->need_reflow_columns = TRUE; + set_empty (reflow); + e_canvas_item_request_reflow(GNOME_CANVAS_ITEM (reflow)); + + e_sorter_array_set_count (reflow->sorter, reflow->count); + + e_selection_model_simple_delete_rows (E_SELECTION_MODEL_SIMPLE (reflow->selection), i, 1); +} + +static void +items_inserted (EReflowModel *model, gint position, gint count, EReflow *reflow) +{ + gint i, oldcount; + + if (position < 0 || position > reflow->count) + return; + + oldcount = reflow->count; + + reflow->count += count; + + if (reflow->count > reflow->allocated_count) { + while (reflow->count > reflow->allocated_count) + reflow->allocated_count += 256; + reflow->heights = g_renew (int, reflow->heights, reflow->allocated_count); + reflow->items = g_renew (GnomeCanvasItem *, reflow->items, reflow->allocated_count); + } + memmove (reflow->heights + position + count, reflow->heights + position, (reflow->count - position - count) * sizeof (gint)); + memmove (reflow->items + position + count, reflow->items + position, (reflow->count - position - count) * sizeof (GnomeCanvasItem *)); + for (i = position; i < position + count; i++) { + reflow->items[i] = NULL; + reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow)); + } + + e_selection_model_simple_set_row_count (E_SELECTION_MODEL_SIMPLE (reflow->selection), reflow->count); + if (position == oldcount) + e_sorter_array_append (reflow->sorter, count); + else + e_sorter_array_set_count (reflow->sorter, reflow->count); + + for (i = position; i < position + count; i ++) { + gint sorted = e_sorter_model_to_sorted (E_SORTER (reflow->sorter), i); + gint c; + + for (c = reflow->column_count - 1; c >= 0; c--) { + gint start_of_column = reflow->columns[c]; + + if (start_of_column <= sorted) { + if (reflow->reflow_from_column == -1 + || reflow->reflow_from_column > c) { + reflow->reflow_from_column = c; + } + break; + } + } + } + + reflow->need_reflow_columns = TRUE; + set_empty (reflow); + e_canvas_item_request_reflow(GNOME_CANVAS_ITEM (reflow)); +} + +static void +model_changed (EReflowModel *model, EReflow *reflow) +{ + gint i; + gint count; + gint oldcount; + + count = reflow->count; + oldcount = count; + + for (i = 0; i < count; i++) { + if (reflow->items[i]) + gtk_object_destroy (GTK_OBJECT (reflow->items[i])); + } + g_free (reflow->items); + g_free (reflow->heights); + reflow->count = e_reflow_model_count (model); + reflow->allocated_count = reflow->count; + reflow->items = g_new (GnomeCanvasItem *, reflow->count); + reflow->heights = g_new (int, reflow->count); + + count = reflow->count; + for (i = 0; i < count; i++) { + reflow->items[i] = NULL; + reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow)); + } + + e_selection_model_simple_set_row_count (E_SELECTION_MODEL_SIMPLE (reflow->selection), count); + e_sorter_array_set_count (reflow->sorter, reflow->count); + + reflow->need_reflow_columns = TRUE; + if (oldcount > reflow->count) + reflow_columns (reflow); + set_empty (reflow); + e_canvas_item_request_reflow(GNOME_CANVAS_ITEM (reflow)); +} + +static void +comparison_changed (EReflowModel *model, EReflow *reflow) +{ + e_sorter_array_clean (reflow->sorter); + reflow->reflow_from_column = -1; + reflow->need_reflow_columns = TRUE; + e_canvas_item_request_reflow(GNOME_CANVAS_ITEM (reflow)); +} + +static void +set_empty(EReflow *reflow) +{ + if (reflow->count == 0) { + if (reflow->empty_text) { + if (reflow->empty_message) { + gnome_canvas_item_set(reflow->empty_text, + "width", reflow->minimum_width, + "text", reflow->empty_message, + NULL); + e_canvas_item_move_absolute(reflow->empty_text, + reflow->minimum_width / 2, + 0); + } else { + gtk_object_destroy(GTK_OBJECT(reflow->empty_text)); + reflow->empty_text = NULL; + } + } else { + if (reflow->empty_message) { + reflow->empty_text = + gnome_canvas_item_new(GNOME_CANVAS_GROUP(reflow), + e_text_get_type(), + "anchor", GTK_ANCHOR_N, + "width", reflow->minimum_width, + "clip", TRUE, + "use_ellipsis", TRUE, + "justification", GTK_JUSTIFY_CENTER, + "text", reflow->empty_message, + "draw_background", FALSE, + NULL); + e_canvas_item_move_absolute(reflow->empty_text, + reflow->minimum_width / 2, + 0); + } + } + } else { + if (reflow->empty_text) { + gtk_object_destroy(GTK_OBJECT(reflow->empty_text)); + reflow->empty_text = NULL; + } + } +} + +static void +disconnect_model (EReflow *reflow) +{ + if (reflow->model == NULL) + return; + + g_signal_handler_disconnect (reflow->model, + reflow->model_changed_id); + g_signal_handler_disconnect (reflow->model, + reflow->comparison_changed_id); + g_signal_handler_disconnect (reflow->model, + reflow->model_items_inserted_id); + g_signal_handler_disconnect (reflow->model, + reflow->model_item_removed_id); + g_signal_handler_disconnect (reflow->model, + reflow->model_item_changed_id); + g_object_unref (reflow->model); + + reflow->model_changed_id = 0; + reflow->comparison_changed_id = 0; + reflow->model_items_inserted_id = 0; + reflow->model_item_removed_id = 0; + reflow->model_item_changed_id = 0; + reflow->model = NULL; +} + +static void +disconnect_selection (EReflow *reflow) +{ + if (reflow->selection == NULL) + return; + + g_signal_handler_disconnect (reflow->selection, + reflow->selection_changed_id); + g_signal_handler_disconnect (reflow->selection, + reflow->selection_row_changed_id); + g_signal_handler_disconnect (reflow->selection, + reflow->cursor_changed_id); + g_object_unref (reflow->selection); + + reflow->selection_changed_id = 0; + reflow->selection_row_changed_id = 0; + reflow->cursor_changed_id = 0; + reflow->selection = NULL; +} + +static void +connect_model (EReflow *reflow, EReflowModel *model) +{ + if (reflow->model != NULL) + disconnect_model (reflow); + + if (model == NULL) + return; + + reflow->model = model; + g_object_ref (reflow->model); + reflow->model_changed_id = + g_signal_connect (reflow->model, "model_changed", + G_CALLBACK (model_changed), reflow); + reflow->comparison_changed_id = + g_signal_connect (reflow->model, "comparison_changed", + G_CALLBACK (comparison_changed), reflow); + reflow->model_items_inserted_id = + g_signal_connect (reflow->model, "model_items_inserted", + G_CALLBACK (items_inserted), reflow); + reflow->model_item_removed_id = + g_signal_connect (reflow->model, "model_item_removed", + G_CALLBACK (item_removed), reflow); + reflow->model_item_changed_id = + g_signal_connect (reflow->model, "model_item_changed", + G_CALLBACK (item_changed), reflow); + model_changed (model, reflow); +} + +static void +adjustment_changed (GtkAdjustment *adjustment, EReflow *reflow) +{ + queue_incarnate (reflow); +} + +static void +disconnect_adjustment (EReflow *reflow) +{ + if (reflow->adjustment == NULL) + return; + + g_signal_handler_disconnect (reflow->adjustment, + reflow->adjustment_changed_id); + g_signal_handler_disconnect (reflow->adjustment, + reflow->adjustment_value_changed_id); + + g_object_unref (reflow->adjustment); + + reflow->adjustment_changed_id = 0; + reflow->adjustment_value_changed_id = 0; + reflow->adjustment = NULL; +} + +static void +connect_adjustment (EReflow *reflow, GtkAdjustment *adjustment) +{ + if (reflow->adjustment != NULL) + disconnect_adjustment (reflow); + + if (adjustment == NULL) + return; + + reflow->adjustment = adjustment; + reflow->adjustment_changed_id = + g_signal_connect (adjustment, "changed", + G_CALLBACK (adjustment_changed), reflow); + reflow->adjustment_value_changed_id = + g_signal_connect (adjustment, "value_changed", + G_CALLBACK (adjustment_changed), reflow); + g_object_ref (adjustment); +} + +#if 0 +static void +set_scroll_adjustments (GtkLayout *layout, GtkAdjustment *hadj, GtkAdjustment *vadj, EReflow *reflow) +{ + connect_adjustment (reflow, hadj); +} + +static void +connect_set_adjustment (EReflow *reflow) +{ + reflow->set_scroll_adjustments_id = + g_signal_connect (GNOME_CANVAS_ITEM (reflow)->canvas, + "set_scroll_adjustments", + G_CALLBACK (set_scroll_adjustments), reflow); +} +#endif + +static void +disconnect_set_adjustment (EReflow *reflow) +{ + if (reflow->set_scroll_adjustments_id != 0) { + g_signal_handler_disconnect (GNOME_CANVAS_ITEM (reflow)->canvas, + reflow->set_scroll_adjustments_id); + reflow->set_scroll_adjustments_id = 0; + } +} + +static void +column_width_changed (EReflow *reflow) +{ + g_signal_emit (reflow, signals[COLUMN_WIDTH_CHANGED], 0, reflow->column_width); +} + + + + +/* Virtual functions */ +static void +e_reflow_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GnomeCanvasItem *item; + EReflow *reflow; + + item = GNOME_CANVAS_ITEM (object); + reflow = E_REFLOW (object); + + switch (prop_id){ + case PROP_HEIGHT: + reflow->height = g_value_get_double (value); + reflow->need_reflow_columns = TRUE; + e_canvas_item_request_reflow(item); + break; + case PROP_MINIMUM_WIDTH: + reflow->minimum_width = g_value_get_double (value); + if (GNOME_CANVAS_ITEM_REALIZED & GTK_OBJECT_FLAGS(object)) + set_empty(reflow); + e_canvas_item_request_reflow(item); + break; + case PROP_EMPTY_MESSAGE: + g_free(reflow->empty_message); + reflow->empty_message = g_strdup(g_value_get_string (value)); + if (GNOME_CANVAS_ITEM_REALIZED & GTK_OBJECT_FLAGS(object)) + set_empty(reflow); + break; + case PROP_MODEL: + connect_model (reflow, (EReflowModel *) g_value_get_object (value)); + break; + case PROP_COLUMN_WIDTH: + if (reflow->column_width != g_value_get_double (value)) { + GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas)); + double old_width = reflow->column_width; + + reflow->column_width = g_value_get_double (value); + adjustment->step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2; + adjustment->page_increment = adjustment->page_size - adjustment->step_increment; + gtk_adjustment_changed(adjustment); + e_reflow_resize_children(item); + e_canvas_item_request_reflow(item); + + reflow->need_column_resize = TRUE; + gnome_canvas_item_request_update(item); + + if (old_width != reflow->column_width) + column_width_changed (reflow); + } + break; + } +} + +static void +e_reflow_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + EReflow *reflow; + + reflow = E_REFLOW (object); + + switch (prop_id) { + case PROP_MINIMUM_WIDTH: + g_value_set_double (value, reflow->minimum_width); + break; + case PROP_WIDTH: + g_value_set_double (value, reflow->width); + break; + case PROP_HEIGHT: + g_value_set_double (value, reflow->height); + break; + case PROP_EMPTY_MESSAGE: + g_value_set_string (value, reflow->empty_message); + break; + case PROP_MODEL: + g_value_set_object (value, reflow->model); + break; + case PROP_COLUMN_WIDTH: + g_value_set_double (value, reflow->column_width); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +e_reflow_dispose (GObject *object) +{ + EReflow *reflow = E_REFLOW(object); + + g_free (reflow->items); + g_free (reflow->heights); + g_free (reflow->columns); + + reflow->items = NULL; + reflow->heights = NULL; + reflow->columns = NULL; + reflow->count = 0; + reflow->allocated_count = 0; + + if (reflow->incarnate_idle_id) + g_source_remove (reflow->incarnate_idle_id); + reflow->incarnate_idle_id = 0; + + if (reflow->do_adjustment_idle_id) + g_source_remove (reflow->do_adjustment_idle_id); + reflow->do_adjustment_idle_id = 0; + + disconnect_model (reflow); + disconnect_selection (reflow); + + g_free(reflow->empty_message); + reflow->empty_message = NULL; + + if (reflow->sorter) { + g_object_unref (reflow->sorter); + reflow->sorter = NULL; + } + + G_OBJECT_CLASS(e_reflow_parent_class)->dispose (object); +} + +static void +e_reflow_realize (GnomeCanvasItem *item) +{ + EReflow *reflow; + GtkAdjustment *adjustment; + gint count; + gint i; + + reflow = E_REFLOW (item); + + if (GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->realize) + (* GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->realize) (item); + + reflow->arrow_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + reflow->default_cursor = gdk_cursor_new (GDK_LEFT_PTR); + + count = reflow->count; + for(i = 0; i < count; i++) { + if (reflow->items[i]) + gnome_canvas_item_set(reflow->items[i], + "width", reflow->column_width, + NULL); + } + + set_empty(reflow); + + reflow->need_reflow_columns = TRUE; + e_canvas_item_request_reflow(item); + + adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas)); + +#if 0 + connect_set_adjustment (reflow); +#endif + connect_adjustment (reflow, adjustment); + + adjustment->step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2; + adjustment->page_increment = adjustment->page_size - adjustment->step_increment; + gtk_adjustment_changed(adjustment); + + if (!item->canvas->aa) { + } +} + +static void +e_reflow_unrealize (GnomeCanvasItem *item) +{ + EReflow *reflow; + + reflow = E_REFLOW (item); + + if (!item->canvas->aa) { + } + + gdk_cursor_unref (reflow->arrow_cursor); + gdk_cursor_unref (reflow->default_cursor); + reflow->arrow_cursor = NULL; + reflow->default_cursor = NULL; + + g_free (reflow->columns); + reflow->columns = NULL; + + disconnect_set_adjustment (reflow); + disconnect_adjustment (reflow); + + if (GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->unrealize) + (* GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->unrealize) (item); +} + +static gboolean +e_reflow_event (GnomeCanvasItem *item, GdkEvent *event) +{ + EReflow *reflow; + gint return_val = FALSE; + + reflow = E_REFLOW (item); + + switch( event->type ) + { + case GDK_KEY_PRESS: + return_val = e_selection_model_key_press(reflow->selection, (GdkEventKey *) event); + break; +#if 0 + if (event->key.keyval == GDK_Tab || + event->key.keyval == GDK_KP_Tab || + event->key.keyval == GDK_ISO_Left_Tab) { + gint i; + gint count; + count = reflow->count; + for (i = 0; i < count; i++) { + gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); + GnomeCanvasItem *item = reflow->items[unsorted]; + EFocus has_focus; + if (item) { + g_object_get(item, + "has_focus", &has_focus, + NULL); + if (has_focus) { + if (event->key.state & GDK_SHIFT_MASK) { + if (i == 0) + return FALSE; + i--; + } else { + if (i == count - 1) + return FALSE; + i++; + } + + unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); + if (reflow->items[unsorted] == NULL) { + reflow->items[unsorted] = e_reflow_model_incarnate (reflow->model, unsorted, GNOME_CANVAS_GROUP (reflow)); + } + + item = reflow->items[unsorted]; + gnome_canvas_item_set(item, + "has_focus", (event->key.state & GDK_SHIFT_MASK) ? E_FOCUS_END : E_FOCUS_START, + NULL); + return TRUE; + } + } + } + } +#endif + case GDK_BUTTON_PRESS: + switch(event->button.button) + { + case 1: + { + GdkEventButton *button = (GdkEventButton *) event; + double n_x; + n_x = button->x; + n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; + n_x = fmod(n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); + + if ( button->y >= E_REFLOW_BORDER_WIDTH && button->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER ) { + /* don't allow to drag the first line*/ + if (e_reflow_pick_line(reflow, button->x) == 0) + return TRUE; + reflow->which_column_dragged = e_reflow_pick_line(reflow, button->x); + reflow->start_x = reflow->which_column_dragged * (reflow->column_width + E_REFLOW_FULL_GUTTER) - E_REFLOW_DIVIDER_WIDTH / 2; + reflow->temp_column_width = reflow->column_width; + reflow->column_drag = TRUE; + + gnome_canvas_item_grab (item, + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, + reflow->arrow_cursor, + button->time); + + reflow->previous_temp_column_width = -1; + reflow->need_column_resize = TRUE; + gnome_canvas_item_request_update(item); + return TRUE; + } + } + break; + case 4: + { + GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas)); + gdouble new_value = adjustment->value; + new_value -= adjustment->step_increment; + gtk_adjustment_set_value(adjustment, new_value); + } + break; + case 5: + { + GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas)); + gdouble new_value = adjustment->value; + new_value += adjustment->step_increment; + if ( new_value > adjustment->upper - adjustment->page_size ) + new_value = adjustment->upper - adjustment->page_size; + gtk_adjustment_set_value(adjustment, new_value); + } + break; + } + break; + case GDK_BUTTON_RELEASE: + if (reflow->column_drag) { + gdouble old_width = reflow->column_width; + GdkEventButton *button = (GdkEventButton *) event; + GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas)); + reflow->temp_column_width = reflow->column_width + + (button->x - reflow->start_x)/(reflow->which_column_dragged - e_reflow_pick_line(reflow, adjustment->value)); + if ( reflow->temp_column_width < 50 ) + reflow->temp_column_width = 50; + reflow->column_drag = FALSE; + if ( old_width != reflow->temp_column_width ) { + gtk_adjustment_set_value(adjustment, adjustment->value + e_reflow_pick_line(reflow, adjustment->value) * (reflow->temp_column_width - reflow->column_width)); + reflow->column_width = reflow->temp_column_width; + adjustment->step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2; + adjustment->page_increment = adjustment->page_size - adjustment->step_increment; + gtk_adjustment_changed(adjustment); + e_reflow_resize_children(item); + e_canvas_item_request_reflow(item); + gnome_canvas_request_redraw(item->canvas, 0, 0, reflow->width, reflow->height); + column_width_changed (reflow); + } + reflow->need_column_resize = TRUE; + gnome_canvas_item_request_update(item); + gnome_canvas_item_ungrab (item, button->time); + return TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if (reflow->column_drag) { + double old_width = reflow->temp_column_width; + GdkEventMotion *motion = (GdkEventMotion *) event; + GtkAdjustment *adjustment = gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas)); + reflow->temp_column_width = reflow->column_width + + (motion->x - reflow->start_x)/(reflow->which_column_dragged - e_reflow_pick_line(reflow, adjustment->value)); + if (reflow->temp_column_width < 50) + reflow->temp_column_width = 50; + if (old_width != reflow->temp_column_width) { + reflow->need_column_resize = TRUE; + gnome_canvas_item_request_update(item); + } + return TRUE; + } else { + GdkEventMotion *motion = (GdkEventMotion *) event; + double n_x; + + n_x = motion->x; + n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; + n_x = fmod(n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); + + if ( motion->y >= E_REFLOW_BORDER_WIDTH && motion->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER ) { + if ( reflow->default_cursor_shown ) { + gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, reflow->arrow_cursor); + reflow->default_cursor_shown = FALSE; + } + } else + if ( ! reflow->default_cursor_shown ) { + gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, reflow->default_cursor); + reflow->default_cursor_shown = TRUE; + } + + } + break; + case GDK_ENTER_NOTIFY: + if (!reflow->column_drag) { + GdkEventCrossing *crossing = (GdkEventCrossing *) event; + double n_x; + n_x = crossing->x; + n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; + n_x = fmod(n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); + + if ( crossing->y >= E_REFLOW_BORDER_WIDTH && crossing->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER ) { + if ( reflow->default_cursor_shown ) { + gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, reflow->arrow_cursor); + reflow->default_cursor_shown = FALSE; + } + } + } + break; + case GDK_LEAVE_NOTIFY: + if (!reflow->column_drag) { + GdkEventCrossing *crossing = (GdkEventCrossing *) event; + double n_x; + n_x = crossing->x; + n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; + n_x = fmod(n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); + if ( !( crossing->y >= E_REFLOW_BORDER_WIDTH && crossing->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER ) ) { + if ( ! reflow->default_cursor_shown ) { + gdk_window_set_cursor(GTK_WIDGET(item->canvas)->window, reflow->default_cursor); + reflow->default_cursor_shown = TRUE; + } + } + } + break; + default: + break; + } + if (return_val) + return return_val; + else if (GNOME_CANVAS_ITEM_CLASS( e_reflow_parent_class )->event) + return (* GNOME_CANVAS_ITEM_CLASS( e_reflow_parent_class )->event) (item, event); + else + return FALSE; +} + +static void e_reflow_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + gint x, gint y, gint width, gint height) +{ + gint x_rect, y_rect, width_rect, height_rect; + gdouble running_width; + EReflow *reflow = E_REFLOW(item); + gint i; + double column_width; + + if (GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->draw) + GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->draw (item, drawable, x, y, width, height); + column_width = reflow->column_width; + running_width = E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + x_rect = running_width; + y_rect = E_REFLOW_BORDER_WIDTH; + width_rect = E_REFLOW_DIVIDER_WIDTH; + height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); + + /* Compute first column to draw. */ + i = x; + i /= column_width + E_REFLOW_FULL_GUTTER; + running_width += i * (column_width + E_REFLOW_FULL_GUTTER); + + for (; i < reflow->column_count; i++) { + if ( running_width > x + width ) + break; + x_rect = running_width; + gtk_paint_flat_box(GTK_WIDGET(item->canvas)->style, + drawable, + GTK_STATE_ACTIVE, + GTK_SHADOW_NONE, + NULL, + GTK_WIDGET(item->canvas), + "reflow", + x_rect - x, + y_rect - y, + width_rect, + height_rect); + running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + } + if (reflow->column_drag) { + gint start_line = e_reflow_pick_line(reflow, + gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas))->value); + i = x - start_line * (column_width + E_REFLOW_FULL_GUTTER); + running_width = start_line * (column_width + E_REFLOW_FULL_GUTTER); + column_width = reflow->temp_column_width; + running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER); + i += start_line * (column_width + E_REFLOW_FULL_GUTTER); + running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + x_rect = running_width; + y_rect = E_REFLOW_BORDER_WIDTH; + width_rect = E_REFLOW_DIVIDER_WIDTH; + height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); + + /* Compute first column to draw. */ + i /= column_width + E_REFLOW_FULL_GUTTER; + running_width += i * (column_width + E_REFLOW_FULL_GUTTER); + + for (; i < reflow->column_count; i++) { + if ( running_width > x + width ) + break; + x_rect = running_width; + gdk_draw_rectangle(drawable, + GTK_WIDGET(item->canvas)->style->fg_gc[GTK_STATE_NORMAL], + TRUE, + x_rect - x, + y_rect - y, + width_rect - 1, + height_rect - 1); + running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + } + } +} + +static void +e_reflow_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags) +{ + EReflow *reflow; + double x0, x1, y0, y1; + + reflow = E_REFLOW (item); + + if (GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->update) + GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->update (item, affine, clip_path, flags); + + x0 = item->x1; + y0 = item->y1; + x1 = item->x2; + y1 = item->y2; + if ( x1 < x0 + reflow->width ) + x1 = x0 + reflow->width; + if ( y1 < y0 + reflow->height ) + y1 = y0 + reflow->height; + item->x2 = x1; + item->y2 = y1; + + if (reflow->need_height_update) { + x0 = item->x1; + y0 = item->y1; + x1 = item->x2; + y1 = item->y2; + if ( x0 > 0 ) + x0 = 0; + if ( y0 > 0 ) + y0 = 0; + if ( x1 < E_REFLOW(item)->width ) + x1 = E_REFLOW(item)->width; + if ( x1 < E_REFLOW(item)->height ) + x1 = E_REFLOW(item)->height; + + gnome_canvas_request_redraw(item->canvas, x0, y0, x1, y1); + reflow->need_height_update = FALSE; + } else if (reflow->need_column_resize) { + gint x_rect, y_rect, width_rect, height_rect; + gint start_line = e_reflow_pick_line(reflow, + gtk_layout_get_hadjustment(GTK_LAYOUT(item->canvas))->value); + gdouble running_width; + gint i; + double column_width; + + if ( reflow->previous_temp_column_width != -1 ) { + running_width = start_line * (reflow->column_width + E_REFLOW_FULL_GUTTER); + column_width = reflow->previous_temp_column_width; + running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER); + running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + y_rect = E_REFLOW_BORDER_WIDTH; + width_rect = E_REFLOW_DIVIDER_WIDTH; + height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); + + for ( i = 0; i < reflow->column_count; i++) { + x_rect = running_width; + gnome_canvas_request_redraw(item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect); + running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + } + } + + if ( reflow->temp_column_width != -1 ) { + running_width = start_line * (reflow->column_width + E_REFLOW_FULL_GUTTER); + column_width = reflow->temp_column_width; + running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER); + running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + y_rect = E_REFLOW_BORDER_WIDTH; + width_rect = E_REFLOW_DIVIDER_WIDTH; + height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); + + for ( i = 0; i < reflow->column_count; i++) { + x_rect = running_width; + gnome_canvas_request_redraw(item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect); + running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; + } + } + + reflow->previous_temp_column_width = reflow->temp_column_width; + reflow->need_column_resize = FALSE; + } +} + +static double +e_reflow_point (GnomeCanvasItem *item, + double x, double y, gint cx, gint cy, + GnomeCanvasItem **actual_item) +{ + double distance = 1; + + *actual_item = NULL; + + if (GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->point) + distance = GNOME_CANVAS_ITEM_CLASS(e_reflow_parent_class)->point (item, x, y, cx, cy, actual_item); + if ((gint) (distance * item->canvas->pixels_per_unit + 0.5) <= item->canvas->close_enough && *actual_item) + return distance; + + *actual_item = item; + return 0; +#if 0 + if (y >= E_REFLOW_BORDER_WIDTH && y <= reflow->height - E_REFLOW_BORDER_WIDTH) { + gfloat n_x; + n_x = x; + n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; + n_x = fmod(n_x, (reflow->column_width + E_REFLOW_FULL_GUTTER)); + if (n_x < E_REFLOW_FULL_GUTTER) { + *actual_item = item; + return 0; + } + } + return distance; +#endif +} + +static void +e_reflow_reflow( GnomeCanvasItem *item, gint flags ) +{ + EReflow *reflow = E_REFLOW(item); + gdouble old_width; + gdouble running_width; + gdouble running_height; + gint next_column; + gint i; + + if (! (GTK_OBJECT_FLAGS (reflow) & GNOME_CANVAS_ITEM_REALIZED)) + return; + + if (reflow->need_reflow_columns) { + reflow_columns (reflow); + } + + old_width = reflow->width; + + running_width = E_REFLOW_BORDER_WIDTH; + running_height = E_REFLOW_BORDER_WIDTH; + + next_column = 1; + + for (i = 0; i < reflow->count; i++) { + gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); + if (next_column < reflow->column_count && i == reflow->columns[next_column]) { + running_height = E_REFLOW_BORDER_WIDTH; + running_width += reflow->column_width + E_REFLOW_FULL_GUTTER; + next_column ++; + } + + if (unsorted >= 0 && reflow->items[unsorted]) { + e_canvas_item_move_absolute(GNOME_CANVAS_ITEM(reflow->items[unsorted]), + (double) running_width, + (double) running_height); + running_height += reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH; + } + } + reflow->width = running_width + reflow->column_width + E_REFLOW_BORDER_WIDTH; + if ( reflow->width < reflow->minimum_width ) + reflow->width = reflow->minimum_width; + if (old_width != reflow->width) + e_canvas_item_request_parent_reflow(item); +} + +static gint +e_reflow_selection_event_real (EReflow *reflow, GnomeCanvasItem *item, GdkEvent *event) +{ + gint row; + gint return_val = TRUE; + switch (event->type) { + case GDK_BUTTON_PRESS: + switch (event->button.button) { + case 1: /* Fall through. */ + case 2: + row = er_find_item (reflow, item); + if (event->button.button == 1) { + reflow->maybe_did_something = + e_selection_model_maybe_do_something(reflow->selection, row, 0, event->button.state); + reflow->maybe_in_drag = TRUE; + } else { + e_selection_model_do_something(reflow->selection, row, 0, event->button.state); + } + break; + case 3: + row = er_find_item (reflow, item); + e_selection_model_right_click_down(reflow->selection, row, 0, 0); + break; + default: + return_val = FALSE; + break; + } + break; + case GDK_BUTTON_RELEASE: + if (event->button.button == 1) { + if (reflow->maybe_in_drag) { + reflow->maybe_in_drag = FALSE; + if (!reflow->maybe_did_something) { + row = er_find_item (reflow, item); + e_selection_model_do_something(reflow->selection, row, 0, event->button.state); + } + } + } + break; + case GDK_KEY_PRESS: + return_val = e_selection_model_key_press(reflow->selection, (GdkEventKey *) event); + break; + default: + return_val = FALSE; + break; + } + + return return_val; +} + +static void +e_reflow_class_init (EReflowClass *klass) +{ + GObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + object_class = (GObjectClass*) klass; + item_class = (GnomeCanvasItemClass *) klass; + + object_class->set_property = e_reflow_set_property; + object_class->get_property = e_reflow_get_property; + object_class->dispose = e_reflow_dispose; + + /* GnomeCanvasItem method overrides */ + item_class->event = e_reflow_event; + item_class->realize = e_reflow_realize; + item_class->unrealize = e_reflow_unrealize; + item_class->draw = e_reflow_draw; + item_class->update = e_reflow_update; + item_class->point = e_reflow_point; + + klass->selection_event = e_reflow_selection_event_real; + klass->column_width_changed = NULL; + + g_object_class_install_property (object_class, PROP_MINIMUM_WIDTH, + g_param_spec_double ("minimum_width", + _( "Minimum width" ), + _( "Minimum Width" ), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_double ("width", + _( "Width" ), + _( "Width" ), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE)); + + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_double ("height", + _( "Height" ), + _( "Height" ), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_EMPTY_MESSAGE, + g_param_spec_string ("empty_message", + _( "Empty message" ), + _( "Empty message" ), + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_MODEL, + g_param_spec_object ("model", + _( "Reflow model" ), + _( "Reflow model" ), + E_REFLOW_MODEL_TYPE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_COLUMN_WIDTH, + g_param_spec_double ("column_width", + _( "Column width" ), + _( "Column width" ), + 0.0, G_MAXDOUBLE, 150.0, + G_PARAM_READWRITE)); + + signals [SELECTION_EVENT] = + g_signal_new ("selection_event", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowClass, selection_event), + NULL, NULL, + e_marshal_INT__OBJECT_BOXED, + G_TYPE_INT, 2, G_TYPE_OBJECT, + GDK_TYPE_EVENT); + + signals [COLUMN_WIDTH_CHANGED] = + g_signal_new ("column_width_changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EReflowClass, column_width_changed), + NULL, NULL, + g_cclosure_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, G_TYPE_DOUBLE); +} + +static void +e_reflow_init (EReflow *reflow) +{ + reflow->model = NULL; + reflow->items = NULL; + reflow->heights = NULL; + reflow->count = 0; + + reflow->columns = NULL; + reflow->column_count = 0; + + reflow->empty_text = NULL; + reflow->empty_message = NULL; + + reflow->minimum_width = 10; + reflow->width = 10; + reflow->height = 10; + + reflow->column_width = 150; + + reflow->column_drag = FALSE; + + reflow->need_height_update = FALSE; + reflow->need_column_resize = FALSE; + reflow->need_reflow_columns = FALSE; + + reflow->maybe_did_something = FALSE; + reflow->maybe_in_drag = FALSE; + + reflow->default_cursor_shown = TRUE; + reflow->arrow_cursor = NULL; + reflow->default_cursor = NULL; + + reflow->cursor_row = -1; + + reflow->incarnate_idle_id = 0; + reflow->do_adjustment_idle_id = 0; + reflow->set_scroll_adjustments_id = 0; + + reflow->selection = E_SELECTION_MODEL (e_selection_model_simple_new()); + reflow->sorter = e_sorter_array_new (er_compare, reflow); + + g_object_set (reflow->selection, + "sorter", reflow->sorter, + NULL); + + reflow->selection_changed_id = + g_signal_connect(reflow->selection, "selection_changed", + G_CALLBACK (selection_changed), reflow); + reflow->selection_row_changed_id = + g_signal_connect(reflow->selection, "selection_row_changed", + G_CALLBACK (selection_row_changed), reflow); + reflow->cursor_changed_id = + g_signal_connect(reflow->selection, "cursor_changed", + G_CALLBACK (cursor_changed), reflow); + + e_canvas_item_set_reflow_callback(GNOME_CANVAS_ITEM(reflow), e_reflow_reflow); +} diff --git a/widgets/text/e-reflow.h b/widgets/text/e-reflow.h new file mode 100644 index 0000000000..234dbc16a2 --- /dev/null +++ b/widgets/text/e-reflow.h @@ -0,0 +1,141 @@ +/* + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Chris Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __E_REFLOW_H__ +#define __E_REFLOW_H__ + +#include <libgnomecanvas/gnome-canvas.h> +#include <text/e-reflow-model.h> +#include <misc/e-selection-model.h> +#include <e-util/e-sorter-array.h> + +G_BEGIN_DECLS + +/* EReflow - A canvas item container. + * + * The following arguments are available: + * + * name type read/write description + * -------------------------------------------------------------------------------- + * minimum_width double RW minimum width of the reflow. width >= minimum_width + * width double R width of the reflow + * height double RW height of the reflow + */ + +#define E_REFLOW_TYPE (e_reflow_get_type ()) +#define E_REFLOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_REFLOW_TYPE, EReflow)) +#define E_REFLOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_REFLOW_TYPE, EReflowClass)) +#define E_IS_REFLOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_REFLOW_TYPE)) +#define E_IS_REFLOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_REFLOW_TYPE)) + + +typedef struct EReflowPriv EReflowPriv; + +typedef struct _EReflow EReflow; +typedef struct _EReflowClass EReflowClass; + +struct _EReflow +{ + GnomeCanvasGroup parent; + + /* item specific fields */ + EReflowModel *model; + guint model_changed_id; + guint comparison_changed_id; + guint model_items_inserted_id; + guint model_item_removed_id; + guint model_item_changed_id; + + ESelectionModel *selection; + guint selection_changed_id; + guint selection_row_changed_id; + guint cursor_changed_id; + ESorterArray *sorter; + + GtkAdjustment *adjustment; + guint adjustment_changed_id; + guint adjustment_value_changed_id; + guint set_scroll_adjustments_id; + + gint *heights; + GnomeCanvasItem **items; + gint count; + gint allocated_count; + + gint *columns; + gint column_count; /* Number of columnns */ + + GnomeCanvasItem *empty_text; + gchar *empty_message; + + double minimum_width; + double width; + double height; + + double column_width; + + gint incarnate_idle_id; + gint do_adjustment_idle_id; + + /* These are all for when the column is being dragged. */ + gdouble start_x; + gint which_column_dragged; + double temp_column_width; + double previous_temp_column_width; + + gint cursor_row; + + gint reflow_from_column; + + guint column_drag : 1; + + guint need_height_update : 1; + guint need_column_resize : 1; + guint need_reflow_columns : 1; + + guint default_cursor_shown : 1; + + guint maybe_did_something : 1; + guint maybe_in_drag : 1; + GdkCursor *arrow_cursor; + GdkCursor *default_cursor; +}; + +struct _EReflowClass +{ + GnomeCanvasGroupClass parent_class; + + gint (*selection_event) (EReflow *reflow, GnomeCanvasItem *item, GdkEvent *event); + void (*column_width_changed) (EReflow *reflow, double width); +}; + +/* + * To be added to a reflow, an item must have the argument "width" as + * a Read/Write argument and "height" as a Read Only argument. It + * should also do an ECanvas parent reflow request if its size + * changes. + */ +GType e_reflow_get_type (void); + +G_END_DECLS + +#endif /* __E_REFLOW_H__ */ diff --git a/widgets/text/e-text.c b/widgets/text/e-text.c index 0b2f84cf8e..7c542d8f5f 100644 --- a/widgets/text/e-text.c +++ b/widgets/text/e-text.c @@ -46,10 +46,10 @@ #include <gtk/gtk.h> #include <libgnomecanvas/gnome-canvas-rect-ellipse.h> -#include "a11y/e-text/gal-a11y-e-text.h" +#include "gal-a11y-e-text.h" #include "misc/e-canvas.h" #include "misc/e-canvas-utils.h" -#include "misc/e-unicode.h" +#include "e-util/e-unicode.h" #include <glib/gi18n.h> #include "e-util/e-text-event-processor-emacs-like.h" #include "e-util/e-util.h" diff --git a/widgets/text/gal-a11y-e-text-factory.c b/widgets/text/gal-a11y-e-text-factory.c new file mode 100644 index 0000000000..2df9241014 --- /dev/null +++ b/widgets/text/gal-a11y-e-text-factory.c @@ -0,0 +1,101 @@ +/* + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Christopher James Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include <config.h> +#include "text/e-text.h" +#include "gal-a11y-e-text-factory.h" +#include "gal-a11y-e-text.h" + +#define CS_CLASS(factory) (G_TYPE_INSTANCE_GET_CLASS ((factory), C_TYPE_STREAM, GalA11yETextFactoryClass)) +static AtkObjectFactoryClass *parent_class; +#define PARENT_TYPE (ATK_TYPE_OBJECT_FACTORY) + +/* Static functions */ + +static GType +gal_a11y_e_text_factory_get_accessible_type (void) +{ + return GAL_A11Y_TYPE_E_TEXT; +} + +static AtkObject* +gal_a11y_e_text_factory_create_accessible (GObject *obj) +{ + AtkObject *atk_object; + + g_return_val_if_fail (E_IS_TEXT (obj), NULL); + + atk_object = g_object_new (GAL_A11Y_TYPE_E_TEXT, NULL); + atk_object_initialize (atk_object, obj); + + return atk_object; +} + +static void +gal_a11y_e_text_factory_class_init (GalA11yETextFactoryClass *klass) +{ + AtkObjectFactoryClass *factory_class = ATK_OBJECT_FACTORY_CLASS (klass); + + parent_class = g_type_class_ref (PARENT_TYPE); + + factory_class->create_accessible = gal_a11y_e_text_factory_create_accessible; + factory_class->get_accessible_type = gal_a11y_e_text_factory_get_accessible_type; +} + +static void +gal_a11y_e_text_factory_init (GalA11yETextFactory *factory) +{ +} + +/** + * gal_a11y_e_text_factory_get_type: + * @void: + * + * Registers the &GalA11yETextFactory class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &GalA11yETextFactory class. + **/ +GType +gal_a11y_e_text_factory_get_type (void) +{ + static GType type = 0; + + if (!type) { + GTypeInfo info = { + sizeof (GalA11yETextFactoryClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gal_a11y_e_text_factory_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GalA11yETextFactory), + 0, + (GInstanceInitFunc) gal_a11y_e_text_factory_init, + NULL /* value_text */ + }; + + type = g_type_register_static (PARENT_TYPE, "GalA11yETextFactory", &info, 0); + } + + return type; +} diff --git a/widgets/text/gal-a11y-e-text-factory.h b/widgets/text/gal-a11y-e-text-factory.h new file mode 100644 index 0000000000..df7638f64f --- /dev/null +++ b/widgets/text/gal-a11y-e-text-factory.h @@ -0,0 +1,50 @@ +/* + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Christopher James Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __GAL_A11Y_E_TEXT_FACTORY_H__ +#define __GAL_A11Y_E_TEXT_FACTORY_H__ + +#include <glib-object.h> +#include <atk/atkobjectfactory.h> + +#define GAL_A11Y_TYPE_E_TEXT_FACTORY (gal_a11y_e_text_factory_get_type ()) +#define GAL_A11Y_E_TEXT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TEXT_FACTORY, GalA11yETextFactory)) +#define GAL_A11Y_E_TEXT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TEXT_FACTORY, GalA11yETextFactoryClass)) +#define GAL_A11Y_IS_E_TEXT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TEXT_FACTORY)) +#define GAL_A11Y_IS_E_TEXT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TEXT_FACTORY)) + +typedef struct _GalA11yETextFactory GalA11yETextFactory; +typedef struct _GalA11yETextFactoryClass GalA11yETextFactoryClass; + +struct _GalA11yETextFactory { + AtkObjectFactory object; +}; + +struct _GalA11yETextFactoryClass { + AtkObjectFactoryClass parent_class; +}; + + +/* Standard Glib function */ +GType gal_a11y_e_text_factory_get_type (void); + +#endif /* ! __GAL_A11Y_E_TEXT_FACTORY_H__ */ diff --git a/widgets/text/gal-a11y-e-text.c b/widgets/text/gal-a11y-e-text.c new file mode 100644 index 0000000000..99fb4e8a94 --- /dev/null +++ b/widgets/text/gal-a11y-e-text.c @@ -0,0 +1,1134 @@ +/* + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Christopher James Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include <config.h> + +#include <string.h> + +#include <gtk/gtk.h> + +#include "a11y/gal-a11y-util.h" +#include "text/e-text.h" +#include "text/e-text-model-repos.h" + +#include "gal-a11y-e-text.h" +#include "gal-a11y-e-text-factory.h" + +#define CS_CLASS(a11y) (G_TYPE_INSTANCE_GET_CLASS ((a11y), C_TYPE_STREAM, GalA11yETextClass)) +static GObjectClass *parent_class; +static AtkComponentIface *component_parent_iface; +static GType parent_type; +static gint priv_offset; +static GQuark quark_accessible_object = 0; +#define GET_PRIVATE(object) ((GalA11yETextPrivate *) (((gchar *) object) + priv_offset)) +#define PARENT_TYPE (parent_type) + +struct _GalA11yETextPrivate { + gint dummy; +}; + +static void +et_dispose (GObject *object) +{ + if (parent_class->dispose) + parent_class->dispose (object); +} + +/* Static functions */ + +static void +et_get_extents (AtkComponent *component, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coord_type) +{ + EText *item = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (component))); + double real_width; + double real_height; + gint fake_width; + gint fake_height; + + if (component_parent_iface && + component_parent_iface->get_extents) + component_parent_iface->get_extents (component, + x, + y, + &fake_width, + &fake_height, + coord_type); + + g_object_get (item, + "text_width", &real_width, + "text_height", &real_height, + NULL); + + if (width) + *width = real_width; + if (height) + *height = real_height; +} + +static const gchar * +et_get_full_text (AtkText *text) +{ + EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text))); + ETextModel *model; + const gchar *full_text; + + g_object_get (etext, "model", &model, NULL); + + full_text = e_text_model_get_text (model); + + return full_text; +} + +static void +et_set_full_text (AtkEditableText *text, + const gchar *full_text) +{ + EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text))); + ETextModel *model; + + g_object_get (etext, "model", &model, NULL); + + e_text_model_set_text (model, full_text); +} + +static gchar * +et_get_text (AtkText *text, + gint start_offset, + gint end_offset) +{ + gint start, end, real_start, real_end, len; + const gchar *full_text = et_get_full_text (text); + if (full_text == NULL) + return NULL; + len = g_utf8_strlen (full_text, -1); + + start = MIN (MAX (0, start_offset), len); + end = MIN (MAX (-1, end_offset), len); + + if (end_offset == -1) + end = strlen (full_text); + else + end = g_utf8_offset_to_pointer (full_text, end) - full_text; + + start = g_utf8_offset_to_pointer (full_text, start) - full_text; + + real_start = MIN (start, end); + real_end = MAX (start, end); + + return g_strndup (full_text + real_start, real_end - real_start); +} + +static gboolean +is_a_seperator (gunichar c) +{ + return g_unichar_ispunct(c) || g_unichar_isspace(c); +} + +static gint +find_word_start (const gchar *text, + gint begin_offset, + gint step) +{ + gint offset; + gchar *at_offset; + gunichar current, previous; + gint len; + + offset = begin_offset; + len = g_utf8_strlen (text, -1); + + while (offset > 0 && offset < len) { + at_offset = g_utf8_offset_to_pointer (text, offset); + current = g_utf8_get_char_validated (at_offset, -1); + at_offset = g_utf8_offset_to_pointer (text, offset-1); + previous = g_utf8_get_char_validated (at_offset, -1); + if ((! is_a_seperator (current)) && is_a_seperator (previous)) + break; + offset += step; + } + + return offset; +} + +static gint +find_word_end (const gchar *text, + gint begin_offset, + gint step) +{ + gint offset; + gchar *at_offset; + gunichar current, previous; + gint len; + + offset = begin_offset; + len = g_utf8_strlen (text, -1); + + while (offset > 0 && offset < len) { + at_offset = g_utf8_offset_to_pointer (text, offset); + current = g_utf8_get_char_validated (at_offset, -1); + at_offset = g_utf8_offset_to_pointer (text, offset-1); + previous = g_utf8_get_char_validated (at_offset, -1); + if (is_a_seperator (current) && (! is_a_seperator (previous))) + break; + offset += step; + } + + return offset; +} + +static gint +find_sentence_start (const gchar *text, + gint begin_offset, + gint step) +{ + gint offset, last_word_end, len; + gchar *at_offset; + gunichar ch; + gint i; + + offset = find_word_start (text, begin_offset, step); + len = g_utf8_strlen (text, -1); + + while (offset>0 && offset <len) { + last_word_end = find_word_end (text, offset - 1, -1); + if (last_word_end == 0) + break; + for (i = last_word_end; i < offset; i++) { + at_offset = g_utf8_offset_to_pointer (text, i); + ch = g_utf8_get_char_validated (at_offset, -1); + if (ch == '.' || ch == '!' || ch == '?') + return offset; + } + + offset = find_word_start (text, offset + step, step); + } + + return offset; +} + +static gint +find_sentence_end (const gchar *text, + gint begin_offset, + gint step) +{ + gint offset; + gchar *at_offset; + gunichar previous; + gint len; + + offset = begin_offset; + len = g_utf8_strlen (text, -1); + + while (offset > 0 && offset < len) { + at_offset = g_utf8_offset_to_pointer (text, offset - 1); + previous = g_utf8_get_char_validated (at_offset, -1); + if (previous == '.' || previous == '!' || previous == '?') + break; + offset += step; + } + + return offset; +} + +static gint +find_line_start (const gchar *text, + gint begin_offset, + gint step) +{ + gint offset; + gchar *at_offset; + gunichar previous; + gint len; + + offset = begin_offset; + len = g_utf8_strlen (text, -1); + + while (offset > 0 && offset < len) { + at_offset = g_utf8_offset_to_pointer (text, offset - 1); + previous = g_utf8_get_char_validated (at_offset, -1); + if (previous == '\n' || previous == '\r') + break; + offset += step; + } + + return offset; +} + +static gint +find_line_end (const gchar *text, + gint begin_offset, + gint step) +{ + gint offset; + gchar *at_offset; + gunichar current; + gint len; + + offset = begin_offset; + len = g_utf8_strlen (text, -1); + + while (offset >= 0 && offset < len) { + at_offset = g_utf8_offset_to_pointer (text, offset); + current = g_utf8_get_char_validated (at_offset, -1); + if (current == '\n' || current == '\r') + break; + offset += step; + } + + return offset; +} + +static gchar * +et_get_text_after_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + gint start, end, len; + const gchar *full_text = et_get_full_text (text); + g_return_val_if_fail (full_text, NULL); + + switch (boundary_type) + { + case ATK_TEXT_BOUNDARY_CHAR: + start = offset + 1; + end = offset + 2; + break; + case ATK_TEXT_BOUNDARY_WORD_START: + start = find_word_start (full_text, offset + 1, 1); + end = find_word_start (full_text, start + 1, 1); + break; + case ATK_TEXT_BOUNDARY_WORD_END: + start = find_word_end (full_text, offset + 1, 1); + end = find_word_end (full_text, start + 1, 1); + break; + case ATK_TEXT_BOUNDARY_SENTENCE_START: + start = find_sentence_start (full_text, offset + 1, 1); + end = find_sentence_start (full_text, start + 1, 1); + break; + case ATK_TEXT_BOUNDARY_SENTENCE_END: + start = find_sentence_end (full_text, offset + 1, 1); + end = find_sentence_end (full_text, start + 1, 1); + break; + case ATK_TEXT_BOUNDARY_LINE_START: + start = find_line_start (full_text, offset + 1, 1); + end = find_line_start (full_text, start + 1, 1); + break; + case ATK_TEXT_BOUNDARY_LINE_END: + start = find_line_end (full_text, offset + 1, 1); + end = find_line_end (full_text, start + 1, 1); + break; + default: + return NULL; + } + + len = g_utf8_strlen (full_text, -1); + if (start_offset) + *start_offset = MIN (MAX (0, start), len); + if (end_offset) + *end_offset = MIN (MAX (0, end), len); + return et_get_text (text, start, end); +} + +static gchar * +et_get_text_at_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + gint start, end, len; + const gchar *full_text = et_get_full_text (text); + g_return_val_if_fail (full_text, NULL); + + switch (boundary_type) + { + case ATK_TEXT_BOUNDARY_CHAR: + start = offset; + end = offset + 1; + break; + case ATK_TEXT_BOUNDARY_WORD_START: + start = find_word_start (full_text, offset - 1, -1); + end = find_word_start (full_text, offset, 1); + break; + case ATK_TEXT_BOUNDARY_WORD_END: + start = find_word_end (full_text, offset, -1); + end = find_word_end (full_text, offset + 1, 1); + break; + case ATK_TEXT_BOUNDARY_SENTENCE_START: + start = find_sentence_start (full_text, offset - 1, -1); + end = find_sentence_start (full_text, offset, 1); + break; + case ATK_TEXT_BOUNDARY_SENTENCE_END: + start = find_sentence_end (full_text, offset, -1); + end = find_sentence_end (full_text, offset + 1, 1); + break; + case ATK_TEXT_BOUNDARY_LINE_START: + start = find_line_start (full_text, offset - 1, -1); + end = find_line_start (full_text, offset, 1); + break; + case ATK_TEXT_BOUNDARY_LINE_END: + start = find_line_end (full_text, offset, -1); + end = find_line_end (full_text, offset + 1, 1); + break; + default: + return NULL; + } + + len = g_utf8_strlen (full_text, -1); + if (start_offset) + *start_offset = MIN (MAX (0, start), len); + if (end_offset) + *end_offset = MIN (MAX (0, end), len); + return et_get_text (text, start, end); +} + +static gunichar +et_get_character_at_offset (AtkText *text, + gint offset) +{ + const gchar *full_text = et_get_full_text (text); + gchar *at_offset; + + at_offset = g_utf8_offset_to_pointer (full_text, offset); + return g_utf8_get_char_validated (at_offset, -1); +} + + +static gchar * +et_get_text_before_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + gint start, end, len; + const gchar *full_text = et_get_full_text (text); + g_return_val_if_fail (full_text, NULL); + + switch (boundary_type) + { + case ATK_TEXT_BOUNDARY_CHAR: + start = offset - 1; + end = offset; + break; + case ATK_TEXT_BOUNDARY_WORD_START: + end = find_word_start (full_text, offset - 1, -1); + start = find_word_start (full_text, end - 1, -1); + break; + case ATK_TEXT_BOUNDARY_WORD_END: + end = find_word_end (full_text, offset, -1); + start = find_word_end (full_text, end - 1, -1); + break; + case ATK_TEXT_BOUNDARY_SENTENCE_START: + end = find_sentence_start (full_text, offset, -1); + start = find_sentence_start (full_text, end - 1, -1); + break; + case ATK_TEXT_BOUNDARY_SENTENCE_END: + end = find_sentence_end (full_text, offset, -1); + start = find_sentence_end (full_text, end - 1, -1); + break; + case ATK_TEXT_BOUNDARY_LINE_START: + end = find_line_start (full_text, offset, -1); + start = find_line_start (full_text, end - 1, -1); + break; + case ATK_TEXT_BOUNDARY_LINE_END: + end = find_line_end (full_text, offset, -1); + start = find_line_end (full_text, end - 1, -1); + break; + default: + return NULL; + } + + len = g_utf8_strlen (full_text, -1); + if (start_offset) + *start_offset = MIN (MAX (0, start), len); + if (end_offset) + *end_offset = MIN (MAX (0, end), len); + return et_get_text (text, start, end); +} + +static gint +et_get_caret_offset (AtkText *text) +{ + GObject *obj; + EText *etext; + gint offset; + + g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE(text), -1); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return -1; + + g_return_val_if_fail (E_IS_TEXT (obj), -1); + etext = E_TEXT (obj); + + g_object_get (etext, "cursor_pos", &offset, NULL); + return offset; +} + + +static AtkAttributeSet* +et_get_run_attributes (AtkText *text, + gint offset, + gint *start_offset, + gint *end_offset) +{ + /* Unimplemented */ + return NULL; +} + + +static AtkAttributeSet* +et_get_default_attributes (AtkText *text) +{ + /* Unimplemented */ + return NULL; +} + + +static void +et_get_character_extents (AtkText *text, + gint offset, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coords) +{ + GObject *obj; + EText *etext; + GnomeCanvas *canvas; + gint x_widget, y_widget, x_window, y_window; + GdkWindow *window; + GtkWidget *widget; + PangoRectangle pango_pos; + + g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE(text)); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return; + g_return_if_fail (E_IS_TEXT (obj)); + etext = E_TEXT(obj); + canvas = GNOME_CANVAS_ITEM(etext)->canvas; + widget = GTK_WIDGET(canvas); + window = widget->window; + gdk_window_get_origin (window, &x_widget, &y_widget); + + pango_layout_index_to_pos (etext->layout, offset, &pango_pos); + pango_pos.x = PANGO_PIXELS (pango_pos.x); + pango_pos.y = PANGO_PIXELS (pango_pos.y); + pango_pos.width = (pango_pos.width + PANGO_SCALE / 2) / PANGO_SCALE; + pango_pos.height = (pango_pos.height + PANGO_SCALE / 2) / PANGO_SCALE; + + *x = pango_pos.x + x_widget; + *y = pango_pos.y + y_widget; + + *width = pango_pos.width; + *height = pango_pos.height; + + if (etext->draw_borders) { + *x += 3; /*BORDER_INDENT;*/ + *y += 3; /*BORDER_INDENT;*/ + } + + *x += etext->xofs; + *y += etext->yofs; + + if (etext->editing) { + *x -= etext->xofs_edit; + *y -= etext->yofs_edit; + } + + *x += etext->cx; + *y += etext->cy; + + if (coords == ATK_XY_WINDOW) { + window = gdk_window_get_toplevel (window); + gdk_window_get_origin (window, &x_window, &y_window); + *x -= x_window; + *y -= y_window; + } + else if (coords == ATK_XY_SCREEN) { + } + else { + *x = 0; + *y = 0; + *height = 0; + *width = 0; + } +} + + +static gint +et_get_character_count (AtkText *text) +{ + const gchar *full_text = et_get_full_text (text); + + return g_utf8_strlen (full_text, -1); +} + + +static gint +et_get_offset_at_point (AtkText *text, + gint x, + gint y, + AtkCoordType coords) +{ + GObject *obj; + EText *etext; + GnomeCanvas *canvas; + gint x_widget, y_widget, x_window, y_window; + GdkWindow *window; + GtkWidget *widget; + gint index; + gint trailing; + + g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE(text), -1); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return -1; + g_return_val_if_fail (E_IS_TEXT (obj), -1); + etext = E_TEXT(obj); + canvas = GNOME_CANVAS_ITEM(etext)->canvas; + widget = GTK_WIDGET(canvas); + window = widget->window; + gdk_window_get_origin (window, &x_widget, &y_widget); + + if (coords == ATK_XY_SCREEN) { + x = x - x_widget; + y = y - y_widget; + } + else if (coords == ATK_XY_WINDOW) { + window = gdk_window_get_toplevel (window); + gdk_window_get_origin (window, &x_window, &y_window); + x = x - x_widget + x_window; + y = y - y_widget + y_window; + } + else + return -1; + + if (etext->draw_borders) { + x -= 3; /*BORDER_INDENT;*/ + y -= 3; /*BORDER_INDENT;*/ + } + + x -= etext->xofs; + y -= etext->yofs; + + if (etext->editing) { + x += etext->xofs_edit; + y += etext->yofs_edit; + } + + x -= etext->cx; + y -= etext->cy; + + pango_layout_xy_to_index (etext->layout, + x * PANGO_SCALE - PANGO_SCALE / 2, + y * PANGO_SCALE - PANGO_SCALE / 2, + &index, + &trailing); + + return g_utf8_pointer_to_offset (etext->text, etext->text + index + trailing); +} + + +static gint +et_get_n_selections (AtkText *text) +{ + EText *etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text))); + if (etext->selection_start != + etext->selection_end) + return 1; + return 0; +} + + +static gchar * +et_get_selection (AtkText *text, + gint selection_num, + gint *start_offset, + gint *end_offset) +{ + gint start, end, real_start, real_end, len; + EText *etext; + if (selection_num == 0) { + const gchar *full_text = et_get_full_text (text); + if (full_text == NULL) + return NULL; + len = g_utf8_strlen (full_text, -1); + etext = E_TEXT (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text))); + start = MIN (etext->selection_start, etext->selection_end); + end = MAX (etext->selection_start, etext->selection_end); + start = MIN (MAX (0, start), len); + end = MIN (MAX (0, end), len); + if (start != end) { + if (start_offset) + *start_offset = start; + if (end_offset) + *end_offset = end; + real_start = g_utf8_offset_to_pointer (full_text, start) - full_text; + real_end = g_utf8_offset_to_pointer (full_text, end) - full_text; + return g_strndup (full_text + real_start, real_end - real_start); + } + } + + return NULL; +} + + +static gboolean +et_add_selection (AtkText *text, + gint start_offset, + gint end_offset) +{ + GObject *obj; + EText *etext; + + g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return FALSE; + g_return_val_if_fail (E_IS_TEXT (obj), FALSE); + etext = E_TEXT (obj); + + g_return_val_if_fail (start_offset >= 0, FALSE); + g_return_val_if_fail (start_offset >= -1, FALSE); + if (end_offset == -1) + end_offset = et_get_character_count (text); + + if (start_offset != end_offset) { + gint real_start, real_end; + real_start = MIN (start_offset, end_offset); + real_end = MAX (start_offset, end_offset); + etext->selection_start = real_start; + etext->selection_end = real_end; + + gnome_canvas_item_grab_focus (GNOME_CANVAS_ITEM (etext)); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (etext)); + + g_signal_emit_by_name (ATK_OBJECT (text), "text_selection_changed"); + + return TRUE; + } + + return FALSE; +} + + +static gboolean +et_remove_selection (AtkText *text, + gint selection_num) +{ + GObject *obj; + EText *etext; + + g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return FALSE; + g_return_val_if_fail (E_IS_TEXT (obj), FALSE); + etext = E_TEXT (obj); + + if (selection_num == 0 + && etext->selection_start != etext->selection_end) { + etext->selection_end = etext->selection_start; + g_signal_emit_by_name (ATK_OBJECT(text), "text_selection_changed"); + return TRUE; + } + + return FALSE; +} + + +static gboolean +et_set_selection (AtkText *text, + gint selection_num, + gint start_offset, + gint end_offset) +{ + GObject *obj; + + g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return FALSE; + g_return_val_if_fail (E_IS_TEXT (obj), FALSE); + if (selection_num == 0) + return et_add_selection (text, start_offset, end_offset); + return FALSE; +} + + +static gboolean +et_set_caret_offset (AtkText *text, + gint offset) +{ + GObject *obj; + EText *etext; + + g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return FALSE; + + g_return_val_if_fail (E_IS_TEXT (obj), FALSE); + etext = E_TEXT (obj); + + if (offset < -1) + return FALSE; + else { + ETextEventProcessorCommand command; + + if (offset == -1) + offset = et_get_character_count (text); + + command.action = E_TEP_MOVE; + command.position = E_TEP_VALUE; + command.value = offset; + command.time = GDK_CURRENT_TIME; + g_signal_emit_by_name (etext->tep, "command", &command); + return TRUE; + } +} + +static gboolean +et_set_run_attributes (AtkEditableText *text, + AtkAttributeSet *attrib_set, + gint start_offset, + gint end_offset) +{ + /* Unimplemented */ + return FALSE; +} + +static void +et_set_text_contents (AtkEditableText *text, + const gchar *string) +{ + et_set_full_text (text, string); +} + +static void +et_insert_text (AtkEditableText *text, + const gchar *string, + gint length, + gint *position) +{ + /* Utf8 unimplemented */ + gchar *result; + + const gchar *full_text = et_get_full_text (ATK_TEXT (text)); + if (full_text == NULL) + return; + + result = g_strdup_printf ("%.*s%.*s%s", *position, full_text, length, string, full_text + *position); + + et_set_full_text (text, result); + + *position += length; + + g_free (result); +} + +static void +et_copy_text (AtkEditableText *text, + gint start_pos, + gint end_pos) +{ + GObject *obj; + EText *etext; + + g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text)); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return; + + g_return_if_fail (E_IS_TEXT (obj)); + etext = E_TEXT (obj); + + if (start_pos != end_pos) { + etext->selection_start = start_pos; + etext->selection_end = end_pos; + e_text_copy_clipboard (etext); + } +} + +static void +et_delete_text (AtkEditableText *text, + gint start_pos, + gint end_pos) +{ + GObject *obj; + EText *etext; + + g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE(text)); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return; + + g_return_if_fail (E_IS_TEXT (obj)); + etext = E_TEXT (obj); + + etext->selection_start = start_pos; + etext->selection_end = end_pos; + + e_text_delete_selection (etext); +} + +static void +et_cut_text (AtkEditableText *text, + gint start_pos, + gint end_pos) +{ + et_copy_text (text, start_pos, end_pos); + et_delete_text (text, start_pos, end_pos); +} + +static void +et_paste_text (AtkEditableText *text, + gint position) +{ + GObject *obj; + EText *etext; + + g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text)); + obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text)); + if (obj == NULL) + return; + + g_return_if_fail (E_IS_TEXT (obj)); + etext = E_TEXT (obj); + + g_object_set (etext, "cursor_pos", position, NULL); + e_text_paste_clipboard (etext); +} + +static void +et_atk_component_iface_init (AtkComponentIface *iface) +{ + iface->get_extents = et_get_extents; +} + +static void +et_atk_text_iface_init (AtkTextIface *iface) +{ + iface->get_text = et_get_text; + iface->get_text_after_offset = et_get_text_after_offset; + iface->get_text_at_offset = et_get_text_at_offset; + iface->get_character_at_offset = et_get_character_at_offset; + iface->get_text_before_offset = et_get_text_before_offset; + iface->get_caret_offset = et_get_caret_offset; + iface->get_run_attributes = et_get_run_attributes; + iface->get_default_attributes = et_get_default_attributes; + iface->get_character_extents = et_get_character_extents; + iface->get_character_count = et_get_character_count; + iface->get_offset_at_point = et_get_offset_at_point; + iface->get_n_selections = et_get_n_selections; + iface->get_selection = et_get_selection; + iface->add_selection = et_add_selection; + iface->remove_selection = et_remove_selection; + iface->set_selection = et_set_selection; + iface->set_caret_offset = et_set_caret_offset; +} + +static void +et_atk_editable_text_iface_init (AtkEditableTextIface *iface) +{ + iface->set_run_attributes = et_set_run_attributes; + iface->set_text_contents = et_set_text_contents; + iface->insert_text = et_insert_text; + iface->copy_text = et_copy_text; + iface->cut_text = et_cut_text; + iface->delete_text = et_delete_text; + iface->paste_text = et_paste_text; +} + +static void +_et_reposition_cb (ETextModel *model, + ETextModelReposFn fn, + gpointer repos_data, + gpointer user_data) +{ + AtkObject *accessible; + AtkText *text; + + accessible = ATK_OBJECT (user_data); + text = ATK_TEXT (accessible); + + if (fn == e_repos_delete_shift) { + EReposDeleteShift *info = (EReposDeleteShift *) repos_data; + g_signal_emit_by_name (text, "text-changed::delete", info->pos, info->len); + } + else if (fn == e_repos_insert_shift) { + EReposInsertShift *info = (EReposInsertShift *) repos_data; + g_signal_emit_by_name (text, "text-changed::insert", info->pos, info->len); + } +} + +static void +_et_command_cb (ETextEventProcessor *tep, + ETextEventProcessorCommand *command, + gpointer user_data) +{ + AtkObject *accessible; + AtkText *text; + + accessible = ATK_OBJECT (user_data); + text = ATK_TEXT (accessible); + + switch (command->action) { + case E_TEP_MOVE: + g_signal_emit_by_name (text, "text-caret-moved", et_get_caret_offset (text)); + break; + case E_TEP_SELECT: + g_signal_emit_by_name (text, "text-selection-changed"); + break; + default: + break; + } +} + +static void +et_real_initialize (AtkObject *obj, + gpointer data) +{ + EText *etext; + + ATK_OBJECT_CLASS (parent_class)->initialize (obj, data); + + g_return_if_fail (GAL_A11Y_IS_E_TEXT (obj)); + g_return_if_fail (E_IS_TEXT (data)); + + etext = E_TEXT (data); + + /* Set up signal callbacks */ + g_signal_connect (etext->model, "reposition", + G_CALLBACK (_et_reposition_cb), obj); + + if (etext->tep) + g_signal_connect_after (etext->tep, "command", + (GCallback) _et_command_cb, obj); + + obj->role = ATK_ROLE_TEXT; +} + +static void +et_class_init (GalA11yETextClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass); + + quark_accessible_object = g_quark_from_static_string ("gtk-accessible-object"); + parent_class = g_type_class_ref (PARENT_TYPE); + component_parent_iface = g_type_interface_peek(parent_class, ATK_TYPE_COMPONENT); + object_class->dispose = et_dispose; + atk_class->initialize = et_real_initialize; +} + +static void +et_init (GalA11yEText *a11y) +{ +#if 0 + GalA11yETextPrivate *priv; + + priv = GET_PRIVATE (a11y); +#endif +} + +/** + * gal_a11y_e_text_get_type: + * @void: + * + * Registers the &GalA11yEText class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &GalA11yEText class. + **/ +GType +gal_a11y_e_text_get_type (void) +{ + static GType type = 0; + + if (!type) { + AtkObjectFactory *factory; + + GTypeInfo info = { + sizeof (GalA11yETextClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) et_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GalA11yEText), + 0, + (GInstanceInitFunc) et_init, + NULL /* value_text */ + }; + + static const GInterfaceInfo atk_component_info = { + (GInterfaceInitFunc) et_atk_component_iface_init, + (GInterfaceFinalizeFunc) NULL, + NULL + }; + static const GInterfaceInfo atk_text_info = { + (GInterfaceInitFunc) et_atk_text_iface_init, + (GInterfaceFinalizeFunc) NULL, + NULL + }; + static const GInterfaceInfo atk_editable_text_info = { + (GInterfaceInitFunc) et_atk_editable_text_iface_init, + (GInterfaceFinalizeFunc) NULL, + NULL + }; + + factory = atk_registry_get_factory (atk_get_default_registry (), GNOME_TYPE_CANVAS_ITEM); + parent_type = atk_object_factory_get_accessible_type (factory); + + type = gal_a11y_type_register_static_with_private (PARENT_TYPE, "GalA11yEText", &info, 0, + sizeof (GalA11yETextPrivate), &priv_offset); + + g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info); + g_type_add_interface_static (type, ATK_TYPE_TEXT, &atk_text_info); + g_type_add_interface_static (type, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info); + } + + return type; +} + +void +gal_a11y_e_text_init (void) +{ + if (atk_get_root ()) + atk_registry_set_factory_type (atk_get_default_registry (), + E_TYPE_TEXT, + gal_a11y_e_text_factory_get_type ()); + +} + diff --git a/widgets/text/gal-a11y-e-text.h b/widgets/text/gal-a11y-e-text.h new file mode 100644 index 0000000000..6a5bb80de0 --- /dev/null +++ b/widgets/text/gal-a11y-e-text.h @@ -0,0 +1,57 @@ +/* + * + * This program 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) version 3. + * + * This program 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 the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Authors: + * Christopher James Lahey <clahey@ximian.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __GAL_A11Y_E_TEXT_H__ +#define __GAL_A11Y_E_TEXT_H__ + +#include <glib-object.h> +#include <table/e-table-item.h> + +#define GAL_A11Y_TYPE_E_TEXT (gal_a11y_e_text_get_type ()) +#define GAL_A11Y_E_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TEXT, GalA11yEText)) +#define GAL_A11Y_E_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TEXT, GalA11yETextClass)) +#define GAL_A11Y_IS_E_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TEXT)) +#define GAL_A11Y_IS_E_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TEXT)) + +typedef struct _GalA11yEText GalA11yEText; +typedef struct _GalA11yETextClass GalA11yETextClass; +typedef struct _GalA11yETextPrivate GalA11yETextPrivate; + +/* This struct should actually be larger as this isn't what we derive from. + * The GalA11yETextPrivate comes right after the parent class structure. + **/ +struct _GalA11yEText { + AtkObject object; +}; + +struct _GalA11yETextClass { + AtkObject parent_class; +}; + + +/* Standard Glib function */ +GType gal_a11y_e_text_get_type (void); + +void gal_a11y_e_text_init (void); + +#endif /* ! __GAL_A11Y_E_TEXT_H__ */ |