/* -*- 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 * * * Authors: * Chris Lahey * Miguel de Icaza * * 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 #endif #include "e-table-item.h" #include #include #include #include #include #include #include #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" G_DEFINE_TYPE ( ETableItem, e_table_item, 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_IS_EDITING, 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) { if (row == -1) return -1; if (eti->uses_source_model) { gint model_row; model_row = e_table_subset_view_to_model_row ( E_TABLE_SUBSET (eti->table_model), eti->row_guess); if (model_row >= 0 && model_row == row) return eti->row_guess; return e_table_subset_model_to_view_row ( E_TABLE_SUBSET (eti->table_model), row); } else return row; } inline static gint view_to_model_row (ETableItem *eti, gint row) { if (eti->uses_source_model) { gint model_row; model_row = e_table_subset_view_to_model_row ( E_TABLE_SUBSET (eti->table_model), row); if (model_row >= 0) eti->row_guess = row; return model_row; } else return row; } inline static gint model_to_view_col (ETableItem *eti, gint model_col) { gint i; if (model_col == -1) return -1; for (i = 0; i < eti->cols; i++) { ETableCol *ecol = e_table_header_get_column (eti->header, i); if (ecol->spec->model_col == model_col) return i; } return -1; } inline static gint view_to_model_col (ETableItem *eti, gint view_col) { ETableCol *ecol = e_table_header_get_column (eti->header, view_col); return (ecol != NULL) ? ecol->spec->model_col : -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 (e_table_item_parent_class)->update) (*GNOME_CANVAS_ITEM_CLASS (e_table_item_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_get_source_model ( E_TABLE_SUBSET (table_model)); 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 (e_table_item_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; case PROP_IS_EDITING: g_value_set_boolean (value, e_table_item_is_editing (eti)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void e_table_item_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 (e_table_item_parent_class)->realize) (*GNOME_CANVAS_ITEM_CLASS (e_table_item_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 (e_table_item_parent_class)->unrealize) (*GNOME_CANVAS_ITEM_CLASS (e_table_item_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->spec->model_col && 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->spec->model_col, 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 e_table_item_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)); g_object_class_install_property ( object_class, PROP_IS_EDITING, g_param_spec_boolean ( "is-editing", "Whether is in an editing mode", "Whether is in an editing mode", FALSE, G_PARAM_READABLE)); 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); g_object_notify (G_OBJECT (eti), "is-editing"); } /** * 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); g_object_notify (G_OBJECT (eti), "is-editing"); } /** * 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]->spec->resizable && eth->columns[i]->expansion > 0) last_resizable = i; expansion += eth->columns[i]->spec->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]->spec->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; } /** * e_table_item_is_editing: * @eti: an %ETableItem * * Returns: Whether the table item is currently editing cell content. **/ gboolean e_table_item_is_editing (ETableItem *eti) { g_return_val_if_fail (E_IS_TABLE_ITEM (eti), FALSE); return eti_editing (eti); } /** * e_table_item_cursor_scrolled: * @eti: an %ETableItem * * Does necessary recalculations after cursor scrolled, like whether * the cursor is on screen or not anymore. **/ void e_table_item_cursor_scrolled (ETableItem *eti) { g_return_if_fail (E_IS_TABLE_ITEM (eti)); eti_check_cursor_bounds (eti); }