diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2012-12-10 21:09:59 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2012-12-13 03:33:43 +0800 |
commit | d09d8de870b6697c8a8b262e7e077b871a69b315 (patch) | |
tree | 3b718882e7a0bb0a996daf2967a033d91714c9b5 /e-util/e-table-item.c | |
parent | b61331ed03ac1c7a9b8614e25510040b9c60ae02 (diff) | |
download | gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.gz gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.bz2 gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.lz gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.xz gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.zst gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.zip |
Consolidate base utility libraries into libeutil.
Evolution consists of entirely too many small utility libraries, which
increases linking and loading time, places a burden on higher layers of
the application (e.g. modules) which has to remember to link to all the
small in-tree utility libraries, and makes it difficult to generate API
documentation for these utility libraries in one Gtk-Doc module.
Merge the following utility libraries under the umbrella of libeutil,
and enforce a single-include policy on libeutil so we can reorganize
the files as desired without disrupting its pseudo-public API.
libemail-utils/libemail-utils.la
libevolution-utils/libevolution-utils.la
filter/libfilter.la
widgets/e-timezone-dialog/libetimezonedialog.la
widgets/menus/libmenus.la
widgets/misc/libemiscwidgets.la
widgets/table/libetable.la
widgets/text/libetext.la
This also merges libedataserverui from the Evolution-Data-Server module,
since Evolution is its only consumer nowadays, and I'd like to make some
improvements to those APIs without concern for backward-compatibility.
And finally, start a Gtk-Doc module for libeutil. It's going to be a
project just getting all the symbols _listed_ much less _documented_.
But the skeletal structure is in place and I'm off to a good start.
Diffstat (limited to 'e-util/e-table-item.c')
-rw-r--r-- | e-util/e-table-item.c | 4041 |
1 files changed, 4041 insertions, 0 deletions
diff --git a/e-util/e-table-item.c b/e-util/e-table-item.c new file mode 100644 index 0000000000..de749ead68 --- /dev/null +++ b/e-util/e-table-item.c @@ -0,0 +1,4041 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * e-table-item.c + * + * 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> + * Miguel de Icaza <miguel@gnu.org> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + */ +/* + * TODO: + * Add a border to the thing, so that focusing works properly. + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-table-item.h" + +#include <math.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gdk/gdkkeysyms.h> + +#include "e-canvas-utils.h" +#include "e-canvas.h" +#include "e-cell.h" +#include "e-marshal.h" +#include "e-table-subset.h" +#include "gal-a11y-e-table-item-factory.h" +#include "gal-a11y-e-table-item.h" + +/* workaround for avoiding API breakage */ +#define eti_get_type e_table_item_get_type +G_DEFINE_TYPE (ETableItem, eti, GNOME_TYPE_CANVAS_ITEM) + +#define FOCUSED_BORDER 2 + +#define d(x) + +#if d(!)0 +#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__)) +#else +#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x))) +#endif + +static void eti_check_cursor_bounds (ETableItem *eti); +static void eti_cancel_drag_due_to_model_change (ETableItem *eti); + +/* FIXME: Do an analysis of which cell functions are needed before + * realize and make sure that all of them are doable by all the cells + * and that all of the others are only done after realization. */ + +enum { + CURSOR_CHANGE, + CURSOR_ACTIVATED, + DOUBLE_CLICK, + RIGHT_CLICK, + CLICK, + KEY_PRESS, + START_DRAG, + STYLE_SET, + SELECTION_MODEL_REMOVED, + SELECTION_MODEL_ADDED, + LAST_SIGNAL +}; + +static guint eti_signals[LAST_SIGNAL] = { 0, }; + +enum { + PROP_0, + PROP_TABLE_HEADER, + PROP_TABLE_MODEL, + PROP_SELECTION_MODEL, + PROP_TABLE_ALTERNATING_ROW_COLORS, + PROP_TABLE_HORIZONTAL_DRAW_GRID, + PROP_TABLE_VERTICAL_DRAW_GRID, + PROP_TABLE_DRAW_FOCUS, + PROP_CURSOR_MODE, + PROP_LENGTH_THRESHOLD, + PROP_CURSOR_ROW, + PROP_UNIFORM_ROW_HEIGHT, + + PROP_MINIMUM_WIDTH, + PROP_WIDTH, + PROP_HEIGHT +}; + +#define DOUBLE_CLICK_TIME 250 +#define TRIPLE_CLICK_TIME 500 + +static gint eti_get_height (ETableItem *eti); +static gint eti_row_height (ETableItem *eti, gint row); +static void e_table_item_focus (ETableItem *eti, gint col, gint row, GdkModifierType state); +static void eti_cursor_change (ESelectionModel *selection, gint row, gint col, ETableItem *eti); +static void eti_cursor_activated (ESelectionModel *selection, gint row, gint col, ETableItem *eti); +static void eti_selection_change (ESelectionModel *selection, ETableItem *eti); +static void eti_selection_row_change (ESelectionModel *selection, gint row, ETableItem *eti); +static void e_table_item_redraw_row (ETableItem *eti, gint row); + +#define ETI_SINGLE_ROW_HEIGHT(eti) ((eti)->uniform_row_height_cache != -1 ? (eti)->uniform_row_height_cache : eti_row_height((eti), -1)) +#define ETI_MULTIPLE_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row))) +#define ETI_ROW_HEIGHT(eti,row) ((eti)->uniform_row_height ? ETI_SINGLE_ROW_HEIGHT ((eti)) : ETI_MULTIPLE_ROW_HEIGHT((eti),(row))) + +/* tweak_hsv is a really tweaky function. it modifies its first argument, which + * should be the color you want tweaked. delta_h, delta_s and delta_v specify + * how much you want their respective channels modified (and in what direction). + * if it can't do the specified modification, it does it in the oppositon direction */ +static void +e_hsv_tweak (GdkColor *color, + gdouble delta_h, + gdouble delta_s, + gdouble delta_v) +{ + gdouble h, s, v, r, g, b; + + r = color->red / 65535.0f; + g = color->green / 65535.0f; + b = color->blue / 65535.0f; + + gtk_rgb_to_hsv (r, g, b, &h, &s, &v); + + if (h + delta_h < 0) { + h -= delta_h; + } else { + h += delta_h; + } + + if (s + delta_s < 0) { + s -= delta_s; + } else { + s += delta_s; + } + + if (v + delta_v < 0) { + v -= delta_v; + } else { + v += delta_v; + } + + gtk_hsv_to_rgb (h, s, v, &r, &g, &b); + + color->red = r * 65535.0f; + color->green = g * 65535.0f; + color->blue = b * 65535.0f; +} + +inline static gint +model_to_view_row (ETableItem *eti, + gint row) +{ + gint i; + if (row == -1) + return -1; + if (eti->uses_source_model) { + ETableSubset *etss = E_TABLE_SUBSET (eti->table_model); + if (eti->row_guess >= 0 && eti->row_guess < etss->n_map) { + if (etss->map_table[eti->row_guess] == row) { + return eti->row_guess; + } + } + for (i = 0; i < etss->n_map; i++) { + if (etss->map_table[i] == row) + return i; + } + return -1; + } else + return row; +} + +inline static gint +view_to_model_row (ETableItem *eti, + gint row) +{ + if (eti->uses_source_model) { + ETableSubset *etss = E_TABLE_SUBSET (eti->table_model); + if (row >= 0 && row < etss->n_map) { + eti->row_guess = row; + return etss->map_table[row]; + } else + return -1; + } else + return row; +} + +inline static gint +model_to_view_col (ETableItem *eti, + gint col) +{ + gint i; + if (col == -1) + return -1; + for (i = 0; i < eti->cols; i++) { + ETableCol *ecol = e_table_header_get_column (eti->header, i); + if (ecol->col_idx == col) + return i; + } + return -1; +} + +inline static gint +view_to_model_col (ETableItem *eti, + gint col) +{ + ETableCol *ecol = e_table_header_get_column (eti->header, col); + return ecol ? ecol->col_idx : -1; +} + +static void +grab_cancelled (ECanvas *canvas, + GnomeCanvasItem *item, + gpointer data) +{ + ETableItem *eti = data; + + eti->grab_cancelled = TRUE; +} + +inline static void +eti_grab (ETableItem *eti, + GdkDevice *device, + guint32 time) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + d (g_print ("%s: time: %d\n", __FUNCTION__, time)); + if (eti->grabbed_count == 0) { + GdkGrabStatus grab_status; + + eti->gtk_grabbed = FALSE; + eti->grab_cancelled = FALSE; + + grab_status = e_canvas_item_grab ( + E_CANVAS (item->canvas), + item, + GDK_BUTTON1_MOTION_MASK | + GDK_BUTTON2_MOTION_MASK | + GDK_BUTTON3_MOTION_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK, + NULL, + device, time, + grab_cancelled, + eti); + + if (grab_status != GDK_GRAB_SUCCESS) { + d (g_print ("%s: gtk_grab_add\n", __FUNCTION__)); + gtk_grab_add (GTK_WIDGET (item->canvas)); + eti->gtk_grabbed = TRUE; + } + } + eti->grabbed_count++; +} + +inline static void +eti_ungrab (ETableItem *eti, + guint32 time) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + d (g_print ("%s: time: %d\n", __FUNCTION__, time)); + eti->grabbed_count--; + if (eti->grabbed_count == 0) { + if (eti->grab_cancelled) { + eti->grab_cancelled = FALSE; + } else { + if (eti->gtk_grabbed) { + d (g_print ("%s: gtk_grab_remove\n", __FUNCTION__)); + gtk_grab_remove (GTK_WIDGET (item->canvas)); + eti->gtk_grabbed = FALSE; + } + gnome_canvas_item_ungrab (item, time); + eti->grabbed_col = -1; + eti->grabbed_row = -1; + } + } +} + +inline static gboolean +eti_editing (ETableItem *eti) +{ + d (g_print ("%s: %s\n", __FUNCTION__, (eti->editing_col == -1) ? "false":"true")); + + if (eti->editing_col == -1) + return FALSE; + else + return TRUE; +} + +inline static GdkColor * +eti_get_cell_background_color (ETableItem *eti, + gint row, + gint col, + gboolean selected, + gboolean *allocatedp) +{ + ECellView *ecell_view = eti->cell_views[col]; + GtkWidget *canvas; + GdkColor *background, bg; + GtkStyle *style; + gchar *color_spec = NULL; + gboolean allocated = FALSE; + + canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas); + style = gtk_widget_get_style (canvas); + + if (selected) { + if (gtk_widget_has_focus (canvas)) + background = &style->bg[GTK_STATE_SELECTED]; + else + background = &style->bg[GTK_STATE_ACTIVE]; + } else { + background = &style->base[GTK_STATE_NORMAL]; + } + + color_spec = e_cell_get_bg_color (ecell_view, row); + + if (color_spec != NULL) { + if (gdk_color_parse (color_spec, &bg)) { + background = gdk_color_copy (&bg); + allocated = TRUE; + } + } + + if (eti->alternating_row_colors) { + if (row % 2) { + + } else { + if (!allocated) { + background = gdk_color_copy (background); + allocated = TRUE; + } + e_hsv_tweak (background, 0.0f, 0.0f, -0.07f); + } + } + if (allocatedp) + *allocatedp = allocated; + + return background; +} + +inline static GdkColor * +eti_get_cell_foreground_color (ETableItem *eti, + gint row, + gint col, + gboolean selected, + gboolean *allocated) +{ + GtkWidget *canvas; + GdkColor *foreground; + GtkStyle *style; + + canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas); + style = gtk_widget_get_style (canvas); + + if (allocated) + *allocated = FALSE; + + if (selected) { + if (gtk_widget_has_focus (canvas)) + foreground = &style->fg[GTK_STATE_SELECTED]; + else + foreground = &style->fg[GTK_STATE_ACTIVE]; + } else { + foreground = &style->text[GTK_STATE_NORMAL]; + } + + return foreground; +} + +static void +eti_free_save_state (ETableItem *eti) +{ + if (eti->save_row == -1 || + !eti->cell_views_realized) + return; + + e_cell_free_state ( + eti->cell_views[eti->save_col], view_to_model_col (eti, eti->save_col), + eti->save_col, eti->save_row, eti->save_state); + eti->save_row = -1; + eti->save_col = -1; + eti->save_state = NULL; +} + +/* + * During realization, we have to invoke the per-ecell realize routine + * (On our current setup, we have one e-cell per column. + * + * We might want to optimize this to only realize the unique e-cells: + * ie, a strings-only table, uses the same e-cell for every column, and + * we might want to avoid realizing each e-cell. + */ +static void +eti_realize_cell_views (ETableItem *eti) +{ + GnomeCanvasItem *item; + gint i; + + item = GNOME_CANVAS_ITEM (eti); + + if (eti->cell_views_realized) + return; + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) + return; + + for (i = 0; i < eti->n_cells; i++) + e_cell_realize (eti->cell_views[i]); + eti->cell_views_realized = 1; +} + +static void +eti_attach_cell_views (ETableItem *eti) +{ + gint i; + + g_return_if_fail (eti->header); + g_return_if_fail (eti->table_model); + + /* this is just c&p from model pre change, but it fixes things */ + eti_cancel_drag_due_to_model_change (eti); + eti_check_cursor_bounds (eti); + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + eti->motion_row = -1; + eti->motion_col = -1; + + /* + * Now realize the various ECells + */ + eti->n_cells = eti->cols; + eti->cell_views = g_new (ECellView *, eti->n_cells); + + for (i = 0; i < eti->n_cells; i++) { + ETableCol *ecol = e_table_header_get_column (eti->header, i); + + eti->cell_views[i] = e_cell_new_view (ecol->ecell, eti->table_model, eti); + } + + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +/* + * During unrealization: we invoke every e-cell (one per column in the current + * setup) to dispose all X resources allocated + */ +static void +eti_unrealize_cell_views (ETableItem *eti) +{ + gint i; + + if (eti->cell_views_realized == 0) + return; + + eti_free_save_state (eti); + + for (i = 0; i < eti->n_cells; i++) + e_cell_unrealize (eti->cell_views[i]); + eti->cell_views_realized = 0; +} + +static void +eti_detach_cell_views (ETableItem *eti) +{ + gint i; + + eti_free_save_state (eti); + + for (i = 0; i < eti->n_cells; i++) { + e_cell_kill_view (eti->cell_views[i]); + eti->cell_views[i] = NULL; + } + + g_free (eti->cell_views); + eti->cell_views = NULL; + eti->n_cells = 0; +} + +static void +eti_bounds (GnomeCanvasItem *item, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + cairo_matrix_t i2c; + ETableItem *eti = E_TABLE_ITEM (item); + + /* Wrong BBox's are the source of redraw nightmares */ + + *x1 = 0; + *y1 = 0; + *x2 = eti->width; + *y2 = eti->height; + + gnome_canvas_item_i2c_matrix (GNOME_CANVAS_ITEM (eti), &i2c); + gnome_canvas_matrix_transform_rect (&i2c, x1, y1, x2, y2); +} + +static void +eti_reflow (GnomeCanvasItem *item, + gint flags) +{ + ETableItem *eti = E_TABLE_ITEM (item); + + if (eti->needs_compute_height) { + gint new_height = eti_get_height (eti); + + if (new_height != eti->height) { + eti->height = new_height; + e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); + } + eti->needs_compute_height = 0; + } + if (eti->needs_compute_width) { + gint new_width = e_table_header_total_width (eti->header); + if (new_width != eti->width) { + eti->width = new_width; + e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); + } + eti->needs_compute_width = 0; + } +} + +/* + * GnomeCanvasItem::update method + */ +static void +eti_update (GnomeCanvasItem *item, + const cairo_matrix_t *i2c, + gint flags) +{ + ETableItem *eti = E_TABLE_ITEM (item); + gdouble x1, x2, y1, y2; + + if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update) + (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)(item, i2c, flags); + + x1 = item->x1; + y1 = item->y1; + x2 = item->x2; + y2 = item->y2; + + eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2); + if (item->x1 != x1 || + item->y1 != y1 || + item->x2 != x2 || + item->y2 != y2) { + gnome_canvas_request_redraw (item->canvas, x1, y1, x2, y2); + eti->needs_redraw = 1; + } + + if (eti->needs_redraw) { + gnome_canvas_request_redraw ( + item->canvas, item->x1, item->y1, + item->x2, item->y2); + eti->needs_redraw = 0; + } +} + +/* + * eti_remove_table_model: + * + * Invoked to release the table model associated with this ETableItem + */ +static void +eti_remove_table_model (ETableItem *eti) +{ + if (!eti->table_model) + return; + + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_pre_change_id); + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_no_change_id); + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_change_id); + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_row_change_id); + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_cell_change_id); + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_rows_inserted_id); + g_signal_handler_disconnect ( + eti->table_model, + eti->table_model_rows_deleted_id); + g_object_unref (eti->table_model); + if (eti->source_model) + g_object_unref (eti->source_model); + + eti->table_model_pre_change_id = 0; + eti->table_model_no_change_id = 0; + eti->table_model_change_id = 0; + eti->table_model_row_change_id = 0; + eti->table_model_cell_change_id = 0; + eti->table_model_rows_inserted_id = 0; + eti->table_model_rows_deleted_id = 0; + eti->table_model = NULL; + eti->source_model = NULL; + eti->uses_source_model = 0; +} + +/* + * eti_remove_table_model: + * + * Invoked to release the table model associated with this ETableItem + */ +static void +eti_remove_selection_model (ETableItem *eti) +{ + if (!eti->selection) + return; + + g_signal_handler_disconnect ( + eti->selection, + eti->selection_change_id); + g_signal_handler_disconnect ( + eti->selection, + eti->selection_row_change_id); + g_signal_handler_disconnect ( + eti->selection, + eti->cursor_change_id); + g_signal_handler_disconnect ( + eti->selection, + eti->cursor_activated_id); + g_object_unref (eti->selection); + + eti->selection_change_id = 0; + eti->selection_row_change_id = 0; + eti->cursor_activated_id = 0; + eti->selection = NULL; +} + +/* + * eti_remove_header_model: + * + * Invoked to release the header model associated with this ETableItem + */ +static void +eti_remove_header_model (ETableItem *eti) +{ + if (!eti->header) + return; + + g_signal_handler_disconnect ( + eti->header, + eti->header_structure_change_id); + g_signal_handler_disconnect ( + eti->header, + eti->header_dim_change_id); + g_signal_handler_disconnect ( + eti->header, + eti->header_request_width_id); + + if (eti->cell_views) { + eti_unrealize_cell_views (eti); + eti_detach_cell_views (eti); + } + g_object_unref (eti->header); + + eti->header_structure_change_id = 0; + eti->header_dim_change_id = 0; + eti->header_request_width_id = 0; + eti->header = NULL; +} + +/* + * eti_row_height_real: + * + * Returns the height used by row @row. This does not include the one-pixel + * used as a separator between rows + */ +static gint +eti_row_height_real (ETableItem *eti, + gint row) +{ + const gint cols = e_table_header_count (eti->header); + gint col; + gint h, max_h; + + g_return_val_if_fail (cols == 0 || eti->cell_views, 0); + + max_h = 0; + + for (col = 0; col < cols; col++) { + h = e_cell_height (eti->cell_views[col], view_to_model_col (eti, col), col, row); + + if (h > max_h) + max_h = h; + } + return max_h; +} + +static void +confirm_height_cache (ETableItem *eti) +{ + gint i; + + if (eti->uniform_row_height || eti->height_cache) + return; + eti->height_cache = g_new (int, eti->rows); + for (i = 0; i < eti->rows; i++) { + eti->height_cache[i] = -1; + } +} + +static gboolean +height_cache_idle (ETableItem *eti) +{ + gint changed = 0; + gint i; + confirm_height_cache (eti); + for (i = eti->height_cache_idle_count; i < eti->rows; i++) { + if (eti->height_cache[i] == -1) { + eti_row_height (eti, i); + changed++; + if (changed >= 20) + break; + } + } + if (changed >= 20) { + eti->height_cache_idle_count = i; + return TRUE; + } + eti->height_cache_idle_id = 0; + return FALSE; +} + +static void +free_height_cache (ETableItem *eti) +{ + GnomeCanvasItem *item; + + item = GNOME_CANVAS_ITEM (eti); + + if (item->flags & GNOME_CANVAS_ITEM_REALIZED) { + if (eti->height_cache) + g_free (eti->height_cache); + eti->height_cache = NULL; + eti->height_cache_idle_count = 0; + eti->uniform_row_height_cache = -1; + + if (eti->uniform_row_height && eti->height_cache_idle_id != 0) { + g_source_remove (eti->height_cache_idle_id); + eti->height_cache_idle_id = 0; + } + + if ((!eti->uniform_row_height) && eti->height_cache_idle_id == 0) + eti->height_cache_idle_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) height_cache_idle, eti, NULL); + } +} + +static void +calculate_height_cache (ETableItem *eti) +{ + free_height_cache (eti); + confirm_height_cache (eti); +} + +/* + * eti_row_height: + * + * Returns the height used by row @row. This does not include the one-pixel + * used as a separator between rows + */ +static gint +eti_row_height (ETableItem *eti, + gint row) +{ + if (eti->uniform_row_height) { + eti->uniform_row_height_cache = eti_row_height_real (eti, -1); + return eti->uniform_row_height_cache; + } else { + if (!eti->height_cache) { + calculate_height_cache (eti); + } + if (eti->height_cache[row] == -1) { + eti->height_cache[row] = eti_row_height_real (eti, row); + if (row > 0 && + eti->length_threshold != -1 && + eti->rows > eti->length_threshold && + eti->height_cache[row] != eti_row_height (eti, 0)) { + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + } + } + return eti->height_cache[row]; + } +} + +/* + * eti_get_height: + * + * Returns the height of the ETableItem. + * + * The ETableItem might compute the whole height by asking every row its + * size. There is a special mode (designed to work when there are too + * many rows in the table that performing the previous step could take + * too long) set by the ETableItem->length_threshold that would determine + * when the height is computed by using the first row as the size for + * every other row in the ETableItem. + */ +static gint +eti_get_height (ETableItem *eti) +{ + const gint rows = eti->rows; + gint height_extra = eti->horizontal_draw_grid ? 1 : 0; + + if (rows == 0) + return 0; + + if (eti->uniform_row_height) { + gint row_height = ETI_ROW_HEIGHT (eti, -1); + return ((row_height + height_extra) * rows + height_extra); + } else { + gint height; + gint row; + if (eti->length_threshold != -1) { + if (rows > eti->length_threshold) { + gint row_height = ETI_ROW_HEIGHT (eti, 0); + if (eti->height_cache) { + height = 0; + for (row = 0; row < rows; row++) { + if (eti->height_cache[row] == -1) { + height += (row_height + height_extra) * (rows - row); + break; + } + else + height += eti->height_cache[row] + height_extra; + } + } else + height = (ETI_ROW_HEIGHT (eti, 0) + height_extra) * rows; + + /* + * 1 pixel at the top + */ + return height + height_extra; + } + } + + height = height_extra; + for (row = 0; row < rows; row++) + height += ETI_ROW_HEIGHT (eti, row) + height_extra; + + return height; + } +} + +static void +eti_item_region_redraw (ETableItem *eti, + gint x0, + gint y0, + gint x1, + gint y1) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + gdouble dx1, dy1, dx2, dy2; + cairo_matrix_t i2c; + + dx1 = x0; + dy1 = y0; + dx2 = x1; + dy2 = y1; + + gnome_canvas_item_i2c_matrix (item, &i2c); + gnome_canvas_matrix_transform_rect (&i2c, &dx1, &dy1, &dx2, &dy2); + + gnome_canvas_request_redraw (item->canvas, floor (dx1), floor (dy1), ceil (dx2), ceil (dy2)); +} + +/* + * Computes the distance between @start_row and @end_row in pixels + */ +gint +e_table_item_row_diff (ETableItem *eti, + gint start_row, + gint end_row) +{ + gint height_extra = eti->horizontal_draw_grid ? 1 : 0; + + if (start_row < 0) + start_row = 0; + if (end_row > eti->rows) + end_row = eti->rows; + + if (eti->uniform_row_height) { + return ((end_row - start_row) * (ETI_ROW_HEIGHT (eti, -1) + height_extra)); + } else { + gint row, total; + total = 0; + for (row = start_row; row < end_row; row++) + total += ETI_ROW_HEIGHT (eti, row) + height_extra; + + return total; + } +} + +static void +eti_get_region (ETableItem *eti, + gint start_col, + gint start_row, + gint end_col, + gint end_row, + gint *x1p, + gint *y1p, + gint *x2p, + gint *y2p) +{ + gint x1, y1, x2, y2; + + x1 = e_table_header_col_diff (eti->header, 0, start_col); + y1 = e_table_item_row_diff (eti, 0, start_row); + x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1); + y2 = y1 + e_table_item_row_diff (eti, start_row, end_row + 1); + if (x1p) + *x1p = x1; + if (y1p) + *y1p = y1; + if (x2p) + *x2p = x2; + if (y2p) + *y2p = y2; +} + +/* + * eti_request_region_redraw: + * + * Request a canvas redraw on the range (start_col, start_row) to (end_col, end_row). + * This is inclusive (ie, you can use: 0,0-0,0 to redraw the first cell). + * + * The @border argument is a number of pixels around the region that should also be queued + * for redraw. This is typically used by the focus routines to queue a redraw for the + * border as well. + */ +static void +eti_request_region_redraw (ETableItem *eti, + gint start_col, + gint start_row, + gint end_col, + gint end_row, + gint border) +{ + gint x1, y1, x2, y2; + + if (eti->rows > 0) { + + eti_get_region ( + eti, + start_col, start_row, + end_col, end_row, + &x1, &y1, &x2, &y2); + + eti_item_region_redraw ( + eti, + x1 - border, + y1 - border, + x2 + 1 + border, + y2 + 1 + border); + } +} + +/* + * eti_request_region_show + * + * Request a canvas show on the range (start_col, start_row) to (end_col, end_row). + * This is inclusive (ie, you can use: 0,0-0,0 to show the first cell). + */ +static void +eti_request_region_show (ETableItem *eti, + gint start_col, + gint start_row, + gint end_col, + gint end_row, + gint delay) +{ + gint x1, y1, x2, y2; + + eti_get_region ( + eti, + start_col, start_row, + end_col, end_row, + &x1, &y1, &x2, &y2); + + if (delay) + e_canvas_item_show_area_delayed ( + GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2, delay); + else + e_canvas_item_show_area ( + GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2); +} + +static void +eti_show_cursor (ETableItem *eti, + gint delay) +{ + GnomeCanvasItem *item; + gint cursor_row; + + item = GNOME_CANVAS_ITEM (eti); + + if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized)) + return; + + if (eti->frozen_count > 0) { + eti->queue_show_cursor = TRUE; + return; + } + +#if 0 + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + NULL); +#else + cursor_row = e_selection_model_cursor_row (eti->selection); +#endif + + d (g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row)); + + if (cursor_row != -1) { + cursor_row = model_to_view_row (eti, cursor_row); + eti_request_region_show ( + eti, + 0, cursor_row, eti->cols - 1, cursor_row, + delay); + } +} + +static void +eti_check_cursor_bounds (ETableItem *eti) +{ + GnomeCanvasItem *item; + gint x1, y1, x2, y2; + gint cursor_row; + + item = GNOME_CANVAS_ITEM (eti); + + if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized)) + return; + + if (eti->frozen_count > 0) { + return; + } + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + NULL); + + if (cursor_row == -1) { + eti->cursor_x1 = -1; + eti->cursor_y1 = -1; + eti->cursor_x2 = -1; + eti->cursor_y2 = -1; + eti->cursor_on_screen = TRUE; + return; + } + + d (g_print ("%s: model cursor row: %d\n", __FUNCTION__, cursor_row)); + + cursor_row = model_to_view_row (eti, cursor_row); + + d (g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row)); + + eti_get_region ( + eti, + 0, cursor_row, eti->cols - 1, cursor_row, + &x1, &y1, &x2, &y2); + eti->cursor_x1 = x1; + eti->cursor_y1 = y1; + eti->cursor_x2 = x2; + eti->cursor_y2 = y2; + eti->cursor_on_screen = e_canvas_item_area_shown (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2); + + d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE")); +} + +static void +eti_maybe_show_cursor (ETableItem *eti, + gint delay) +{ + d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE")); + if (eti->cursor_on_screen) + eti_show_cursor (eti, delay); + eti_check_cursor_bounds (eti); +} + +static gboolean +eti_idle_show_cursor_cb (gpointer data) +{ + ETableItem *eti = data; + + if (eti->selection) { + eti_show_cursor (eti, 0); + eti_check_cursor_bounds (eti); + } + + eti->cursor_idle_id = 0; + g_object_unref (eti); + return FALSE; +} + +static void +eti_idle_maybe_show_cursor (ETableItem *eti) +{ + d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE")); + if (eti->cursor_on_screen) { + g_object_ref (eti); + if (!eti->cursor_idle_id) + eti->cursor_idle_id = g_idle_add (eti_idle_show_cursor_cb, eti); + } +} + +static void +eti_cancel_drag_due_to_model_change (ETableItem *eti) +{ + if (eti->maybe_in_drag) { + eti->maybe_in_drag = FALSE; + if (!eti->maybe_did_something) + e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state); + } + if (eti->in_drag) { + eti->in_drag = FALSE; + } +} + +static void +eti_freeze (ETableItem *eti) +{ + eti->frozen_count++; + d (g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count)); +} + +static void +eti_unfreeze (ETableItem *eti) +{ + if (eti->frozen_count <= 0) + return; + + eti->frozen_count--; + d (g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count)); + if (eti->frozen_count == 0 && eti->queue_show_cursor) { + eti_show_cursor (eti, 0); + eti_check_cursor_bounds (eti); + eti->queue_show_cursor = FALSE; + } +} + +/* + * Callback routine: invoked before the ETableModel suffers a change + */ +static void +eti_table_model_pre_change (ETableModel *table_model, + ETableItem *eti) +{ + eti_cancel_drag_due_to_model_change (eti); + eti_check_cursor_bounds (eti); + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + eti->motion_row = -1; + eti->motion_col = -1; + eti_freeze (eti); +} + +/* + * Callback routine: invoked when the ETableModel has not suffered a change + */ +static void +eti_table_model_no_change (ETableModel *table_model, + ETableItem *eti) +{ + eti_unfreeze (eti); +} + +/* + * Callback routine: invoked when the ETableModel has suffered a change + */ + +static void +eti_table_model_changed (ETableModel *table_model, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) { + eti_unfreeze (eti); + return; + } + + eti->rows = e_table_model_row_count (eti->table_model); + + free_height_cache (eti); + + eti_unfreeze (eti); + + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); + + eti_idle_maybe_show_cursor (eti); +} + +static void +eti_table_model_row_changed (ETableModel *table_model, + gint row, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) { + eti_unfreeze (eti); + return; + } + + if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) { + eti_table_model_changed (table_model, eti); + return; + } + + eti_unfreeze (eti); + + e_table_item_redraw_row (eti, row); +} + +static void +eti_table_model_cell_changed (ETableModel *table_model, + gint col, + gint row, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) { + eti_unfreeze (eti); + return; + } + + if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) { + eti_table_model_changed (table_model, eti); + return; + } + + eti_unfreeze (eti); + + e_table_item_redraw_row (eti, row); +} + +static void +eti_table_model_rows_inserted (ETableModel *table_model, + gint row, + gint count, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) { + eti_unfreeze (eti); + return; + } + eti->rows = e_table_model_row_count (eti->table_model); + + if (eti->height_cache) { + gint i; + eti->height_cache = g_renew (int, eti->height_cache, eti->rows); + memmove (eti->height_cache + row + count, eti->height_cache + row, (eti->rows - count - row) * sizeof (gint)); + for (i = row; i < row + count; i++) + eti->height_cache[i] = -1; + } + + eti_unfreeze (eti); + + eti_idle_maybe_show_cursor (eti); + + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +static void +eti_table_model_rows_deleted (ETableModel *table_model, + gint row, + gint count, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) { + eti_unfreeze (eti); + return; + } + + eti->rows = e_table_model_row_count (eti->table_model); + + if (eti->height_cache && (eti->rows > row)) { + memmove (eti->height_cache + row, eti->height_cache + row + count, (eti->rows - row) * sizeof (gint)); + } + + eti_unfreeze (eti); + + eti_idle_maybe_show_cursor (eti); + + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +/** + * e_table_item_redraw_range + * @eti: %ETableItem which will be redrawn + * @start_col: The first col to redraw. + * @start_row: The first row to redraw. + * @end_col: The last col to redraw. + * @end_row: The last row to redraw. + * + * This routine redraws the given %ETableItem in the range given. The + * range is inclusive at both ends. + */ +void +e_table_item_redraw_range (ETableItem *eti, + gint start_col, + gint start_row, + gint end_col, + gint end_row) +{ + gint border; + gint cursor_col, cursor_row; + + g_return_if_fail (eti != NULL); + g_return_if_fail (E_IS_TABLE_ITEM (eti)); + + g_object_get ( + eti->selection, + "cursor_col", &cursor_col, + "cursor_row", &cursor_row, + NULL); + + if ((start_col == cursor_col) || + (end_col == cursor_col) || + (view_to_model_row (eti, start_row) == cursor_row) || + (view_to_model_row (eti, end_row) == cursor_row)) + border = 2; + else + border = 0; + + eti_request_region_redraw (eti, start_col, start_row, end_col, end_row, border); +} + +static void +e_table_item_redraw_row (ETableItem *eti, + gint row) +{ + if (row != -1) + e_table_item_redraw_range (eti, 0, row, eti->cols - 1, row); +} + +static void +eti_add_table_model (ETableItem *eti, + ETableModel *table_model) +{ + g_return_if_fail (eti->table_model == NULL); + + eti->table_model = table_model; + g_object_ref (eti->table_model); + + eti->table_model_pre_change_id = g_signal_connect ( + table_model, "model_pre_change", + G_CALLBACK (eti_table_model_pre_change), eti); + + eti->table_model_no_change_id = g_signal_connect ( + table_model, "model_no_change", + G_CALLBACK (eti_table_model_no_change), eti); + + eti->table_model_change_id = g_signal_connect ( + table_model, "model_changed", + G_CALLBACK (eti_table_model_changed), eti); + + eti->table_model_row_change_id = g_signal_connect ( + table_model, "model_row_changed", + G_CALLBACK (eti_table_model_row_changed), eti); + + eti->table_model_cell_change_id = g_signal_connect ( + table_model, "model_cell_changed", + G_CALLBACK (eti_table_model_cell_changed), eti); + + eti->table_model_rows_inserted_id = g_signal_connect ( + table_model, "model_rows_inserted", + G_CALLBACK (eti_table_model_rows_inserted), eti); + + eti->table_model_rows_deleted_id = g_signal_connect ( + table_model, "model_rows_deleted", + G_CALLBACK (eti_table_model_rows_deleted), eti); + + if (eti->header) { + eti_detach_cell_views (eti); + eti_attach_cell_views (eti); + } + + if (E_IS_TABLE_SUBSET (table_model)) { + eti->uses_source_model = 1; + eti->source_model = E_TABLE_SUBSET (table_model)->source; + if (eti->source_model) + g_object_ref (eti->source_model); + } + + eti_freeze (eti); + + eti_table_model_changed (table_model, eti); +} + +static void +eti_add_selection_model (ETableItem *eti, + ESelectionModel *selection) +{ + g_return_if_fail (eti->selection == NULL); + + eti->selection = selection; + g_object_ref (eti->selection); + + eti->selection_change_id = g_signal_connect ( + selection, "selection_changed", + G_CALLBACK (eti_selection_change), eti); + + eti->selection_row_change_id = g_signal_connect ( + selection, "selection_row_changed", + G_CALLBACK (eti_selection_row_change), eti); + + eti->cursor_change_id = g_signal_connect ( + selection, "cursor_changed", + G_CALLBACK (eti_cursor_change), eti); + + eti->cursor_activated_id = g_signal_connect ( + selection, "cursor_activated", + G_CALLBACK (eti_cursor_activated), eti); + + eti_selection_change (selection, eti); + g_signal_emit_by_name (eti, "selection_model_added", eti->selection); +} + +static void +eti_header_dim_changed (ETableHeader *eth, + gint col, + ETableItem *eti) +{ + eti->needs_compute_width = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +static void +eti_header_structure_changed (ETableHeader *eth, + ETableItem *eti) +{ + eti->cols = e_table_header_count (eti->header); + + /* + * There should be at least one column + * BUT: then you can't remove all columns from a header and add new ones. + */ + + if (eti->cell_views) { + eti_unrealize_cell_views (eti); + eti_detach_cell_views (eti); + eti_attach_cell_views (eti); + eti_realize_cell_views (eti); + } else { + if (eti->table_model) { + eti_attach_cell_views (eti); + eti_realize_cell_views (eti); + } + } + eti->needs_compute_width = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +static gint +eti_request_column_width (ETableHeader *eth, + gint col, + ETableItem *eti) +{ + gint width = 0; + + if (eti->cell_views && eti->cell_views_realized) { + width = e_cell_max_width (eti->cell_views[col], view_to_model_col (eti, col), col); + } + + return width; +} + +static void +eti_add_header_model (ETableItem *eti, + ETableHeader *header) +{ + g_return_if_fail (eti->header == NULL); + + eti->header = header; + g_object_ref (header); + + eti_header_structure_changed (header, eti); + + eti->header_dim_change_id = g_signal_connect ( + header, "dimension_change", + G_CALLBACK (eti_header_dim_changed), eti); + + eti->header_structure_change_id = g_signal_connect ( + header, "structure_change", + G_CALLBACK (eti_header_structure_changed), eti); + + eti->header_request_width_id = g_signal_connect ( + header, "request_width", + G_CALLBACK (eti_request_column_width), eti); +} + +/* + * GObject::dispose method + */ +static void +eti_dispose (GObject *object) +{ + ETableItem *eti = E_TABLE_ITEM (object); + + eti_remove_header_model (eti); + eti_remove_table_model (eti); + eti_remove_selection_model (eti); + + if (eti->height_cache_idle_id) { + g_source_remove (eti->height_cache_idle_id); + eti->height_cache_idle_id = 0; + } + eti->height_cache_idle_count = 0; + + if (eti->cursor_idle_id) { + g_source_remove (eti->cursor_idle_id); + eti->cursor_idle_id = 0; + } + + if (eti->height_cache) + g_free (eti->height_cache); + eti->height_cache = NULL; + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (eti_parent_class)->dispose (object); +} + +static void +eti_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (object); + ETableItem *eti = E_TABLE_ITEM (object); + gint cursor_col; + + switch (property_id) { + case PROP_TABLE_HEADER: + eti_remove_header_model (eti); + eti_add_header_model (eti, E_TABLE_HEADER (g_value_get_object (value))); + break; + + case PROP_TABLE_MODEL: + eti_remove_table_model (eti); + eti_add_table_model (eti, E_TABLE_MODEL (g_value_get_object (value))); + break; + + case PROP_SELECTION_MODEL: + g_signal_emit_by_name ( + eti, "selection_model_removed", eti->selection); + eti_remove_selection_model (eti); + if (g_value_get_object (value)) + eti_add_selection_model (eti, E_SELECTION_MODEL (g_value_get_object (value))); + break; + + case PROP_LENGTH_THRESHOLD: + eti->length_threshold = g_value_get_int (value); + break; + + case PROP_TABLE_ALTERNATING_ROW_COLORS: + eti->alternating_row_colors = g_value_get_boolean (value); + break; + + case PROP_TABLE_HORIZONTAL_DRAW_GRID: + eti->horizontal_draw_grid = g_value_get_boolean (value); + break; + + case PROP_TABLE_VERTICAL_DRAW_GRID: + eti->vertical_draw_grid = g_value_get_boolean (value); + break; + + case PROP_TABLE_DRAW_FOCUS: + eti->draw_focus = g_value_get_boolean (value); + break; + + case PROP_CURSOR_MODE: + eti->cursor_mode = g_value_get_int (value); + break; + + case PROP_MINIMUM_WIDTH: + case PROP_WIDTH: + if ((eti->minimum_width == eti->width && g_value_get_double (value) > eti->width) || + g_value_get_double (value) < eti->width) { + eti->needs_compute_width = 1; + e_canvas_item_request_reflow (item); + } + eti->minimum_width = g_value_get_double (value); + break; + case PROP_CURSOR_ROW: + g_object_get ( + eti->selection, + "cursor_col", &cursor_col, + NULL); + + e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row (eti, g_value_get_int (value)), 0); + break; + case PROP_UNIFORM_ROW_HEIGHT: + if (eti->uniform_row_height != g_value_get_boolean (value)) { + eti->uniform_row_height = g_value_get_boolean (value); + if (item->flags & GNOME_CANVAS_ITEM_REALIZED) { + free_height_cache (eti); + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (item); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (item); + } + } + break; + } + eti->needs_redraw = 1; + gnome_canvas_item_request_update (item); +} + +static void +eti_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ETableItem *eti; + gint row; + + eti = E_TABLE_ITEM (object); + + switch (property_id) { + case PROP_WIDTH: + g_value_set_double (value, eti->width); + break; + case PROP_HEIGHT: + g_value_set_double (value, eti->height); + break; + case PROP_MINIMUM_WIDTH: + g_value_set_double (value, eti->minimum_width); + break; + case PROP_CURSOR_ROW: + g_object_get ( + eti->selection, + "cursor_row", &row, + NULL); + g_value_set_int (value, model_to_view_row (eti, row)); + break; + case PROP_UNIFORM_ROW_HEIGHT: + g_value_set_boolean (value, eti->uniform_row_height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +eti_init (ETableItem *eti) +{ + eti->motion_row = -1; + eti->motion_col = -1; + eti->editing_col = -1; + eti->editing_row = -1; + eti->height = 0; + eti->width = 0; + eti->minimum_width = 0; + + eti->save_col = -1; + eti->save_row = -1; + eti->save_state = NULL; + + eti->click_count = 0; + + eti->height_cache = NULL; + eti->height_cache_idle_id = 0; + eti->height_cache_idle_count = 0; + + eti->length_threshold = -1; + eti->uniform_row_height = FALSE; + + eti->uses_source_model = 0; + eti->source_model = NULL; + + eti->row_guess = -1; + eti->cursor_mode = E_CURSOR_SIMPLE; + + eti->selection_change_id = 0; + eti->selection_row_change_id = 0; + eti->cursor_change_id = 0; + eti->cursor_activated_id = 0; + eti->selection = NULL; + + eti->old_cursor_row = -1; + + eti->needs_redraw = 0; + eti->needs_compute_height = 0; + + eti->in_key_press = 0; + + eti->maybe_did_something = TRUE; + + eti->grabbed_count = 0; + eti->gtk_grabbed = 0; + + eti->in_drag = 0; + eti->maybe_in_drag = 0; + eti->grabbed = 0; + + eti->grabbed_col = -1; + eti->grabbed_row = -1; + + eti->cursor_on_screen = FALSE; + eti->cursor_x1 = -1; + eti->cursor_y1 = -1; + eti->cursor_x2 = -1; + eti->cursor_y2 = -1; + + eti->rows = -1; + eti->cols = -1; + + eti->frozen_count = 0; + eti->queue_show_cursor = FALSE; + + e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow); +} + +#define gray50_width 2 +#define gray50_height 2 +static const gchar gray50_bits[] = { + 0x02, 0x01, }; + +static gboolean +eti_tree_unfreeze (GtkWidget *widget, + GdkEvent *event, + ETableItem *eti) +{ + + if (widget) + g_object_set_data (G_OBJECT (widget), "freeze-cursor", NULL); + + return FALSE; +} + +static void +eti_realize (GnomeCanvasItem *item) +{ + ETableItem *eti = E_TABLE_ITEM (item); + + if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize) + (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)(item); + + eti->rows = e_table_model_row_count (eti->table_model); + + g_signal_connect ( + item->canvas, "scroll_event", + G_CALLBACK (eti_tree_unfreeze), eti); + + if (eti->cell_views == NULL) + eti_attach_cell_views (eti); + + eti_realize_cell_views (eti); + + free_height_cache (eti); + + if (item->canvas->focused_item == NULL && eti->selection) { + gint row; + + row = e_selection_model_cursor_row (E_SELECTION_MODEL (eti->selection)); + row = model_to_view_row (eti, row); + if (row != -1) { + e_canvas_item_grab_focus (item, FALSE); + eti_show_cursor (eti, 0); + eti_check_cursor_bounds (eti); + } + } + + eti->needs_compute_height = 1; + eti->needs_compute_width = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +static void +eti_unrealize (GnomeCanvasItem *item) +{ + ETableItem *eti = E_TABLE_ITEM (item); + + if (eti->grabbed_count > 0) { + d (g_print ("%s: eti_ungrab\n", __FUNCTION__)); + eti_ungrab (eti, -1); + } + + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + + if (eti->height_cache_idle_id) { + g_source_remove (eti->height_cache_idle_id); + eti->height_cache_idle_id = 0; + } + + if (eti->height_cache) + g_free (eti->height_cache); + eti->height_cache = NULL; + eti->height_cache_idle_count = 0; + + eti_unrealize_cell_views (eti); + + eti->height = 0; + + if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->unrealize) + (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->unrealize)(item); +} + +static void +eti_draw_grid_line (ETableItem *eti, + cairo_t *cr, + GtkStyle *style, + gint x1, + gint y1, + gint x2, + gint y2) +{ + cairo_save (cr); + + cairo_set_line_width (cr, 1.0); + gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]); + + cairo_move_to (cr, x1 + 0.5, y1 + 0.5); + cairo_line_to (cr, x2 + 0.5, y2 + 0.5); + cairo_stroke (cr); + + cairo_restore (cr); +} + +static void +eti_draw (GnomeCanvasItem *item, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height) +{ + ETableItem *eti = E_TABLE_ITEM (item); + const gint rows = eti->rows; + const gint cols = eti->cols; + gint row, col; + gint first_col, last_col, x_offset; + gint first_row, last_row, y_offset, yd; + gint x1, x2; + gint f_x1, f_x2, f_y1, f_y2; + gboolean f_found; + cairo_matrix_t i2c; + gdouble eti_base_x, eti_base_y, lower_right_y, lower_right_x; + GtkWidget *canvas = GTK_WIDGET (item->canvas); + GtkStyle *style = gtk_widget_get_style (canvas); + gint height_extra = eti->horizontal_draw_grid ? 1 : 0; + + /* + * Find out our real position after grouping + */ + gnome_canvas_item_i2c_matrix (item, &i2c); + eti_base_x = 0; + eti_base_y = 0; + cairo_matrix_transform_point (&i2c, &eti_base_x, &eti_base_y); + + lower_right_x = eti->width; + lower_right_y = eti->height; + cairo_matrix_transform_point (&i2c, &lower_right_x, &lower_right_y); + + /* + * First column to draw, last column to draw + */ + first_col = -1; + x_offset = 0; + x1 = floor (eti_base_x); + for (col = 0; col < cols; col++, x1 = x2) { + ETableCol *ecol = e_table_header_get_column (eti->header, col); + + x2 = x1 + ecol->width; + + if (x1 > (x + width)) + break; + if (x2 < x) + continue; + if (first_col == -1) { + x_offset = x1 - x; + first_col = col; + } + } + last_col = col; + + /* + * Nothing to paint + */ + if (first_col == -1) + return; + + /* + * Compute row span. + */ + if (eti->uniform_row_height) { + first_row = (y - floor (eti_base_y) - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra); + last_row = (y + height - floor (eti_base_y) ) / (ETI_ROW_HEIGHT (eti, -1) + height_extra) + 1; + if (first_row > last_row) + return; + y_offset = floor (eti_base_y) - y + height_extra + first_row * (ETI_ROW_HEIGHT (eti, -1) + height_extra); + if (first_row < 0) + first_row = 0; + if (last_row > eti->rows) + last_row = eti->rows; + } else { + gint y1, y2; + + y_offset = 0; + first_row = -1; + + y1 = y2 = floor (eti_base_y) + height_extra; + for (row = 0; row < rows; row++, y1 = y2) { + + y2 += ETI_ROW_HEIGHT (eti, row) + height_extra; + + if (y1 > y + height) + break; + + if (y2 < y) + continue; + + if (first_row == -1) { + y_offset = y1 - y; + first_row = row; + } + } + last_row = row; + + if (first_row == -1) + return; + } + + if (first_row == -1) + return; + + /* + * Draw cells + */ + yd = y_offset; + f_x1 = f_x2 = f_y1 = f_y2 = -1; + f_found = FALSE; + + if (eti->horizontal_draw_grid && first_row == 0) + eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd); + + yd += height_extra; + + for (row = first_row; row < last_row; row++) { + gint xd; + gboolean selected; + gint cursor_col, cursor_row; + + height = ETI_ROW_HEIGHT (eti, row); + + xd = x_offset; + + selected = e_selection_model_is_row_selected (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti,row)); + + g_object_get ( + eti->selection, + "cursor_col", &cursor_col, + "cursor_row", &cursor_row, + NULL); + + for (col = first_col; col < last_col; col++) { + ETableCol *ecol = e_table_header_get_column (eti->header, col); + ECellView *ecell_view = eti->cell_views[col]; + gboolean col_selected = selected; + gboolean cursor = FALSE; + ECellFlags flags; + gboolean free_background; + GdkColor *background; + gint x1, x2, y1, y2; + cairo_pattern_t *pat; + + switch (eti->cursor_mode) { + case E_CURSOR_SIMPLE: + case E_CURSOR_SPREADSHEET: + if (cursor_col == ecol->col_idx && cursor_row == view_to_model_row (eti, row)) { + col_selected = !col_selected; + cursor = TRUE; + } + break; + case E_CURSOR_LINE: + /* Nothing */ + break; + } + + x1 = xd; + y1 = yd + 1; + x2 = x1 + ecol->width; + y2 = yd + height; + + background = eti_get_cell_background_color (eti, row, col, col_selected, &free_background); + + cairo_save (cr); + pat = cairo_pattern_create_linear (0, y1, 0, y2); + cairo_pattern_add_color_stop_rgba ( + pat, 0.0, background->red / 65535.0 , + background->green / 65535.0, + background->blue / 65535.0, selected ? 0.8: 1.0); + if (selected) + cairo_pattern_add_color_stop_rgba ( + pat, 0.5, background->red / 65535.0 , + background->green / 65535.0, + background->blue / 65535.0, 0.9); + + cairo_pattern_add_color_stop_rgba ( + pat, 1, background->red / 65535.0 , + background->green / 65535.0, + background->blue / 65535.0, selected ? 0.8 : 1.0); + cairo_rectangle (cr, x1, y1, ecol->width, height - 1); + cairo_set_source (cr, pat); + cairo_fill_preserve (cr); + cairo_pattern_destroy (pat); + cairo_set_line_width (cr, 0); + cairo_stroke (cr); + cairo_restore (cr); + + cairo_save (cr); + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba ( + cr, background->red / 65535.0 , + background->green / 65535.0, + background->blue / 65535.0, 1); + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x2, y1); + cairo_stroke (cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba ( + cr, background->red / 65535.0 , + background->green / 65535.0, + background->blue / 65535.0, 1); + cairo_move_to (cr, x1, y2); + cairo_line_to (cr, x2, y2); + cairo_stroke (cr); + cairo_restore (cr); + + if (free_background) + gdk_color_free (background); + + flags = col_selected ? E_CELL_SELECTED : 0; + flags |= gtk_widget_has_focus (canvas) ? E_CELL_FOCUSED : 0; + flags |= cursor ? E_CELL_CURSOR : 0; + + switch (ecol->justification) { + case GTK_JUSTIFY_LEFT: + flags |= E_CELL_JUSTIFY_LEFT; + break; + case GTK_JUSTIFY_RIGHT: + flags |= E_CELL_JUSTIFY_RIGHT; + break; + case GTK_JUSTIFY_CENTER: + flags |= E_CELL_JUSTIFY_CENTER; + break; + case GTK_JUSTIFY_FILL: + flags |= E_CELL_JUSTIFY_FILL; + break; + } + + e_cell_draw ( + ecell_view, cr, ecol->col_idx, col, row, flags, + xd, yd, xd + ecol->width, yd + height); + + if (!f_found && !selected) { + switch (eti->cursor_mode) { + case E_CURSOR_LINE: + if (view_to_model_row (eti, row) == cursor_row) { + f_x1 = floor (eti_base_x) - x; + f_x2 = floor (lower_right_x) - x; + f_y1 = yd + 1; + f_y2 = yd + height; + f_found = TRUE; + } + break; + case E_CURSOR_SIMPLE: + case E_CURSOR_SPREADSHEET: + if (view_to_model_col (eti, col) == cursor_col && view_to_model_row (eti, row) == cursor_row) { + f_x1 = xd; + f_x2 = xd + ecol->width; + f_y1 = yd; + f_y2 = yd + height; + f_found = TRUE; + } + break; + } + } + + xd += ecol->width; + } + yd += height; + + if (eti->horizontal_draw_grid) { + eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd); + yd++; + } + } + + if (eti->vertical_draw_grid) { + gint xd = x_offset; + + for (col = first_col; col <= last_col; col++) { + ETableCol *ecol = e_table_header_get_column (eti->header, col); + + eti_draw_grid_line (eti, cr, style, xd, y_offset, xd, yd - 1); + + /* + * This looks wierd, but it is to draw the last line + */ + if (ecol) + xd += ecol->width; + } + } + + /* + * Draw focus + */ + if (eti->draw_focus && f_found) { + static const double dash[] = { 1.0, 1.0 }; + cairo_set_line_width (cr, 1.0); + cairo_rectangle ( + cr, + f_x1 + 0.5, f_x2 + 0.5, + f_x2 - f_x1 - 1, f_y2 - f_y1); + + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); + cairo_stroke_preserve (cr); + + cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0.0); + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); + cairo_stroke (cr); + } +} + +static GnomeCanvasItem * +eti_point (GnomeCanvasItem *item, + gdouble x, + gdouble y, + gint cx, + gint cy) +{ + return item; +} + +static gboolean +find_cell (ETableItem *eti, + gdouble x, + gdouble y, + gint *view_col_res, + gint *view_row_res, + gdouble *x1_res, + gdouble *y1_res) +{ + const gint cols = eti->cols; + const gint rows = eti->rows; + gdouble x1, y1, x2, y2; + gint col, row; + + gint height_extra = eti->horizontal_draw_grid ? 1 : 0; + + /* FIXME: this routine is inneficient, fix later */ + + if (eti->grabbed_col >= 0 && eti->grabbed_row >= 0) { + *view_col_res = eti->grabbed_col; + *view_row_res = eti->grabbed_row; + *x1_res = x - e_table_header_col_diff (eti->header, 0, eti->grabbed_col); + *y1_res = y - e_table_item_row_diff (eti, 0, eti->grabbed_row); + return TRUE; + } + + if (cols == 0 || rows == 0) + return FALSE; + + x1 = 0; + for (col = 0; col < cols - 1; col++, x1 = x2) { + ETableCol *ecol = e_table_header_get_column (eti->header, col); + + if (x < x1) + return FALSE; + + x2 = x1 + ecol->width; + + if (x <= x2) + break; + } + + if (eti->uniform_row_height) { + if (y < height_extra) + return FALSE; + row = (y - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra); + y1 = row * (ETI_ROW_HEIGHT (eti, -1) + height_extra) + height_extra; + if (row >= eti->rows) + return FALSE; + } else { + y1 = y2 = height_extra; + if (y < height_extra) + return FALSE; + for (row = 0; row < rows; row++, y1 = y2) { + y2 += ETI_ROW_HEIGHT (eti, row) + height_extra; + + if (y <= y2) + break; + } + + if (row == rows) + return FALSE; + } + *view_col_res = col; + if (x1_res) + *x1_res = x - x1; + *view_row_res = row; + if (y1_res) + *y1_res = y - y1; + return TRUE; +} + +static void +eti_cursor_move (ETableItem *eti, + gint row, + gint column) +{ + e_table_item_leave_edit_(eti); + e_table_item_focus (eti, view_to_model_col (eti, column), view_to_model_row (eti, row), 0); +} + +static void +eti_cursor_move_left (ETableItem *eti) +{ + gint cursor_col, cursor_row; + g_object_get ( + eti->selection, + "cursor_col", &cursor_col, + "cursor_row", &cursor_row, + NULL); + + eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) - 1); +} + +static void +eti_cursor_move_right (ETableItem *eti) +{ + gint cursor_col, cursor_row; + g_object_get ( + eti->selection, + "cursor_col", &cursor_col, + "cursor_row", &cursor_row, + NULL); + + eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) + 1); +} + +static gint +eti_e_cell_event (ETableItem *item, + ECellView *ecell_view, + GdkEvent *event, + gint model_col, + gint view_col, + gint row, + ECellFlags flags) +{ + ECellActions actions = 0; + gint ret_val; + + ret_val = e_cell_event ( + ecell_view, event, model_col, view_col, row, flags, &actions); + + if (actions & E_CELL_GRAB) { + GdkDevice *event_device; + guint32 event_time; + + d (g_print ("%s: eti_grab\n", __FUNCTION__)); + + event_device = gdk_event_get_device (event); + event_time = gdk_event_get_time (event); + eti_grab (item, event_device, event_time); + + item->grabbed_col = view_col; + item->grabbed_row = row; + } + + if (actions & E_CELL_UNGRAB) { + guint32 event_time; + + d (g_print ("%s: eti_ungrab\n", __FUNCTION__)); + + event_time = gdk_event_get_time (event); + eti_ungrab (item, event_time); + + item->grabbed_col = -1; + item->grabbed_row = -1; + } + + return ret_val; +} + +/* FIXME: cursor */ +static gint +eti_event (GnomeCanvasItem *item, + GdkEvent *event) +{ + ETableItem *eti = E_TABLE_ITEM (item); + ECellView *ecell_view; + GdkModifierType event_state = 0; + GdkEvent *event_copy; + guint event_button = 0; + guint event_keyval = 0; + gdouble event_x_item = 0; + gdouble event_y_item = 0; + gdouble event_x_win = 0; + gdouble event_y_win = 0; + guint32 event_time; + gboolean return_val = TRUE; +#if d(!)0 + gboolean leave = FALSE; +#endif + + if (!eti->header) + return FALSE; + + /* Don't fetch the device here. GnomeCanvas frequently emits + * synthesized events, and calling gdk_event_get_device() on them + * will trigger a runtime warning. Fetch the device where needed. */ + gdk_event_get_button (event, &event_button); + gdk_event_get_coords (event, &event_x_win, &event_y_win); + gdk_event_get_keyval (event, &event_keyval); + gdk_event_get_state (event, &event_state); + event_time = gdk_event_get_time (event); + + switch (event->type) { + case GDK_BUTTON_PRESS: { + gdouble x1, y1; + gint col, row; + gint cursor_row, cursor_col; + gint new_cursor_row, new_cursor_col; + ECellFlags flags = 0; + + d (g_print ("%s: GDK_BUTTON_PRESS received, button %d\n", __FUNCTION__, event_button)); + + switch (event_button) { + case 1: /* Fall through. */ + case 2: + e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE); + + event_x_item = event_x_win; + event_y_item = event_y_win; + + gnome_canvas_item_w2i ( + item, &event_x_item, &event_y_item); + + if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) { + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + return TRUE; + } + + ecell_view = eti->cell_views[col]; + + /* Clone the event and alter its position. */ + event_copy = gdk_event_copy (event); + event_copy->button.x = x1; + event_copy->button.y = y1; + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + if (cursor_col == view_to_model_col (eti, col) && cursor_row == view_to_model_row (eti, row)) { + flags = E_CELL_CURSOR; + } else { + flags = 0; + } + + return_val = eti_e_cell_event ( + eti, ecell_view, event_copy, + view_to_model_col (eti, col), + col, row, flags); + if (return_val) { + gdk_event_free (event_copy); + return TRUE; + } + + g_signal_emit ( + eti, eti_signals[CLICK], 0, + row, view_to_model_col (eti, col), + event_copy, &return_val); + + gdk_event_free (event_copy); + + if (return_val) { + eti->click_count = 0; + return TRUE; + } + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + eti->maybe_did_something = + e_selection_model_maybe_do_something ( + E_SELECTION_MODEL (eti->selection), + view_to_model_row (eti, row), + view_to_model_col (eti, col), + event_state); + g_object_get ( + eti->selection, + "cursor_row", &new_cursor_row, + "cursor_col", &new_cursor_col, + NULL); + + if (cursor_row != new_cursor_row || cursor_col != new_cursor_col) { + eti->click_count = 1; + } else { + eti->click_count++; + eti->row_guess = row; + + if ((!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) { + e_table_item_enter_edit (eti, col, row); + } + + /* + * Adjust the event positions + */ + + if (eti_editing (eti)) { + return_val = eti_e_cell_event ( + eti, ecell_view, event, + view_to_model_col (eti, col), + col, row, + E_CELL_EDITING | + E_CELL_CURSOR); + if (return_val) + return TRUE; + } + } + + if (event_button == 1) { + GdkDevice *event_device; + + return_val = TRUE; + + event_device = gdk_event_get_device (event); + + eti->maybe_in_drag = TRUE; + eti->drag_row = new_cursor_row; + eti->drag_col = new_cursor_col; + eti->drag_x = event_x_item; + eti->drag_y = event_y_item; + eti->drag_state = event_state; + eti->grabbed = TRUE; + d (g_print ("%s: eti_grab\n", __FUNCTION__)); + eti_grab (eti, event_device, event_time); + } + + break; + case 3: + e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE); + + event_x_item = event_x_win; + event_y_item = event_y_win; + + gnome_canvas_item_w2i ( + item, &event_x_item, &event_y_item); + + if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) + return TRUE; + + e_selection_model_right_click_down ( + E_SELECTION_MODEL (eti->selection), + view_to_model_row (eti, row), + view_to_model_col (eti, col), 0); + + /* Clone the event and alter its position. */ + event_copy = gdk_event_copy (event); + event_copy->button.x = event_x_item; + event_copy->button.y = event_y_item; + + g_signal_emit ( + eti, eti_signals[RIGHT_CLICK], 0, + row, view_to_model_col (eti, col), + event, &return_val); + + gdk_event_free (event_copy); + + if (!return_val) + e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection)); + break; + case 4: + case 5: + return FALSE; + + } + break; + } + + case GDK_BUTTON_RELEASE: { + gdouble x1, y1; + gint col, row; + gint cursor_row, cursor_col; + + d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d\n", __FUNCTION__, event_button)); + + if (eti->grabbed_count > 0) { + d (g_print ("%s: eti_ungrab\n", __FUNCTION__)); + eti_ungrab (eti, event_time); + } + + if (event_button == 1) { + if (eti->maybe_in_drag) { + eti->maybe_in_drag = FALSE; + if (!eti->maybe_did_something) + e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state); + } + if (eti->in_drag) { + eti->in_drag = FALSE; + } + } + + switch (event_button) { + case 1: /* Fall through. */ + case 2: + + event_x_item = event_x_win; + event_y_item = event_y_win; + + gnome_canvas_item_w2i ( + item, &event_x_item, &event_y_item); +#if d(!)0 + { + gboolean cell_found = find_cell ( + eti, event_x_item, event_y_item, + &col, &row, &x1, &y1); + g_print ( + "%s: find_cell(%f, %f) = %s(%d, %d, %f, %f)\n", + __FUNCTION__, event_x_item, event_y_item, + cell_found?"true":"false", col, row, x1, y1); + } +#endif + + if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) + return TRUE; + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + if (eti_editing (eti) && cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) { + + d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d, line: %d\n", __FUNCTION__, event_button, __LINE__)) +; + + ecell_view = eti->cell_views[col]; + + /* Clone the event and alter its position. */ + event_copy = gdk_event_copy (event); + event_copy->button.x = x1; + event_copy->button.y = y1; + + return_val = eti_e_cell_event ( + eti, ecell_view, event_copy, + view_to_model_col (eti, col), + col, row, + E_CELL_EDITING | + E_CELL_CURSOR); + + gdk_event_free (event_copy); + } + break; + case 3: + e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection)); + return_val = TRUE; + break; + case 4: + case 5: + return FALSE; + + } + break; + } + + case GDK_2BUTTON_PRESS: { + gint model_col, model_row; +#if 0 + gdouble x1, y1; +#endif + + d (g_print ("%s: GDK_2BUTTON_PRESS received, button %d\n", __FUNCTION__, event_button)); + + /* + * click_count is so that if you click on two + * different rows we don't send a double click signal. + */ + + if (eti->click_count >= 2) { + + event_x_item = event_x_win; + event_y_item = event_y_win; + + gnome_canvas_item_w2i ( + item, &event_x_item, &event_y_item); + + g_object_get ( + eti->selection, + "cursor_row", &model_row, + "cursor_col", &model_col, + NULL); + + /* Clone the event and alter its position. */ + event_copy = gdk_event_copy (event); + event_copy->button.x = event_x_item - + e_table_header_col_diff ( + eti->header, 0, + model_to_view_col (eti, model_col)); + event_copy->button.y = event_y_item - + e_table_item_row_diff ( + eti, 0, + model_to_view_row (eti, model_row)); + + if (event_button == 1) { + if (eti->maybe_in_drag) { + eti->maybe_in_drag = FALSE; + if (!eti->maybe_did_something) + e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state); + } + if (eti->in_drag) { + eti->in_drag = FALSE; + } + if (eti_editing (eti)) + e_table_item_leave_edit_ (eti); + + } + + if (eti->grabbed_count > 0) { + d (g_print ("%s: eti_ungrab\n", __FUNCTION__)); + eti_ungrab (eti, event_time); + } + + if (model_row != -1 && model_col != -1) { + g_signal_emit ( + eti, eti_signals[DOUBLE_CLICK], 0, + model_row, model_col, event_copy); + } + + gdk_event_free (event_copy); + } + break; + } + case GDK_MOTION_NOTIFY: { + gint col, row, flags; + gdouble x1, y1; + gint cursor_col, cursor_row; + + event_x_item = event_x_win; + event_y_item = event_y_win; + + gnome_canvas_item_w2i (item, &event_x_item, &event_y_item); + + if (eti->maybe_in_drag) { + if (abs (event_x_item - eti->drag_x) >= 3 || + abs (event_y_item - eti->drag_y) >= 3) { + gboolean drag_handled; + + eti->maybe_in_drag = 0; + + /* Clone the event and + * alter its position. */ + event_copy = gdk_event_copy (event); + event_copy->motion.x = event_x_item; + event_copy->motion.y = event_y_item; + + g_signal_emit ( + eti, eti_signals[START_DRAG], 0, + eti->drag_row, eti->drag_col, + event_copy, &drag_handled); + + gdk_event_free (event_copy); + + if (drag_handled) + eti->in_drag = 1; + else + eti->in_drag = 0; + } + } + + if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) + return TRUE; + + if (eti->motion_row != -1 && eti->motion_col != -1 && + (row != eti->motion_row || col != eti->motion_col)) { + GdkEvent *cross = gdk_event_new (GDK_LEAVE_NOTIFY); + cross->crossing.time = event_time; + return_val = eti_e_cell_event ( + eti, eti->cell_views[eti->motion_col], + cross, + view_to_model_col (eti, eti->motion_col), + eti->motion_col, eti->motion_row, 0); + } + + eti->motion_row = row; + eti->motion_col = col; + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + flags = 0; + if (cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) { + flags = E_CELL_EDITING | E_CELL_CURSOR; + } + + ecell_view = eti->cell_views[col]; + + /* Clone the event and alter its position. */ + event_copy = gdk_event_copy (event); + event_copy->motion.x = x1; + event_copy->motion.y = y1; + + return_val = eti_e_cell_event ( + eti, ecell_view, event_copy, + view_to_model_col (eti, col), col, row, flags); + + gdk_event_free (event_copy); + + break; + } + + case GDK_KEY_PRESS: { + gint cursor_row, cursor_col; + gint handled = TRUE; + + d (g_print ("%s: GDK_KEY_PRESS received, keyval: %d\n", __FUNCTION__, (gint) e->key.keyval)); + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + if (cursor_row == -1 && cursor_col == -1) + return FALSE; + + eti->in_key_press = TRUE; + + switch (event_keyval) { + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + if (eti_editing (eti)) { + handled = FALSE; + break; + } + + g_signal_emit ( + eti, eti_signals[KEY_PRESS], 0, + model_to_view_row (eti, cursor_row), + cursor_col, event, &return_val); + if ((!return_val) && + (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) && + cursor_col != view_to_model_col (eti, 0)) + eti_cursor_move_left (eti); + return_val = 1; + break; + + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + if (eti_editing (eti)) { + handled = FALSE; + break; + } + + g_signal_emit ( + eti, eti_signals[KEY_PRESS], 0, + model_to_view_row (eti, cursor_row), + cursor_col, event, &return_val); + if ((!return_val) && + (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) && + cursor_col != view_to_model_col (eti, eti->cols - 1)) + eti_cursor_move_right (eti); + return_val = 1; + break; + + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if ((event_state & GDK_MOD1_MASK) + && ((event_keyval == GDK_KEY_Down) || (event_keyval == GDK_KEY_KP_Down))) { + gint view_col = model_to_view_col (eti, cursor_col); + + if ((view_col >= 0) && (view_col < eti->cols)) + if (eti_e_cell_event (eti, eti->cell_views[view_col], event, cursor_col, view_col, model_to_view_row (eti, cursor_row), E_CELL_CURSOR)) + return TRUE; + } else + return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event); + break; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + if (eti_editing (eti)) { + handled = FALSE; + break; + } + + if (eti->cursor_mode != E_CURSOR_LINE) { + eti_cursor_move (eti, model_to_view_row (eti, cursor_row), 0); + return_val = TRUE; + } else + return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event); + break; + case GDK_KEY_End: + case GDK_KEY_KP_End: + if (eti_editing (eti)) { + handled = FALSE; + break; + } + + if (eti->cursor_mode != E_CURSOR_LINE) { + eti_cursor_move (eti, model_to_view_row (eti, cursor_row), eti->cols - 1); + return_val = TRUE; + } else + return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event); + break; + case GDK_KEY_Tab: + case GDK_KEY_KP_Tab: + case GDK_KEY_ISO_Left_Tab: + if ((event_state & GDK_CONTROL_MASK) != 0) { + return_val = FALSE; + break; + } + if (eti->cursor_mode == E_CURSOR_SPREADSHEET) { + if ((event_state & GDK_SHIFT_MASK) != 0) { + /* shift tab */ + if (cursor_col != view_to_model_col (eti, 0)) + eti_cursor_move_left (eti); + else if (cursor_row != view_to_model_row (eti, 0)) + eti_cursor_move (eti, model_to_view_row (eti, cursor_row) - 1, eti->cols - 1); + else + return_val = FALSE; + } else { + if (cursor_col != view_to_model_col (eti, eti->cols - 1)) + eti_cursor_move_right (eti); + else if (cursor_row != view_to_model_row (eti, eti->rows - 1)) + eti_cursor_move (eti, model_to_view_row (eti, cursor_row) + 1, 0); + else + return_val = FALSE; + } + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + if (cursor_col >= 0 && cursor_row >= 0 && return_val && + (!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, model_to_view_row (eti, cursor_row))) { + e_table_item_enter_edit (eti, model_to_view_col (eti, cursor_col), model_to_view_row (eti, cursor_row)); + } + break; + } else { + /* Let tab send you to the next widget. */ + return_val = FALSE; + break; + } + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + case GDK_KEY_ISO_Enter: + case GDK_KEY_3270_Enter: + if (eti_editing (eti)) { + ecell_view = eti->cell_views[eti->editing_col]; + return_val = eti_e_cell_event ( + eti, ecell_view, event, + view_to_model_col (eti, eti->editing_col), + eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR | E_CELL_PREEDIT); + if (!return_val) + break; + } + g_signal_emit ( + eti, eti_signals[KEY_PRESS], 0, + model_to_view_row (eti, cursor_row), + cursor_col, event, &return_val); + if (!return_val) + return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event); + break; + + default: + handled = FALSE; + break; + } + + if (!handled) { + switch (event_keyval) { + case GDK_KEY_Scroll_Lock: + case GDK_KEY_Sys_Req: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Caps_Lock: + case GDK_KEY_Shift_Lock: + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Super_L: + case GDK_KEY_Super_R: + case GDK_KEY_Hyper_L: + case GDK_KEY_Hyper_R: + case GDK_KEY_ISO_Lock: + break; + + default: + if (!eti_editing (eti)) { + gint col, row; + row = model_to_view_row (eti, cursor_row); + col = model_to_view_col (eti, cursor_col); + if (col != -1 && row != -1 && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) { + e_table_item_enter_edit (eti, col, row); + } + } + if (!eti_editing (eti)) { + g_signal_emit ( + eti, eti_signals[KEY_PRESS], 0, + model_to_view_row (eti, cursor_row), + cursor_col, event, &return_val); + if (!return_val) + e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event); + } else { + ecell_view = eti->cell_views[eti->editing_col]; + return_val = eti_e_cell_event ( + eti, ecell_view, event, + view_to_model_col (eti, eti->editing_col), + eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR); + if (!return_val) + e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event); + } + break; + } + } + eti->in_key_press = FALSE; + break; + } + + case GDK_KEY_RELEASE: { + gint cursor_row, cursor_col; + + d (g_print ("%s: GDK_KEY_RELEASE received, keyval: %d\n", __FUNCTION__, (gint) event_keyval)); + + g_object_get ( + eti->selection, + "cursor_row", &cursor_row, + "cursor_col", &cursor_col, + NULL); + + if (cursor_col == -1) + return FALSE; + + if (eti_editing (eti)) { + ecell_view = eti->cell_views[eti->editing_col]; + return_val = eti_e_cell_event ( + eti, ecell_view, event, + view_to_model_col (eti, eti->editing_col), + eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR); + } + break; + } + + case GDK_LEAVE_NOTIFY: + d (leave = TRUE); + case GDK_ENTER_NOTIFY: + d (g_print ("%s: %s received\n", __FUNCTION__, leave ? "GDK_LEAVE_NOTIFY" : "GDK_ENTER_NOTIFY")); + if (eti->motion_row != -1 && eti->motion_col != -1) + return_val = eti_e_cell_event ( + eti, eti->cell_views[eti->motion_col], + event, + view_to_model_col (eti, eti->motion_col), + eti->motion_col, eti->motion_row, 0); + eti->motion_row = -1; + eti->motion_col = -1; + + break; + + case GDK_FOCUS_CHANGE: + d (g_print ("%s: GDK_FOCUS_CHANGE received, %s\n", __FUNCTION__, e->focus_change.in ? "in": "out")); + if (event->focus_change.in) { + if (eti->save_row != -1 && + eti->save_col != -1 && + !eti_editing (eti) && + e_table_model_is_cell_editable (eti->table_model, view_to_model_col (eti, eti->save_col), eti->save_row)) { + e_table_item_enter_edit (eti, eti->save_col, eti->save_row); + e_cell_load_state ( + eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->save_col), + eti->save_col, eti->save_row, eti->edit_ctx, eti->save_state); + eti_free_save_state (eti); + } + } else { + if (eti_editing (eti)) { + eti_free_save_state (eti); + + eti->save_row = eti->editing_row; + eti->save_col = eti->editing_col; + eti->save_state = e_cell_save_state ( + eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->editing_col), + eti->editing_col, eti->editing_row, eti->edit_ctx); + e_table_item_leave_edit_(eti); + } + } + + default: + return_val = FALSE; + } + /* d(g_print("%s: returning: %s\n", __FUNCTION__, return_val?"true":"false"));*/ + + return return_val; +} + +static void +eti_style_set (ETableItem *eti, + GtkStyle *previous_style) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) + return; + + if (eti->cell_views_realized) { + gint i; + gint n_cells = eti->n_cells; + + for (i = 0; i < n_cells; i++) { + e_cell_style_set (eti->cell_views[i], previous_style); + } + } + + eti->needs_compute_height = 1; + e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti)); + eti->needs_redraw = 1; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); + + free_height_cache (eti); + + eti_idle_maybe_show_cursor (eti); +} + +static void +eti_class_init (ETableItemClass *class) +{ + GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = eti_dispose; + object_class->set_property = eti_set_property; + object_class->get_property = eti_get_property; + + item_class->update = eti_update; + item_class->realize = eti_realize; + item_class->unrealize = eti_unrealize; + item_class->draw = eti_draw; + item_class->point = eti_point; + item_class->event = eti_event; + + class->cursor_change = NULL; + class->cursor_activated = NULL; + class->double_click = NULL; + class->right_click = NULL; + class->click = NULL; + class->key_press = NULL; + class->start_drag = NULL; + class->style_set = eti_style_set; + class->selection_model_removed = NULL; + class->selection_model_added = NULL; + + g_object_class_install_property ( + object_class, + PROP_TABLE_HEADER, + g_param_spec_object ( + "ETableHeader", + "Table header", + "Table header", + E_TYPE_TABLE_HEADER, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_TABLE_MODEL, + g_param_spec_object ( + "ETableModel", + "Table model", + "Table model", + E_TYPE_TABLE_MODEL, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_SELECTION_MODEL, + g_param_spec_object ( + "selection_model", + "Selection model", + "Selection model", + E_TYPE_SELECTION_MODEL, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_TABLE_ALTERNATING_ROW_COLORS, + g_param_spec_boolean ( + "alternating_row_colors", + "Alternating Row Colors", + "Alternating Row Colors", + FALSE, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_TABLE_HORIZONTAL_DRAW_GRID, + g_param_spec_boolean ( + "horizontal_draw_grid", + "Horizontal Draw Grid", + "Horizontal Draw Grid", + FALSE, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_TABLE_VERTICAL_DRAW_GRID, + g_param_spec_boolean ( + "vertical_draw_grid", + "Vertical Draw Grid", + "Vertical Draw Grid", + FALSE, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_TABLE_DRAW_FOCUS, + g_param_spec_boolean ( + "drawfocus", + "Draw focus", + "Draw focus", + FALSE, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_CURSOR_MODE, + g_param_spec_int ( + "cursor_mode", + "Cursor mode", + "Cursor mode", + E_CURSOR_LINE, + E_CURSOR_SPREADSHEET, + E_CURSOR_LINE, + G_PARAM_WRITABLE)); + + g_object_class_install_property ( + object_class, + PROP_LENGTH_THRESHOLD, + g_param_spec_int ( + "length_threshold", + "Length Threshold", + "Length Threshold", + -1, G_MAXINT, 0, + G_PARAM_WRITABLE)); + + 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_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_HEIGHT, + g_param_spec_double ( + "height", + "Height", + "Height", + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_CURSOR_ROW, + g_param_spec_int ( + "cursor_row", + "Cursor row", + "Cursor row", + 0, G_MAXINT, 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_UNIFORM_ROW_HEIGHT, + g_param_spec_boolean ( + "uniform_row_height", + "Uniform row height", + "Uniform row height", + FALSE, + G_PARAM_READWRITE)); + + eti_signals[CURSOR_CHANGE] = g_signal_new ( + "cursor_change", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, cursor_change), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + eti_signals[CURSOR_ACTIVATED] = g_signal_new ( + "cursor_activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, cursor_activated), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, + G_TYPE_INT); + + eti_signals[DOUBLE_CLICK] = g_signal_new ( + "double_click", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, double_click), + NULL, NULL, + e_marshal_NONE__INT_INT_BOXED, + G_TYPE_NONE, 3, + G_TYPE_INT, + G_TYPE_INT, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + eti_signals[START_DRAG] = g_signal_new ( + "start_drag", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, start_drag), + g_signal_accumulator_true_handled, NULL, + e_marshal_BOOLEAN__INT_INT_BOXED, + G_TYPE_BOOLEAN, 3, + G_TYPE_INT, + G_TYPE_INT, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + eti_signals[RIGHT_CLICK] = g_signal_new ( + "right_click", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, right_click), + g_signal_accumulator_true_handled, NULL, + e_marshal_BOOLEAN__INT_INT_BOXED, + G_TYPE_BOOLEAN, 3, + G_TYPE_INT, + G_TYPE_INT, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + eti_signals[CLICK] = g_signal_new ( + "click", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, click), + g_signal_accumulator_true_handled, NULL, + e_marshal_BOOLEAN__INT_INT_BOXED, + G_TYPE_BOOLEAN, 3, + G_TYPE_INT, + G_TYPE_INT, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + eti_signals[KEY_PRESS] = g_signal_new ( + "key_press", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, key_press), + g_signal_accumulator_true_handled, NULL, + e_marshal_BOOLEAN__INT_INT_BOXED, + G_TYPE_BOOLEAN, 3, + G_TYPE_INT, + G_TYPE_INT, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + eti_signals[STYLE_SET] = g_signal_new ( + "style_set", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ETableItemClass, style_set), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + GTK_TYPE_STYLE); + + eti_signals[SELECTION_MODEL_REMOVED] = g_signal_new ( + "selection_model_removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (ETableItemClass, selection_model_removed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + eti_signals[SELECTION_MODEL_ADDED] = g_signal_new ( + "selection_model_added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (ETableItemClass, selection_model_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + /* A11y Init */ + gal_a11y_e_table_item_init (); +} + +/** + * e_table_item_set_cursor: + * @eti: %ETableItem which will have the cursor set. + * @col: Column to select. -1 means the last column. + * @row: Row to select. -1 means the last row. + * + * This routine sets the cursor of the %ETableItem canvas item. + */ +void +e_table_item_set_cursor (ETableItem *eti, + gint col, + gint row) +{ + e_table_item_focus (eti, col, view_to_model_row (eti, row), 0); +} + +static void +e_table_item_focus (ETableItem *eti, + gint col, + gint row, + GdkModifierType state) +{ + g_return_if_fail (eti != NULL); + g_return_if_fail (E_IS_TABLE_ITEM (eti)); + + if (row == -1) { + row = view_to_model_row (eti, eti->rows - 1); + } + + if (col == -1) { + col = eti->cols - 1; + } + + if (row != -1) { + e_selection_model_do_something ( + E_SELECTION_MODEL (eti->selection), + row, col, state); + } +} + +/** + * e_table_item_get_focused_column: + * @eti: %ETableItem which will have the cursor retrieved. + * + * This routine gets the cursor of the %ETableItem canvas item. + * + * Returns: The current cursor column. + */ +gint +e_table_item_get_focused_column (ETableItem *eti) +{ + gint cursor_col; + + g_return_val_if_fail (eti != NULL, -1); + g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1); + + g_object_get ( + eti->selection, + "cursor_col", &cursor_col, + NULL); + + return cursor_col; +} + +static void +eti_cursor_change (ESelectionModel *selection, + gint row, + gint col, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + gint view_row; + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) + return; + + view_row = model_to_view_row (eti, row); + + if (eti->old_cursor_row != -1 && view_row != eti->old_cursor_row) + e_table_item_redraw_row (eti, eti->old_cursor_row); + + if (view_row == -1) { + e_table_item_leave_edit_(eti); + eti->old_cursor_row = -1; + return; + } + + if (!e_table_model_has_change_pending (eti->table_model)) { + if (!eti->in_key_press) { + eti_maybe_show_cursor (eti, DOUBLE_CLICK_TIME + 10); + } else { + eti_maybe_show_cursor (eti, 0); + } + } + + e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), FALSE); + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + + g_signal_emit (eti, eti_signals[CURSOR_CHANGE], 0, view_row); + + e_table_item_redraw_row (eti, view_row); + + eti->old_cursor_row = view_row; +} + +static void +eti_cursor_activated (ESelectionModel *selection, + gint row, + gint col, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + gint view_row; + gint view_col; + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) + return; + + view_row = model_to_view_row (eti, row); + view_col = model_to_view_col (eti, col); + + if (view_row != -1 && view_col != -1) { + if (!e_table_model_has_change_pending (eti->table_model)) { + if (!eti->in_key_press) { + eti_show_cursor (eti, DOUBLE_CLICK_TIME + 10); + } else { + eti_show_cursor (eti, 0); + } + eti_check_cursor_bounds (eti); + } + } + + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + + if (view_row != -1) + g_signal_emit ( + eti, eti_signals[CURSOR_ACTIVATED], 0, view_row); +} + +static void +eti_selection_change (ESelectionModel *selection, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) + return; + + eti->needs_redraw = TRUE; + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti)); +} + +static void +eti_selection_row_change (ESelectionModel *selection, + gint row, + ETableItem *eti) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti); + + if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) + return; + + if (!eti->needs_redraw) { + e_table_item_redraw_row (eti, model_to_view_row (eti, row)); + } +} + +/** + * e_table_item_enter_edit + * @eti: %ETableItem which will start being edited + * @col: The view col to edit. + * @row: The view row to edit. + * + * This routine starts the given %ETableItem editing at the given view + * column and row. + */ +void +e_table_item_enter_edit (ETableItem *eti, + gint col, + gint row) +{ + g_return_if_fail (eti != NULL); + g_return_if_fail (E_IS_TABLE_ITEM (eti)); + + d (g_print ("%s: %d, %d, eti_editing() = %s\n", __FUNCTION__, col, row, eti_editing (eti)?"true":"false")); + + if (eti_editing (eti)) + e_table_item_leave_edit_(eti); + + eti->editing_col = col; + eti->editing_row = row; + + eti->edit_ctx = e_cell_enter_edit (eti->cell_views[col], view_to_model_col (eti, col), col, row); +} + +/** + * e_table_item_leave_edit_ + * @eti: %ETableItem which will stop being edited + * + * This routine stops the given %ETableItem from editing. + */ +void +e_table_item_leave_edit (ETableItem *eti) +{ + gint col, row; + gpointer edit_ctx; + + g_return_if_fail (eti != NULL); + g_return_if_fail (E_IS_TABLE_ITEM (eti)); + + d (g_print ("%s: eti_editing() = %s\n", __FUNCTION__, eti_editing (eti)?"true":"false")); + + if (!eti_editing (eti)) + return; + + col = eti->editing_col; + row = eti->editing_row; + edit_ctx = eti->edit_ctx; + + eti->editing_col = -1; + eti->editing_row = -1; + eti->edit_ctx = NULL; + + e_cell_leave_edit ( + eti->cell_views[col], + view_to_model_col (eti, col), + col, row, edit_ctx); +} + +/** + * e_table_item_compute_location + * @eti: %ETableItem to look in. + * @x: A pointer to the x location to find in the %ETableItem. + * @y: A pointer to the y location to find in the %ETableItem. + * @row: A pointer to the location to store the found row in. + * @col: A pointer to the location to store the found col in. + * + * This routine locates the pixel location (*x, *y) in the + * %ETableItem. If that location is in the %ETableItem, *row and *col + * are set to the view row and column where it was found. If that + * location is not in the %ETableItem, the height of the %ETableItem + * is removed from the value y points to. + */ +void +e_table_item_compute_location (ETableItem *eti, + gint *x, + gint *y, + gint *row, + gint *col) +{ + /* Save the grabbed row but make sure that we don't get flawed + * results because the cursor is grabbed. */ + gint grabbed_row = eti->grabbed_row; + eti->grabbed_row = -1; + + if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) { + *y -= eti->height; + } + + eti->grabbed_row = grabbed_row; +} + +/** + * e_table_item_compute_mouse_over: + * Similar to e_table_item_compute_location, only here recalculating + * the position inside the item too. + **/ +void +e_table_item_compute_mouse_over (ETableItem *eti, + gint x, + gint y, + gint *row, + gint *col) +{ + gdouble realx, realy; + /* Save the grabbed row but make sure that we don't get flawed + * results because the cursor is grabbed. */ + gint grabbed_row = eti->grabbed_row; + eti->grabbed_row = -1; + + realx = x; + realy = y; + + gnome_canvas_item_w2i (GNOME_CANVAS_ITEM (eti), &realx, &realy); + + if (!find_cell (eti, (gint) realx, (gint) realy, col, row, NULL, NULL)) { + *row = -1; + *col = -1; + } + + eti->grabbed_row = grabbed_row; +} + +void +e_table_item_get_cell_geometry (ETableItem *eti, + gint *row, + gint *col, + gint *x, + gint *y, + gint *width, + gint *height) +{ + if (eti->rows > *row) { + if (x) + *x = e_table_header_col_diff (eti->header, 0, *col); + if (y) + *y = e_table_item_row_diff (eti, 0, *row); + if (width) + *width = e_table_header_col_diff (eti->header, *col, *col + 1); + if (height) + *height = ETI_ROW_HEIGHT (eti, *row); + *row = -1; + *col = -1; + } else { + *row -= eti->rows; + } +} + +typedef struct { + ETableItem *item; + gint rows_printed; +} ETableItemPrintContext; + +static gdouble * +e_table_item_calculate_print_widths (ETableHeader *eth, + gdouble width) +{ + gint i; + gdouble extra; + gdouble expansion; + gint last_resizable = -1; + gdouble scale = 1.0L; + gdouble *widths = g_new (gdouble, e_table_header_count (eth)); + /* - 1 to account for the last pixel border. */ + extra = width - 1; + expansion = 0; + for (i = 0; i < eth->col_count; i++) { + extra -= eth->columns[i]->min_width * scale; + if (eth->columns[i]->resizable && eth->columns[i]->expansion > 0) + last_resizable = i; + expansion += eth->columns[i]->resizable ? eth->columns[i]->expansion : 0; + widths[i] = eth->columns[i]->min_width * scale; + } + for (i = 0; i <= last_resizable; i++) { + widths[i] += extra * (eth->columns[i]->resizable ? eth->columns[i]->expansion : 0) / expansion; + } + + return widths; +} + +static gdouble +eti_printed_row_height (ETableItem *eti, + gdouble *widths, + GtkPrintContext *context, + gint row) +{ + gint col; + gint cols = eti->cols; + gdouble height = 0; + for (col = 0; col < cols; col++) { + ECellView *ecell_view = eti->cell_views[col]; + gdouble this_height = e_cell_print_height ( + ecell_view, context, view_to_model_col (eti, col), col, row, + widths[col] - 1); + if (this_height > height) + height = this_height; + } + return height; +} + +#define CHECK(x) if((x) == -1) return -1; + +static gint +gp_draw_rect (GtkPrintContext *context, + gdouble x, + gdouble y, + gdouble width, + gdouble height) +{ + cairo_t *cr; + cr = gtk_print_context_get_cairo_context (context); + cairo_save (cr); + cairo_rectangle (cr, x, y, width, height); + cairo_set_line_width (cr, 0.5); + cairo_stroke (cr); + cairo_restore (cr); + return 0; +} + +static void +e_table_item_print_page (EPrintable *ep, + GtkPrintContext *context, + gdouble width, + gdouble height, + gboolean quantize, + ETableItemPrintContext *itemcontext) +{ + ETableItem *eti = itemcontext->item; + const gint rows = eti->rows; + const gint cols = eti->cols; + gdouble max_height; + gint rows_printed = itemcontext->rows_printed; + gint row, col, next_page = 0; + gdouble yd = height; + cairo_t *cr; + gdouble *widths; + + cr = gtk_print_context_get_cairo_context (context); + max_height = gtk_print_context_get_height (context); + widths = e_table_item_calculate_print_widths (itemcontext->item->header, width); + + /* + * Draw cells + */ + + if (eti->horizontal_draw_grid) { + gp_draw_rect (context, 0, yd, width, 1); + } + yd++; + + for (row = rows_printed; row < rows; row++) { + gdouble xd = 1, row_height; + row_height = eti_printed_row_height (eti, widths, context, row); + + if (quantize) { + if (yd + row_height + 1 > max_height && row != rows_printed) { + next_page = 1; + break; + } + } else { + if (yd > max_height) { + next_page = 1; + break; + } + } + + for (col = 0; col < cols; col++) { + ECellView *ecell_view = eti->cell_views[col]; + + cairo_save (cr); + cairo_translate (cr, xd, yd); + cairo_rectangle (cr, 0, 0, widths[col] - 1, row_height); + cairo_clip (cr); + + e_cell_print ( + ecell_view, context, + view_to_model_col (eti, col), + col, + row, + widths[col] - 1, + row_height + 2); + + cairo_restore (cr); + + xd += widths[col]; + } + + yd += row_height; + if (eti->horizontal_draw_grid) { + gp_draw_rect (context, 0, yd, width, 1); + } + yd++; + } + + itemcontext->rows_printed = row; + if (eti->vertical_draw_grid) { + gdouble xd = 0; + for (col = 0; col < cols; col++) { + gp_draw_rect (context, xd, height, 1, yd - height); + xd += widths[col]; + } + gp_draw_rect (context, xd, height, 1, yd - height); + } + + if (next_page) + cairo_show_page (cr); + + g_free (widths); +} + +static gboolean +e_table_item_data_left (EPrintable *ep, + ETableItemPrintContext *itemcontext) +{ + ETableItem *item = itemcontext->item; + gint rows_printed = itemcontext->rows_printed; + + g_signal_stop_emission_by_name (ep, "data_left"); + return rows_printed < item->rows; +} + +static void +e_table_item_reset (EPrintable *ep, + ETableItemPrintContext *itemcontext) +{ + itemcontext->rows_printed = 0; +} + +static gdouble +e_table_item_height (EPrintable *ep, + GtkPrintContext *context, + gdouble width, + gdouble max_height, + gboolean quantize, + ETableItemPrintContext *itemcontext) +{ + ETableItem *item = itemcontext->item; + const gint rows = item->rows; + gint rows_printed = itemcontext->rows_printed; + gdouble *widths; + gint row; + gdouble yd = 0; + + widths = e_table_item_calculate_print_widths (itemcontext->item->header, width); + + /* + * Draw cells + */ + yd++; + + for (row = rows_printed; row < rows; row++) { + gdouble row_height; + + row_height = eti_printed_row_height (item, widths, context, row); + if (quantize) { + if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) { + break; + } + } else { + if (max_height != -1 && yd > max_height) { + break; + } + } + + yd += row_height; + + yd++; + } + + g_free (widths); + + if (max_height != -1 && (!quantize) && yd > max_height) + yd = max_height; + + g_signal_stop_emission_by_name (ep, "height"); + return yd; +} + +static gboolean +e_table_item_will_fit (EPrintable *ep, + GtkPrintContext *context, + gdouble width, + gdouble max_height, + gboolean quantize, + ETableItemPrintContext *itemcontext) +{ + ETableItem *item = itemcontext->item; + const gint rows = item->rows; + gint rows_printed = itemcontext->rows_printed; + gdouble *widths; + gint row; + gdouble yd = 0; + gboolean ret_val = TRUE; + + widths = e_table_item_calculate_print_widths (itemcontext->item->header, width); + + /* + * Draw cells + */ + yd++; + + for (row = rows_printed; row < rows; row++) { + gdouble row_height; + + row_height = eti_printed_row_height (item, widths, context, row); + if (quantize) { + if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) { + ret_val = FALSE; + break; + } + } else { + if (max_height != -1 && yd > max_height) { + ret_val = FALSE; + break; + } + } + + yd += row_height; + + yd++; + } + + g_free (widths); + + g_signal_stop_emission_by_name (ep, "will_fit"); + return ret_val; +} + +static void +e_table_item_printable_destroy (gpointer data, + GObject *where_object_was) +{ + ETableItemPrintContext *itemcontext = data; + + g_object_unref (itemcontext->item); + g_free (itemcontext); +} + +/** + * e_table_item_get_printable + * @eti: %ETableItem which will be printed + * + * This routine creates and returns an %EPrintable that can be used to + * print the given %ETableItem. + * + * Returns: The %EPrintable. + */ +EPrintable * +e_table_item_get_printable (ETableItem *item) +{ + EPrintable *printable = e_printable_new (); + ETableItemPrintContext *itemcontext; + + itemcontext = g_new (ETableItemPrintContext, 1); + itemcontext->item = item; + g_object_ref (item); + itemcontext->rows_printed = 0; + + g_signal_connect ( + printable, "print_page", + G_CALLBACK (e_table_item_print_page), itemcontext); + g_signal_connect ( + printable, "data_left", + G_CALLBACK (e_table_item_data_left), itemcontext); + g_signal_connect ( + printable, "reset", + G_CALLBACK (e_table_item_reset), itemcontext); + g_signal_connect ( + printable, "height", + G_CALLBACK (e_table_item_height), itemcontext); + g_signal_connect ( + printable, "will_fit", + G_CALLBACK (e_table_item_will_fit), itemcontext); + + g_object_weak_ref ( + G_OBJECT (printable), + e_table_item_printable_destroy, itemcontext); + + return printable; +} |