1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * e-table-item.c
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  *
17  *
18  * Authors:
19  *		Chris Lahey <clahey@ximian.com>
20  *		Miguel de Icaza <miguel@gnu.org>
21  *
22  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23  */
24 /*
25  * TODO:
26  *   Add a border to the thing, so that focusing works properly.
27  */
28 
29 #include "evolution-config.h"
30 
31 #include "e-table-item.h"
32 
33 #include <math.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <stdlib.h>
37 
38 #include <gtk/gtk.h>
39 #include <glib/gi18n.h>
40 #include <gdk/gdkkeysyms.h>
41 
42 #include "e-canvas-utils.h"
43 #include "e-canvas.h"
44 #include "e-cell.h"
45 #include "e-cell-text.h"
46 #include "e-marshal.h"
47 #include "e-table-subset.h"
48 #include "gal-a11y-e-table-item-factory.h"
49 #include "gal-a11y-e-table-item.h"
50 
51 G_DEFINE_TYPE (
52 	ETableItem,
53 	e_table_item,
54 	GNOME_TYPE_CANVAS_ITEM)
55 
56 #define FOCUSED_BORDER 2
57 
58 #define d(x)
59 
60 #if d(!)0
61 #define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", G_STRFUNC))
62 #else
63 #define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
64 #endif
65 
66 #define E_TABLE_ITEM_GET_PRIVATE(obj) \
67 	(G_TYPE_INSTANCE_GET_PRIVATE \
68 	((obj), E_TYPE_TABLE_ITEM, ETableItemPrivate))
69 
70 typedef struct _ETableItemPrivate ETableItemPrivate;
71 
72 struct _ETableItemPrivate {
73 	GSource *show_cursor_delay_source;
74 };
75 
76 static void eti_check_cursor_bounds (ETableItem *eti);
77 static void eti_cancel_drag_due_to_model_change (ETableItem *eti);
78 
79 /* FIXME: Do an analysis of which cell functions are needed before
80  * realize and make sure that all of them are doable by all the cells
81  * and that all of the others are only done after realization. */
82 
83 enum {
84 	CURSOR_CHANGE,
85 	CURSOR_ACTIVATED,
86 	DOUBLE_CLICK,
87 	RIGHT_CLICK,
88 	CLICK,
89 	KEY_PRESS,
90 	START_DRAG,
91 	STYLE_UPDATED,
92 	SELECTION_MODEL_REMOVED,
93 	SELECTION_MODEL_ADDED,
94 	GET_BG_COLOR,
95 	LAST_SIGNAL
96 };
97 
98 static guint eti_signals[LAST_SIGNAL] = { 0, };
99 
100 enum {
101 	PROP_0,
102 	PROP_TABLE_HEADER,
103 	PROP_TABLE_MODEL,
104 	PROP_SELECTION_MODEL,
105 	PROP_TABLE_ALTERNATING_ROW_COLORS,
106 	PROP_TABLE_HORIZONTAL_DRAW_GRID,
107 	PROP_TABLE_VERTICAL_DRAW_GRID,
108 	PROP_TABLE_DRAW_FOCUS,
109 	PROP_CURSOR_MODE,
110 	PROP_LENGTH_THRESHOLD,
111 	PROP_CURSOR_ROW,
112 	PROP_UNIFORM_ROW_HEIGHT,
113 	PROP_IS_EDITING,
114 
115 	PROP_MINIMUM_WIDTH,
116 	PROP_WIDTH,
117 	PROP_HEIGHT
118 };
119 
120 #define DOUBLE_CLICK_TIME      250
121 #define TRIPLE_CLICK_TIME      500
122 
123 static gint eti_get_height (ETableItem *eti);
124 static gint eti_row_height (ETableItem *eti, gint row);
125 static void e_table_item_focus (ETableItem *eti, gint col, gint row, GdkModifierType state);
126 static void eti_cursor_change (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
127 static void eti_cursor_activated (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
128 static void eti_selection_change (ESelectionModel *selection, ETableItem *eti);
129 static void eti_selection_row_change (ESelectionModel *selection, gint row, ETableItem *eti);
130 static void e_table_item_redraw_row (ETableItem *eti, gint row);
131 
132 #define ETI_SINGLE_ROW_HEIGHT(eti) ((eti)->uniform_row_height_cache != -1 ? (eti)->uniform_row_height_cache : eti_row_height((eti), -1))
133 #define ETI_MULTIPLE_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row)))
134 #define ETI_ROW_HEIGHT(eti,row) ((eti)->uniform_row_height ? ETI_SINGLE_ROW_HEIGHT ((eti)) : ETI_MULTIPLE_ROW_HEIGHT((eti),(row)))
135 
136 /* tweak_hsv is a really tweaky function. it modifies its first argument, which
137  * should be the color you want tweaked. delta_h, delta_s and delta_v specify
138  * how much you want their respective channels modified (and in what direction).
139  * if it can't do the specified modification, it does it in the oppositon direction */
140 static void
e_hsv_tweak(GdkRGBA * rgba,gdouble delta_h,gdouble delta_s,gdouble delta_v)141 e_hsv_tweak (GdkRGBA *rgba,
142              gdouble delta_h,
143              gdouble delta_s,
144              gdouble delta_v)
145 {
146 	gdouble h, s, v, r, g, b;
147 
148 	r = rgba->red;
149 	g = rgba->green;
150 	b = rgba->blue;
151 
152 	gtk_rgb_to_hsv (r, g, b, &h, &s, &v);
153 
154 	if (h + delta_h < 0) {
155 		h -= delta_h;
156 	} else {
157 		h += delta_h;
158 	}
159 
160 	if (s + delta_s < 0) {
161 		s -= delta_s;
162 	} else {
163 		s += delta_s;
164 	}
165 
166 	if (v + delta_v < 0) {
167 		v -= delta_v;
168 	} else {
169 		v += delta_v;
170 	}
171 
172 	gtk_hsv_to_rgb (h, s, v, &r, &g, &b);
173 
174 	rgba->red = r;
175 	rgba->green = g;
176 	rgba->blue = b;
177 }
178 
179 inline static gint
model_to_view_row(ETableItem * eti,gint row)180 model_to_view_row (ETableItem *eti,
181                    gint row)
182 {
183 	if (row == -1)
184 		return -1;
185 	if (eti->uses_source_model) {
186 		gint model_row;
187 
188 		model_row = e_table_subset_view_to_model_row (
189 			E_TABLE_SUBSET (eti->table_model), eti->row_guess);
190 		if (model_row >= 0 && model_row == row)
191 			return eti->row_guess;
192 
193 		return e_table_subset_model_to_view_row (
194 			E_TABLE_SUBSET (eti->table_model), row);
195 	} else
196 		return row;
197 }
198 
199 inline static gint
view_to_model_row(ETableItem * eti,gint row)200 view_to_model_row (ETableItem *eti,
201                    gint row)
202 {
203 	if (eti->uses_source_model) {
204 		gint model_row;
205 
206 		model_row = e_table_subset_view_to_model_row (
207 			E_TABLE_SUBSET (eti->table_model), row);
208 
209 		if (model_row >= 0)
210 			eti->row_guess = row;
211 
212 		return model_row;
213 	} else
214 		return row;
215 }
216 
217 inline static gint
model_to_view_col(ETableItem * eti,gint model_col)218 model_to_view_col (ETableItem *eti,
219                    gint model_col)
220 {
221 	gint i;
222 	if (model_col == -1)
223 		return -1;
224 	for (i = 0; i < eti->cols; i++) {
225 		ETableCol *ecol = e_table_header_get_column (eti->header, i);
226 		if (ecol->spec->model_col == model_col)
227 			return i;
228 	}
229 	return -1;
230 }
231 
232 inline static gint
view_to_model_col(ETableItem * eti,gint view_col)233 view_to_model_col (ETableItem *eti,
234                    gint view_col)
235 {
236 	ETableCol *ecol = e_table_header_get_column (eti->header, view_col);
237 
238 	return (ecol != NULL) ? ecol->spec->model_col : -1;
239 }
240 
241 static void
grab_cancelled(ECanvas * canvas,GnomeCanvasItem * item,gpointer data)242 grab_cancelled (ECanvas *canvas,
243                 GnomeCanvasItem *item,
244                 gpointer data)
245 {
246 	ETableItem *eti = data;
247 
248 	eti->grab_cancelled = TRUE;
249 }
250 
251 inline static void
eti_grab(ETableItem * eti,GdkDevice * device,guint32 time)252 eti_grab (ETableItem *eti,
253           GdkDevice *device,
254           guint32 time)
255 {
256 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
257 	d (g_print ("%s: time: %d\n", G_STRFUNC, time));
258 	if (eti->grabbed_count == 0) {
259 		GdkGrabStatus grab_status;
260 
261 		eti->gtk_grabbed = FALSE;
262 		eti->grab_cancelled = FALSE;
263 
264 		grab_status = e_canvas_item_grab (
265 			E_CANVAS (item->canvas),
266 			item,
267 			GDK_BUTTON1_MOTION_MASK |
268 			GDK_BUTTON2_MOTION_MASK |
269 			GDK_BUTTON3_MOTION_MASK |
270 			GDK_POINTER_MOTION_MASK |
271 			GDK_BUTTON_PRESS_MASK |
272 			GDK_BUTTON_RELEASE_MASK,
273 			NULL,
274 			device, time,
275 			grab_cancelled,
276 			eti);
277 
278 		if (grab_status != GDK_GRAB_SUCCESS) {
279 			d (g_print ("%s: gtk_grab_add\n", G_STRFUNC));
280 			gtk_grab_add (GTK_WIDGET (item->canvas));
281 			eti->gtk_grabbed = TRUE;
282 		}
283 	}
284 	eti->grabbed_count++;
285 }
286 
287 inline static void
eti_ungrab(ETableItem * eti,guint32 time)288 eti_ungrab (ETableItem *eti,
289             guint32 time)
290 {
291 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
292 	gboolean was_grabbed;
293 
294 	d (g_print ("%s: time: %d\n", G_STRFUNC, time));
295 
296 	was_grabbed = eti->grabbed_count > 0;
297 	if (was_grabbed)
298 		eti->grabbed_count--;
299 
300 	if (eti->grabbed_count == 0) {
301 		if (eti->grab_cancelled) {
302 			eti->grab_cancelled = FALSE;
303 		} else {
304 			if (eti->gtk_grabbed) {
305 				d (g_print ("%s: gtk_grab_remove\n", G_STRFUNC));
306 				gtk_grab_remove (GTK_WIDGET (item->canvas));
307 				eti->gtk_grabbed = FALSE;
308 			}
309 			if (was_grabbed)
310 				gnome_canvas_item_ungrab (item, time);
311 			eti->grabbed_col = -1;
312 			eti->grabbed_row = -1;
313 		}
314 	}
315 }
316 
317 inline static gboolean
eti_editing(ETableItem * eti)318 eti_editing (ETableItem *eti)
319 {
320 	d (g_print ("%s: %s\n", G_STRFUNC, (eti->editing_col == -1) ? "false":"true"));
321 
322 	if (eti->editing_col == -1)
323 		return FALSE;
324 	else
325 		return TRUE;
326 }
327 
328 static void
eti_get_cell_background_color(ETableItem * eti,gint row,gint col,gboolean selected,GdkRGBA * background)329 eti_get_cell_background_color (ETableItem *eti,
330                                gint row,
331                                gint col,
332                                gboolean selected,
333                                GdkRGBA *background)
334 {
335 	ECellView *ecell_view = eti->cell_views[col];
336 	GtkWidget *canvas;
337 	gboolean was_set = FALSE;
338 	gchar *color_spec = NULL;
339 
340 	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
341 
342 	g_signal_emit (eti, eti_signals[GET_BG_COLOR], 0, row, col, background, &was_set);
343 
344 	if (!was_set) {
345 		color_spec = e_cell_get_bg_color (ecell_view, row);
346 
347 		if (color_spec != NULL) {
348 			GdkRGBA bg;
349 
350 			if (gdk_rgba_parse (&bg, color_spec)) {
351 				*background = bg;
352 				was_set = TRUE;
353 			}
354 		}
355 
356 		g_free (color_spec);
357 	}
358 
359 	if (!was_set) {
360 		if (selected) {
361 			if (gtk_widget_has_focus (canvas))
362 				e_utils_get_theme_color (canvas, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, background);
363 			else
364 				e_utils_get_theme_color (canvas, "theme_unfocused_selected_bg_color,theme_selected_bg_color", E_UTILS_DEFAULT_THEME_UNFOCUSED_SELECTED_BG_COLOR, background);
365 		} else {
366 			e_utils_get_theme_color (canvas, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, background);
367 		}
368 	}
369 
370 	if (eti->alternating_row_colors) {
371 		if (row % 2) {
372 
373 		} else {
374 			e_hsv_tweak (background, 0.0f, 0.0f, -0.07f);
375 		}
376 	}
377 }
378 
379 static void
eti_free_save_state(ETableItem * eti)380 eti_free_save_state (ETableItem *eti)
381 {
382 	if (eti->save_row == -1 ||
383 	    !eti->cell_views_realized)
384 		return;
385 
386 	e_cell_free_state (
387 		eti->cell_views[eti->save_col], view_to_model_col (eti, eti->save_col),
388 		eti->save_col, eti->save_row, eti->save_state);
389 	eti->save_row = -1;
390 	eti->save_col = -1;
391 	eti->save_state = NULL;
392 }
393 
394 /*
395  * During realization, we have to invoke the per-ecell realize routine
396  * (On our current setup, we have one e-cell per column.
397  *
398  * We might want to optimize this to only realize the unique e-cells:
399  * ie, a strings-only table, uses the same e-cell for every column, and
400  * we might want to avoid realizing each e-cell.
401  */
402 static void
eti_realize_cell_views(ETableItem * eti)403 eti_realize_cell_views (ETableItem *eti)
404 {
405 	GnomeCanvasItem *item;
406 	gint i;
407 
408 	item = GNOME_CANVAS_ITEM (eti);
409 
410 	if (eti->cell_views_realized)
411 		return;
412 
413 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
414 		return;
415 
416 	for (i = 0; i < eti->n_cells; i++)
417 		e_cell_realize (eti->cell_views[i]);
418 	eti->cell_views_realized = 1;
419 }
420 
421 static void
eti_attach_cell_views(ETableItem * eti)422 eti_attach_cell_views (ETableItem *eti)
423 {
424 	gint i;
425 
426 	g_return_if_fail (eti->header);
427 	g_return_if_fail (eti->table_model);
428 
429 	/* this is just c&p from model pre change, but it fixes things */
430 	eti_cancel_drag_due_to_model_change (eti);
431 	eti_check_cursor_bounds (eti);
432 	if (eti_editing (eti))
433 		e_table_item_leave_edit_(eti);
434 	eti->motion_row = -1;
435 	eti->motion_col = -1;
436 
437 	/*
438 	 * Now realize the various ECells
439 	 */
440 	eti->n_cells = eti->cols;
441 	eti->cell_views = g_new (ECellView *, eti->n_cells);
442 
443 	for (i = 0; i < eti->n_cells; i++) {
444 		ETableCol *ecol = e_table_header_get_column (eti->header, i);
445 
446 		eti->cell_views[i] = e_cell_new_view (ecol->ecell, eti->table_model, eti);
447 	}
448 
449 	eti->needs_compute_height = 1;
450 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
451 	eti->needs_redraw = 1;
452 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
453 }
454 
455 /*
456  * During unrealization: we invoke every e-cell (one per column in the current
457  * setup) to dispose all X resources allocated
458  */
459 static void
eti_unrealize_cell_views(ETableItem * eti)460 eti_unrealize_cell_views (ETableItem *eti)
461 {
462 	gint i;
463 
464 	if (eti->cell_views_realized == 0)
465 		return;
466 
467 	eti_free_save_state (eti);
468 
469 	for (i = 0; i < eti->n_cells; i++)
470 		e_cell_unrealize (eti->cell_views[i]);
471 	eti->cell_views_realized = 0;
472 }
473 
474 static void
eti_detach_cell_views(ETableItem * eti)475 eti_detach_cell_views (ETableItem *eti)
476 {
477 	gint i;
478 
479 	eti_free_save_state (eti);
480 
481 	for (i = 0; i < eti->n_cells; i++) {
482 		e_cell_kill_view (eti->cell_views[i]);
483 		eti->cell_views[i] = NULL;
484 	}
485 
486 	g_free (eti->cell_views);
487 	eti->cell_views = NULL;
488 	eti->n_cells = 0;
489 }
490 
491 static void
eti_bounds(GnomeCanvasItem * item,gdouble * x1,gdouble * y1,gdouble * x2,gdouble * y2)492 eti_bounds (GnomeCanvasItem *item,
493             gdouble *x1,
494             gdouble *y1,
495             gdouble *x2,
496             gdouble *y2)
497 {
498 	cairo_matrix_t i2c;
499 	ETableItem *eti = E_TABLE_ITEM (item);
500 
501 	/* Wrong BBox's are the source of redraw nightmares */
502 
503 	*x1 = 0;
504 	*y1 = 0;
505 	*x2 = eti->width;
506 	*y2 = eti->height;
507 
508 	gnome_canvas_item_i2c_matrix (GNOME_CANVAS_ITEM (eti), &i2c);
509 	gnome_canvas_matrix_transform_rect (&i2c, x1, y1, x2, y2);
510 }
511 
512 static void
eti_reflow(GnomeCanvasItem * item,gint flags)513 eti_reflow (GnomeCanvasItem *item,
514             gint flags)
515 {
516 	ETableItem *eti = E_TABLE_ITEM (item);
517 
518 	if (eti->needs_compute_height) {
519 		gint new_height = eti_get_height (eti);
520 
521 		if (new_height != eti->height) {
522 			eti->height = new_height;
523 			e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
524 			eti->needs_redraw = 1;
525 			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
526 		}
527 		eti->needs_compute_height = 0;
528 	}
529 	if (eti->needs_compute_width) {
530 		gint new_width = e_table_header_total_width (eti->header);
531 		if (new_width != eti->width) {
532 			eti->width = new_width;
533 			e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
534 			eti->needs_redraw = 1;
535 			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
536 		}
537 		eti->needs_compute_width = 0;
538 	}
539 }
540 
541 /*
542  * GnomeCanvasItem::update method
543  */
544 static void
eti_update(GnomeCanvasItem * item,const cairo_matrix_t * i2c,gint flags)545 eti_update (GnomeCanvasItem *item,
546             const cairo_matrix_t *i2c,
547             gint flags)
548 {
549 	ETableItem *eti = E_TABLE_ITEM (item);
550 	gdouble x1, x2, y1, y2;
551 
552 	if (GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->update)
553 		(*GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->update)(item, i2c, flags);
554 
555 	x1 = item->x1;
556 	y1 = item->y1;
557 	x2 = item->x2;
558 	y2 = item->y2;
559 
560 	eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
561 	if (item->x1 != x1 ||
562 	    item->y1 != y1 ||
563 	    item->x2 != x2 ||
564 	    item->y2 != y2) {
565 		gnome_canvas_request_redraw (item->canvas, x1, y1, x2, y2);
566 		eti->needs_redraw = 1;
567 	}
568 
569 	if (eti->needs_redraw) {
570 		gnome_canvas_request_redraw (
571 			item->canvas, item->x1, item->y1,
572 			item->x2, item->y2);
573 		eti->needs_redraw = 0;
574 	}
575 }
576 
577 /*
578  * eti_remove_table_model:
579  *
580  * Invoked to release the table model associated with this ETableItem
581  */
582 static void
eti_remove_table_model(ETableItem * eti)583 eti_remove_table_model (ETableItem *eti)
584 {
585 	if (!eti->table_model)
586 		return;
587 
588 	g_signal_handler_disconnect (
589 		eti->table_model,
590 		eti->table_model_pre_change_id);
591 	g_signal_handler_disconnect (
592 		eti->table_model,
593 		eti->table_model_no_change_id);
594 	g_signal_handler_disconnect (
595 		eti->table_model,
596 		eti->table_model_change_id);
597 	g_signal_handler_disconnect (
598 		eti->table_model,
599 		eti->table_model_row_change_id);
600 	g_signal_handler_disconnect (
601 		eti->table_model,
602 		eti->table_model_cell_change_id);
603 	g_signal_handler_disconnect (
604 		eti->table_model,
605 		eti->table_model_rows_inserted_id);
606 	g_signal_handler_disconnect (
607 		eti->table_model,
608 		eti->table_model_rows_deleted_id);
609 	g_object_unref (eti->table_model);
610 	if (eti->source_model)
611 		g_object_unref (eti->source_model);
612 
613 	eti->table_model_pre_change_id = 0;
614 	eti->table_model_no_change_id = 0;
615 	eti->table_model_change_id = 0;
616 	eti->table_model_row_change_id = 0;
617 	eti->table_model_cell_change_id = 0;
618 	eti->table_model_rows_inserted_id = 0;
619 	eti->table_model_rows_deleted_id = 0;
620 	eti->table_model = NULL;
621 	eti->source_model = NULL;
622 	eti->uses_source_model = 0;
623 }
624 
625 /*
626  * eti_remove_table_model:
627  *
628  * Invoked to release the table model associated with this ETableItem
629  */
630 static void
eti_remove_selection_model(ETableItem * eti)631 eti_remove_selection_model (ETableItem *eti)
632 {
633 	if (!eti->selection)
634 		return;
635 
636 	g_signal_handler_disconnect (
637 		eti->selection,
638 		eti->selection_change_id);
639 	g_signal_handler_disconnect (
640 		eti->selection,
641 		eti->selection_row_change_id);
642 	g_signal_handler_disconnect (
643 		eti->selection,
644 		eti->cursor_change_id);
645 	g_signal_handler_disconnect (
646 		eti->selection,
647 		eti->cursor_activated_id);
648 	g_object_unref (eti->selection);
649 
650 	eti->selection_change_id = 0;
651 	eti->selection_row_change_id = 0;
652 	eti->cursor_activated_id = 0;
653 	eti->selection = NULL;
654 }
655 
656 /*
657  * eti_remove_header_model:
658  *
659  * Invoked to release the header model associated with this ETableItem
660  */
661 static void
eti_remove_header_model(ETableItem * eti)662 eti_remove_header_model (ETableItem *eti)
663 {
664 	if (!eti->header)
665 		return;
666 
667 	g_signal_handler_disconnect (
668 		eti->header,
669 		eti->header_structure_change_id);
670 	g_signal_handler_disconnect (
671 		eti->header,
672 		eti->header_dim_change_id);
673 	g_signal_handler_disconnect (
674 		eti->header,
675 		eti->header_request_width_id);
676 
677 	if (eti->cell_views) {
678 		eti_unrealize_cell_views (eti);
679 		eti_detach_cell_views (eti);
680 	}
681 	g_object_unref (eti->header);
682 
683 	eti->header_structure_change_id = 0;
684 	eti->header_dim_change_id = 0;
685 	eti->header_request_width_id = 0;
686 	eti->header = NULL;
687 }
688 
689 /*
690  * eti_row_height_real:
691  *
692  * Returns the height used by row @row.  This does not include the one-pixel
693  * used as a separator between rows
694  */
695 static gint
eti_row_height_real(ETableItem * eti,gint row)696 eti_row_height_real (ETableItem *eti,
697                      gint row)
698 {
699 	const gint cols = e_table_header_count (eti->header);
700 	gint col;
701 	gint h, max_h;
702 
703 	g_return_val_if_fail (cols == 0 || eti->cell_views, 0);
704 
705 	max_h = 0;
706 
707 	for (col = 0; col < cols; col++) {
708 		h = e_cell_height (eti->cell_views[col], view_to_model_col (eti, col), col, row);
709 
710 		if (h > max_h)
711 			max_h = h;
712 	}
713 	return max_h;
714 }
715 
716 static void
confirm_height_cache(ETableItem * eti)717 confirm_height_cache (ETableItem *eti)
718 {
719 	gint i;
720 
721 	if (eti->uniform_row_height || eti->height_cache)
722 		return;
723 	eti->height_cache = g_new (int, eti->rows);
724 	for (i = 0; i < eti->rows; i++) {
725 		eti->height_cache[i] = -1;
726 	}
727 }
728 
729 static gboolean
height_cache_idle(ETableItem * eti)730 height_cache_idle (ETableItem *eti)
731 {
732 	gint changed = 0;
733 	gint i;
734 	confirm_height_cache (eti);
735 	for (i = eti->height_cache_idle_count; i < eti->rows; i++) {
736 		if (eti->height_cache[i] == -1) {
737 			eti_row_height (eti, i);
738 			changed++;
739 			if (changed >= 20)
740 				break;
741 		}
742 	}
743 	if (changed >= 20) {
744 		eti->height_cache_idle_count = i;
745 		return TRUE;
746 	}
747 	eti->height_cache_idle_id = 0;
748 	return FALSE;
749 }
750 
751 static void
free_height_cache(ETableItem * eti)752 free_height_cache (ETableItem *eti)
753 {
754 	GnomeCanvasItem *item;
755 
756 	item = GNOME_CANVAS_ITEM (eti);
757 
758 	if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
759 		g_clear_pointer (&eti->height_cache, g_free);
760 		eti->height_cache_idle_count = 0;
761 		eti->uniform_row_height_cache = -1;
762 
763 		if (eti->uniform_row_height && eti->height_cache_idle_id != 0) {
764 			g_source_remove (eti->height_cache_idle_id);
765 			eti->height_cache_idle_id = 0;
766 		}
767 
768 		if ((!eti->uniform_row_height) && eti->height_cache_idle_id == 0)
769 			eti->height_cache_idle_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) height_cache_idle, eti, NULL);
770 	}
771 }
772 
773 static void
calculate_height_cache(ETableItem * eti)774 calculate_height_cache (ETableItem *eti)
775 {
776 	free_height_cache (eti);
777 	confirm_height_cache (eti);
778 }
779 
780 /*
781  * eti_row_height:
782  *
783  * Returns the height used by row @row.  This does not include the one-pixel
784  * used as a separator between rows
785  */
786 static gint
eti_row_height(ETableItem * eti,gint row)787 eti_row_height (ETableItem *eti,
788                 gint row)
789 {
790 	if (eti->uniform_row_height) {
791 		eti->uniform_row_height_cache = eti_row_height_real (eti, -1);
792 		return eti->uniform_row_height_cache;
793 	} else {
794 		if (!eti->height_cache) {
795 			calculate_height_cache (eti);
796 		}
797 		if (eti->height_cache[row] == -1) {
798 			eti->height_cache[row] = eti_row_height_real (eti, row);
799 			if (row > 0 &&
800 			    eti->length_threshold != -1 &&
801 			    eti->rows > eti->length_threshold &&
802 			    eti->height_cache[row] != eti_row_height (eti, 0)) {
803 				eti->needs_compute_height = 1;
804 				e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
805 			}
806 		}
807 		return eti->height_cache[row];
808 	}
809 }
810 
811 /*
812  * eti_get_height:
813  *
814  * Returns the height of the ETableItem.
815  *
816  * The ETableItem might compute the whole height by asking every row its
817  * size.  There is a special mode (designed to work when there are too
818  * many rows in the table that performing the previous step could take
819  * too long) set by the ETableItem->length_threshold that would determine
820  * when the height is computed by using the first row as the size for
821  * every other row in the ETableItem.
822  */
823 static gint
eti_get_height(ETableItem * eti)824 eti_get_height (ETableItem *eti)
825 {
826 	const gint rows = eti->rows;
827 	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
828 
829 	if (rows == 0)
830 		return 0;
831 
832 	if (eti->uniform_row_height) {
833 		gint row_height = ETI_ROW_HEIGHT (eti, -1);
834 		return ((row_height + height_extra) * rows + height_extra);
835 	} else {
836 		gint height;
837 		gint row;
838 		if (eti->length_threshold != -1) {
839 			if (rows > eti->length_threshold) {
840 				gint row_height = ETI_ROW_HEIGHT (eti, 0);
841 				if (eti->height_cache) {
842 					height = 0;
843 					for (row = 0; row < rows; row++) {
844 						if (eti->height_cache[row] == -1) {
845 							height += (row_height + height_extra) * (rows - row);
846 							break;
847 						}
848 						else
849 							height += eti->height_cache[row] + height_extra;
850 					}
851 				} else
852 					height = (ETI_ROW_HEIGHT (eti, 0) + height_extra) * rows;
853 
854 				/*
855 				 * 1 pixel at the top
856 				 */
857 				return height + height_extra;
858 			}
859 		}
860 
861 		height = height_extra;
862 		for (row = 0; row < rows; row++)
863 			height += ETI_ROW_HEIGHT (eti, row) + height_extra;
864 
865 		return height;
866 	}
867 }
868 
869 static void
eti_item_region_redraw(ETableItem * eti,gint x0,gint y0,gint x1,gint y1)870 eti_item_region_redraw (ETableItem *eti,
871                         gint x0,
872                         gint y0,
873                         gint x1,
874                         gint y1)
875 {
876 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
877 	gdouble dx1, dy1, dx2, dy2;
878 	cairo_matrix_t i2c;
879 
880 	dx1 = x0;
881 	dy1 = y0;
882 	dx2 = x1;
883 	dy2 = y1;
884 
885 	gnome_canvas_item_i2c_matrix (item, &i2c);
886 	gnome_canvas_matrix_transform_rect (&i2c, &dx1, &dy1, &dx2, &dy2);
887 
888 	gnome_canvas_request_redraw (item->canvas, floor (dx1), floor (dy1), ceil (dx2), ceil (dy2));
889 }
890 
891 /*
892  * Computes the distance between @start_row and @end_row in pixels
893  */
894 gint
e_table_item_row_diff(ETableItem * eti,gint start_row,gint end_row)895 e_table_item_row_diff (ETableItem *eti,
896                        gint start_row,
897                        gint end_row)
898 {
899 	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
900 
901 	if (start_row < 0)
902 		start_row = 0;
903 	if (end_row > eti->rows)
904 		end_row = eti->rows;
905 
906 	if (eti->uniform_row_height) {
907 		return ((end_row - start_row) * (ETI_ROW_HEIGHT (eti, -1) + height_extra));
908 	} else {
909 		gint row, total;
910 		total = 0;
911 		for (row = start_row; row < end_row; row++)
912 			total += ETI_ROW_HEIGHT (eti, row) + height_extra;
913 
914 		return total;
915 	}
916 }
917 
918 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)919 eti_get_region (ETableItem *eti,
920                 gint start_col,
921                 gint start_row,
922                 gint end_col,
923                 gint end_row,
924                 gint *x1p,
925                 gint *y1p,
926                 gint *x2p,
927                 gint *y2p)
928 {
929 	gint x1, y1, x2, y2;
930 
931 	x1 = e_table_header_col_diff (eti->header, 0, start_col);
932 	y1 = e_table_item_row_diff (eti, 0, start_row);
933 	x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1);
934 	y2 = y1 + e_table_item_row_diff (eti, start_row, end_row + 1);
935 	if (x1p)
936 		*x1p = x1;
937 	if (y1p)
938 		*y1p = y1;
939 	if (x2p)
940 		*x2p = x2;
941 	if (y2p)
942 		*y2p = y2;
943 }
944 
945 /*
946  * eti_request_region_redraw:
947  *
948  * Request a canvas redraw on the range (start_col, start_row) to (end_col, end_row).
949  * This is inclusive (ie, you can use: 0,0-0,0 to redraw the first cell).
950  *
951  * The @border argument is a number of pixels around the region that should also be queued
952  * for redraw.   This is typically used by the focus routines to queue a redraw for the
953  * border as well.
954  */
955 static void
eti_request_region_redraw(ETableItem * eti,gint start_col,gint start_row,gint end_col,gint end_row,gint border)956 eti_request_region_redraw (ETableItem *eti,
957                            gint start_col,
958                            gint start_row,
959                            gint end_col,
960                            gint end_row,
961                            gint border)
962 {
963 	gint x1, y1, x2, y2;
964 
965 	if (eti->rows > 0) {
966 
967 		eti_get_region (
968 			eti,
969 			start_col, start_row,
970 			end_col, end_row,
971 			&x1, &y1, &x2, &y2);
972 
973 		eti_item_region_redraw (
974 			eti,
975 			x1 - border,
976 			y1 - border,
977 			x2 + 1 + border,
978 			y2 + 1 + border);
979 	}
980 }
981 
982 /*
983  * eti_request_region_show
984  *
985  * Request a canvas show on the range (start_col, start_row) to (end_col, end_row).
986  * This is inclusive (ie, you can use: 0,0-0,0 to show the first cell).
987  */
988 static void
eti_request_region_show(ETableItem * eti,gint start_col,gint start_row,gint end_col,gint end_row,gint delay)989 eti_request_region_show (ETableItem *eti,
990                          gint start_col,
991                          gint start_row,
992                          gint end_col,
993                          gint end_row,
994                          gint delay)
995 {
996 	ETableItemPrivate *priv = E_TABLE_ITEM_GET_PRIVATE (eti);
997 	gint x1, y1, x2, y2;
998 
999 	if (priv->show_cursor_delay_source) {
1000 		g_source_destroy (priv->show_cursor_delay_source);
1001 		g_source_unref (priv->show_cursor_delay_source);
1002 		priv->show_cursor_delay_source = NULL;
1003 	}
1004 
1005 	eti_get_region (
1006 		eti,
1007 		start_col, start_row,
1008 		end_col, end_row,
1009 		&x1, &y1, &x2, &y2);
1010 
1011 	if (delay)
1012 		priv->show_cursor_delay_source = e_canvas_item_show_area_delayed_ex (
1013 			GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2, delay);
1014 	else
1015 		e_canvas_item_show_area (
1016 			GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);
1017 }
1018 
1019 static void
eti_show_cursor(ETableItem * eti,gint delay)1020 eti_show_cursor (ETableItem *eti,
1021                  gint delay)
1022 {
1023 	GnomeCanvasItem *item;
1024 	gint cursor_row;
1025 
1026 	item = GNOME_CANVAS_ITEM (eti);
1027 
1028 	if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
1029 		return;
1030 
1031 	if (eti->frozen_count > 0) {
1032 		eti->queue_show_cursor = TRUE;
1033 		return;
1034 	}
1035 
1036 #if 0
1037 	g_object_get (
1038 		eti->selection,
1039 		"cursor_row", &cursor_row,
1040 		NULL);
1041 #else
1042 	cursor_row = e_selection_model_cursor_row (eti->selection);
1043 #endif
1044 
1045 	d (g_print ("%s: cursor row: %d\n", G_STRFUNC, cursor_row));
1046 
1047 	if (cursor_row != -1) {
1048 		cursor_row = model_to_view_row (eti, cursor_row);
1049 		eti_request_region_show (
1050 			eti,
1051 			0, cursor_row, eti->cols - 1, cursor_row,
1052 			delay);
1053 	}
1054 }
1055 
1056 static void
eti_check_cursor_bounds(ETableItem * eti)1057 eti_check_cursor_bounds (ETableItem *eti)
1058 {
1059 	GnomeCanvasItem *item;
1060 	gint x1, y1, x2, y2;
1061 	gint cursor_row;
1062 
1063 	item = GNOME_CANVAS_ITEM (eti);
1064 
1065 	if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
1066 		return;
1067 
1068 	if (eti->frozen_count > 0) {
1069 		return;
1070 	}
1071 
1072 	g_object_get (
1073 		eti->selection,
1074 		"cursor_row", &cursor_row,
1075 		NULL);
1076 
1077 	if (cursor_row == -1) {
1078 		eti->cursor_x1 = -1;
1079 		eti->cursor_y1 = -1;
1080 		eti->cursor_x2 = -1;
1081 		eti->cursor_y2 = -1;
1082 		eti->cursor_on_screen = TRUE;
1083 		return;
1084 	}
1085 
1086 	d (g_print ("%s: model cursor row: %d\n", G_STRFUNC, cursor_row));
1087 
1088 	cursor_row = model_to_view_row (eti, cursor_row);
1089 
1090 	d (g_print ("%s: cursor row: %d\n", G_STRFUNC, cursor_row));
1091 
1092 	eti_get_region (
1093 		eti,
1094 		0, cursor_row, eti->cols - 1, cursor_row,
1095 		&x1, &y1, &x2, &y2);
1096 	eti->cursor_x1 = x1;
1097 	eti->cursor_y1 = y1;
1098 	eti->cursor_x2 = x2;
1099 	eti->cursor_y2 = y2;
1100 	eti->cursor_on_screen = e_canvas_item_area_shown (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);
1101 
1102 	d (g_print ("%s: cursor on screen: %s\n", G_STRFUNC, eti->cursor_on_screen ? "TRUE" : "FALSE"));
1103 }
1104 
1105 static void
eti_maybe_show_cursor(ETableItem * eti,gint delay)1106 eti_maybe_show_cursor (ETableItem *eti,
1107                        gint delay)
1108 {
1109 	d (g_print ("%s: cursor on screen: %s\n", G_STRFUNC, eti->cursor_on_screen ? "TRUE" : "FALSE"));
1110 	if (eti->cursor_on_screen)
1111 		eti_show_cursor (eti, delay);
1112 	eti_check_cursor_bounds (eti);
1113 }
1114 
1115 static gboolean
eti_idle_show_cursor_cb(gpointer data)1116 eti_idle_show_cursor_cb (gpointer data)
1117 {
1118 	ETableItem *eti = data;
1119 
1120 	if (eti->selection) {
1121 		eti_show_cursor (eti, 0);
1122 		eti_check_cursor_bounds (eti);
1123 	}
1124 
1125 	eti->cursor_idle_id = 0;
1126 	g_object_unref (eti);
1127 	return FALSE;
1128 }
1129 
1130 static void
eti_idle_maybe_show_cursor(ETableItem * eti)1131 eti_idle_maybe_show_cursor (ETableItem *eti)
1132 {
1133 	d (g_print ("%s: cursor on screen: %s\n", G_STRFUNC, eti->cursor_on_screen ? "TRUE" : "FALSE"));
1134 	if (eti->cursor_on_screen) {
1135 		g_object_ref (eti);
1136 		if (!eti->cursor_idle_id)
1137 			eti->cursor_idle_id = g_idle_add (eti_idle_show_cursor_cb, eti);
1138 	}
1139 }
1140 
1141 static void
eti_cancel_drag_due_to_model_change(ETableItem * eti)1142 eti_cancel_drag_due_to_model_change (ETableItem *eti)
1143 {
1144 	if (eti->maybe_in_drag) {
1145 		eti->maybe_in_drag = FALSE;
1146 		if (!eti->maybe_did_something)
1147 			e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
1148 	}
1149 	if (eti->in_drag) {
1150 		eti->in_drag = FALSE;
1151 	}
1152 }
1153 
1154 static void
eti_freeze(ETableItem * eti)1155 eti_freeze (ETableItem *eti)
1156 {
1157 	eti->frozen_count++;
1158 	d (g_print ("%s: %d\n", G_STRFUNC, eti->frozen_count));
1159 }
1160 
1161 static void
eti_unfreeze(ETableItem * eti)1162 eti_unfreeze (ETableItem *eti)
1163 {
1164 	if (eti->frozen_count <= 0)
1165 		return;
1166 
1167 	eti->frozen_count--;
1168 	d (g_print ("%s: %d\n", G_STRFUNC, eti->frozen_count));
1169 	if (eti->frozen_count == 0 && eti->queue_show_cursor) {
1170 		eti_show_cursor (eti, 0);
1171 		eti_check_cursor_bounds (eti);
1172 		eti->queue_show_cursor = FALSE;
1173 	}
1174 }
1175 
1176 void
e_table_item_freeze(ETableItem * eti)1177 e_table_item_freeze (ETableItem *eti)
1178 {
1179 	if (!eti)
1180 		return;
1181 
1182 	eti_freeze (eti);
1183 }
1184 
1185 void
e_table_item_thaw(ETableItem * eti)1186 e_table_item_thaw (ETableItem *eti)
1187 {
1188 	if (!eti)
1189 		return;
1190 
1191 	eti_unfreeze (eti);
1192 }
1193 
1194 /*
1195  * Callback routine: invoked before the ETableModel suffers a change
1196  */
1197 static void
eti_table_model_pre_change(ETableModel * table_model,ETableItem * eti)1198 eti_table_model_pre_change (ETableModel *table_model,
1199                             ETableItem *eti)
1200 {
1201 	eti_cancel_drag_due_to_model_change (eti);
1202 	eti_check_cursor_bounds (eti);
1203 	if (eti_editing (eti))
1204 		e_table_item_leave_edit_(eti);
1205 	eti->motion_row = -1;
1206 	eti->motion_col = -1;
1207 	eti_freeze (eti);
1208 }
1209 
1210 /*
1211  * Callback routine: invoked when the ETableModel has not suffered a change
1212  */
1213 static void
eti_table_model_no_change(ETableModel * table_model,ETableItem * eti)1214 eti_table_model_no_change (ETableModel *table_model,
1215                            ETableItem *eti)
1216 {
1217 	eti_unfreeze (eti);
1218 }
1219 
1220 /*
1221  * Callback routine: invoked when the ETableModel has suffered a change
1222  */
1223 
1224 static void
eti_table_model_changed(ETableModel * table_model,ETableItem * eti)1225 eti_table_model_changed (ETableModel *table_model,
1226                          ETableItem *eti)
1227 {
1228 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
1229 
1230 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
1231 		eti_unfreeze (eti);
1232 		return;
1233 	}
1234 
1235 	eti->rows = e_table_model_row_count (eti->table_model);
1236 
1237 	free_height_cache (eti);
1238 
1239 	eti_unfreeze (eti);
1240 
1241 	eti->needs_compute_height = 1;
1242 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
1243 	eti->needs_redraw = 1;
1244 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
1245 
1246 	eti_idle_maybe_show_cursor (eti);
1247 }
1248 
1249 static void
eti_table_model_row_changed(ETableModel * table_model,gint row,ETableItem * eti)1250 eti_table_model_row_changed (ETableModel *table_model,
1251                              gint row,
1252                              ETableItem *eti)
1253 {
1254 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
1255 
1256 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
1257 		eti_unfreeze (eti);
1258 		return;
1259 	}
1260 
1261 	if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
1262 		eti_table_model_changed (table_model, eti);
1263 		return;
1264 	}
1265 
1266 	eti_unfreeze (eti);
1267 
1268 	e_table_item_redraw_row (eti, row);
1269 }
1270 
1271 static void
eti_table_model_cell_changed(ETableModel * table_model,gint col,gint row,ETableItem * eti)1272 eti_table_model_cell_changed (ETableModel *table_model,
1273                               gint col,
1274                               gint row,
1275                               ETableItem *eti)
1276 {
1277 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
1278 
1279 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
1280 		eti_unfreeze (eti);
1281 		return;
1282 	}
1283 
1284 	if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
1285 		eti_table_model_changed (table_model, eti);
1286 		return;
1287 	}
1288 
1289 	eti_unfreeze (eti);
1290 
1291 	e_table_item_redraw_row (eti, row);
1292 }
1293 
1294 static void
eti_table_model_rows_inserted(ETableModel * table_model,gint row,gint count,ETableItem * eti)1295 eti_table_model_rows_inserted (ETableModel *table_model,
1296                                gint row,
1297                                gint count,
1298                                ETableItem *eti)
1299 {
1300 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
1301 
1302 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
1303 		eti_unfreeze (eti);
1304 		return;
1305 	}
1306 	eti->rows = e_table_model_row_count (eti->table_model);
1307 
1308 	if (eti->height_cache) {
1309 		gint i;
1310 		eti->height_cache = g_renew (int, eti->height_cache, eti->rows);
1311 		memmove (eti->height_cache + row + count, eti->height_cache + row, (eti->rows - count - row) * sizeof (gint));
1312 		for (i = row; i < row + count; i++)
1313 			eti->height_cache[i] = -1;
1314 	}
1315 
1316 	eti_unfreeze (eti);
1317 
1318 	eti_idle_maybe_show_cursor (eti);
1319 
1320 	eti->needs_compute_height = 1;
1321 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
1322 	eti->needs_redraw = 1;
1323 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
1324 }
1325 
1326 static void
eti_table_model_rows_deleted(ETableModel * table_model,gint row,gint count,ETableItem * eti)1327 eti_table_model_rows_deleted (ETableModel *table_model,
1328                               gint row,
1329                               gint count,
1330                               ETableItem *eti)
1331 {
1332 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
1333 
1334 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
1335 		eti_unfreeze (eti);
1336 		return;
1337 	}
1338 
1339 	eti->rows = e_table_model_row_count (eti->table_model);
1340 
1341 	if (eti->height_cache && (eti->rows > row)) {
1342 		memmove (eti->height_cache + row, eti->height_cache + row + count, (eti->rows - row) * sizeof (gint));
1343 	}
1344 
1345 	eti_unfreeze (eti);
1346 
1347 	eti_idle_maybe_show_cursor (eti);
1348 
1349 	eti->needs_compute_height = 1;
1350 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
1351 	eti->needs_redraw = 1;
1352 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
1353 }
1354 
1355 /**
1356  * e_table_item_redraw_range
1357  * @eti: %ETableItem which will be redrawn
1358  * @start_col: The first col to redraw.
1359  * @start_row: The first row to redraw.
1360  * @end_col: The last col to redraw.
1361  * @end_row: The last row to redraw.
1362  *
1363  * This routine redraws the given %ETableItem in the range given.  The
1364  * range is inclusive at both ends.
1365  */
1366 void
e_table_item_redraw_range(ETableItem * eti,gint start_col,gint start_row,gint end_col,gint end_row)1367 e_table_item_redraw_range (ETableItem *eti,
1368                            gint start_col,
1369                            gint start_row,
1370                            gint end_col,
1371                            gint end_row)
1372 {
1373 	gint border;
1374 	gint cursor_col, cursor_row;
1375 
1376 	g_return_if_fail (eti != NULL);
1377 	g_return_if_fail (E_IS_TABLE_ITEM (eti));
1378 
1379 	g_object_get (
1380 		eti->selection,
1381 		"cursor_col", &cursor_col,
1382 		"cursor_row", &cursor_row,
1383 		NULL);
1384 
1385 	if ((start_col == cursor_col) ||
1386 	    (end_col == cursor_col) ||
1387 	    (view_to_model_row (eti, start_row) == cursor_row) ||
1388 	    (view_to_model_row (eti, end_row) == cursor_row))
1389 		border = 2;
1390 	else
1391 		border = 0;
1392 
1393 	eti_request_region_redraw (eti, start_col, start_row, end_col, end_row, border);
1394 }
1395 
1396 static void
e_table_item_redraw_row(ETableItem * eti,gint row)1397 e_table_item_redraw_row (ETableItem *eti,
1398                          gint row)
1399 {
1400 	if (row != -1)
1401 		e_table_item_redraw_range (eti, 0, row, eti->cols - 1, row);
1402 }
1403 
1404 static void
eti_add_table_model(ETableItem * eti,ETableModel * table_model)1405 eti_add_table_model (ETableItem *eti,
1406                      ETableModel *table_model)
1407 {
1408 	g_return_if_fail (eti->table_model == NULL);
1409 
1410 	eti->table_model = table_model;
1411 	g_object_ref (eti->table_model);
1412 
1413 	eti->table_model_pre_change_id = g_signal_connect (
1414 		table_model, "model_pre_change",
1415 		G_CALLBACK (eti_table_model_pre_change), eti);
1416 
1417 	eti->table_model_no_change_id = g_signal_connect (
1418 		table_model, "model_no_change",
1419 		G_CALLBACK (eti_table_model_no_change), eti);
1420 
1421 	eti->table_model_change_id = g_signal_connect (
1422 		table_model, "model_changed",
1423 		G_CALLBACK (eti_table_model_changed), eti);
1424 
1425 	eti->table_model_row_change_id = g_signal_connect (
1426 		table_model, "model_row_changed",
1427 		G_CALLBACK (eti_table_model_row_changed), eti);
1428 
1429 	eti->table_model_cell_change_id = g_signal_connect (
1430 		table_model, "model_cell_changed",
1431 		G_CALLBACK (eti_table_model_cell_changed), eti);
1432 
1433 	eti->table_model_rows_inserted_id = g_signal_connect (
1434 		table_model, "model_rows_inserted",
1435 		G_CALLBACK (eti_table_model_rows_inserted), eti);
1436 
1437 	eti->table_model_rows_deleted_id = g_signal_connect (
1438 		table_model, "model_rows_deleted",
1439 		G_CALLBACK (eti_table_model_rows_deleted), eti);
1440 
1441 	if (eti->header) {
1442 		eti_detach_cell_views (eti);
1443 		eti_attach_cell_views (eti);
1444 	}
1445 
1446 	if (E_IS_TABLE_SUBSET (table_model)) {
1447 		eti->uses_source_model = 1;
1448 		eti->source_model = e_table_subset_get_source_model (
1449 			E_TABLE_SUBSET (table_model));
1450 		if (eti->source_model)
1451 			g_object_ref (eti->source_model);
1452 	}
1453 
1454 	eti_freeze (eti);
1455 
1456 	eti_table_model_changed (table_model, eti);
1457 }
1458 
1459 static void
eti_add_selection_model(ETableItem * eti,ESelectionModel * selection)1460 eti_add_selection_model (ETableItem *eti,
1461                          ESelectionModel *selection)
1462 {
1463 	g_return_if_fail (eti->selection == NULL);
1464 
1465 	eti->selection = selection;
1466 	g_object_ref (eti->selection);
1467 
1468 	eti->selection_change_id = g_signal_connect (
1469 		selection, "selection_changed",
1470 		G_CALLBACK (eti_selection_change), eti);
1471 
1472 	eti->selection_row_change_id = g_signal_connect (
1473 		selection, "selection_row_changed",
1474 		G_CALLBACK (eti_selection_row_change), eti);
1475 
1476 	eti->cursor_change_id = g_signal_connect (
1477 		selection, "cursor_changed",
1478 		G_CALLBACK (eti_cursor_change), eti);
1479 
1480 	eti->cursor_activated_id = g_signal_connect (
1481 		selection, "cursor_activated",
1482 		G_CALLBACK (eti_cursor_activated), eti);
1483 
1484 	eti_selection_change (selection, eti);
1485 	g_signal_emit_by_name (eti, "selection_model_added", eti->selection);
1486 }
1487 
1488 static void
eti_header_dim_changed(ETableHeader * eth,gint col,ETableItem * eti)1489 eti_header_dim_changed (ETableHeader *eth,
1490                         gint col,
1491                         ETableItem *eti)
1492 {
1493 	eti->needs_compute_width = 1;
1494 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
1495 	eti->needs_redraw = 1;
1496 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
1497 }
1498 
1499 static void
eti_header_structure_changed(ETableHeader * eth,ETableItem * eti)1500 eti_header_structure_changed (ETableHeader *eth,
1501                               ETableItem *eti)
1502 {
1503 	eti->cols = e_table_header_count (eti->header);
1504 
1505 	/*
1506 	 * There should be at least one column
1507 	 *  BUT: then you can't remove all columns from a header and add new ones.
1508 	 */
1509 
1510 	if (eti->cell_views) {
1511 		eti_unrealize_cell_views (eti);
1512 		eti_detach_cell_views (eti);
1513 		eti_attach_cell_views (eti);
1514 		eti_realize_cell_views (eti);
1515 	} else {
1516 		if (eti->table_model) {
1517 			eti_attach_cell_views (eti);
1518 			eti_realize_cell_views (eti);
1519 		}
1520 	}
1521 	eti->needs_compute_width = 1;
1522 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
1523 	eti->needs_redraw = 1;
1524 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
1525 }
1526 
1527 static gint
eti_request_column_width(ETableHeader * eth,gint col,ETableItem * eti)1528 eti_request_column_width (ETableHeader *eth,
1529                           gint col,
1530                           ETableItem *eti)
1531 {
1532 	gint width = 0;
1533 
1534 	if (eti->cell_views && eti->cell_views_realized) {
1535 		width = e_cell_max_width (eti->cell_views[col], view_to_model_col (eti, col), col);
1536 	}
1537 
1538 	return width;
1539 }
1540 
1541 static void
eti_add_header_model(ETableItem * eti,ETableHeader * header)1542 eti_add_header_model (ETableItem *eti,
1543                       ETableHeader *header)
1544 {
1545 	g_return_if_fail (eti->header == NULL);
1546 
1547 	eti->header = header;
1548 	g_object_ref (header);
1549 
1550 	eti_header_structure_changed (header, eti);
1551 
1552 	eti->header_dim_change_id = g_signal_connect (
1553 		header, "dimension_change",
1554 		G_CALLBACK (eti_header_dim_changed), eti);
1555 
1556 	eti->header_structure_change_id = g_signal_connect (
1557 		header, "structure_change",
1558 		G_CALLBACK (eti_header_structure_changed), eti);
1559 
1560 	eti->header_request_width_id = g_signal_connect (
1561 		header, "request_width",
1562 		G_CALLBACK (eti_request_column_width), eti);
1563 }
1564 
1565 /*
1566  * GObject::dispose method
1567  */
1568 static void
eti_dispose(GObject * object)1569 eti_dispose (GObject *object)
1570 {
1571 	ETableItem *eti = E_TABLE_ITEM (object);
1572 	ETableItemPrivate *priv = E_TABLE_ITEM_GET_PRIVATE (eti);
1573 
1574 	if (priv->show_cursor_delay_source) {
1575 		g_source_destroy (priv->show_cursor_delay_source);
1576 		g_source_unref (priv->show_cursor_delay_source);
1577 		priv->show_cursor_delay_source = NULL;
1578 	}
1579 
1580 	eti_remove_header_model (eti);
1581 	eti_remove_table_model (eti);
1582 	eti_remove_selection_model (eti);
1583 
1584 	if (eti->height_cache_idle_id) {
1585 		g_source_remove (eti->height_cache_idle_id);
1586 		eti->height_cache_idle_id = 0;
1587 	}
1588 	eti->height_cache_idle_count = 0;
1589 
1590 	if (eti->cursor_idle_id) {
1591 		g_source_remove (eti->cursor_idle_id);
1592 		eti->cursor_idle_id = 0;
1593 	}
1594 
1595 	g_clear_pointer (&eti->height_cache, g_free);
1596 
1597 	/* Chain up to parent's dispose() method. */
1598 	G_OBJECT_CLASS (e_table_item_parent_class)->dispose (object);
1599 }
1600 
1601 static void
eti_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1602 eti_set_property (GObject *object,
1603                   guint property_id,
1604                   const GValue *value,
1605                   GParamSpec *pspec)
1606 {
1607 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (object);
1608 	ETableItem *eti = E_TABLE_ITEM (object);
1609 	gint cursor_col;
1610 
1611 	switch (property_id) {
1612 	case PROP_TABLE_HEADER:
1613 		eti_remove_header_model (eti);
1614 		eti_add_header_model (eti, E_TABLE_HEADER (g_value_get_object (value)));
1615 		break;
1616 
1617 	case PROP_TABLE_MODEL:
1618 		eti_remove_table_model (eti);
1619 		eti_add_table_model (eti, E_TABLE_MODEL (g_value_get_object (value)));
1620 		break;
1621 
1622 	case PROP_SELECTION_MODEL:
1623 		g_signal_emit_by_name (
1624 			eti, "selection_model_removed", eti->selection);
1625 		eti_remove_selection_model (eti);
1626 		if (g_value_get_object (value))
1627 			eti_add_selection_model (eti, E_SELECTION_MODEL (g_value_get_object (value)));
1628 		break;
1629 
1630 	case PROP_LENGTH_THRESHOLD:
1631 		eti->length_threshold = g_value_get_int (value);
1632 		break;
1633 
1634 	case PROP_TABLE_ALTERNATING_ROW_COLORS:
1635 		eti->alternating_row_colors = g_value_get_boolean (value);
1636 		break;
1637 
1638 	case PROP_TABLE_HORIZONTAL_DRAW_GRID:
1639 		eti->horizontal_draw_grid = g_value_get_boolean (value);
1640 		break;
1641 
1642 	case PROP_TABLE_VERTICAL_DRAW_GRID:
1643 		eti->vertical_draw_grid = g_value_get_boolean (value);
1644 		break;
1645 
1646 	case PROP_TABLE_DRAW_FOCUS:
1647 		eti->draw_focus = g_value_get_boolean (value);
1648 		break;
1649 
1650 	case PROP_CURSOR_MODE:
1651 		eti->cursor_mode = g_value_get_int (value);
1652 		break;
1653 
1654 	case PROP_MINIMUM_WIDTH:
1655 	case PROP_WIDTH:
1656 		if ((eti->minimum_width == eti->width && g_value_get_double (value) > eti->width) ||
1657 		    g_value_get_double (value) < eti->width) {
1658 			eti->needs_compute_width = 1;
1659 			e_canvas_item_request_reflow (item);
1660 		}
1661 		eti->minimum_width = g_value_get_double (value);
1662 		break;
1663 	case PROP_CURSOR_ROW:
1664 		g_object_get (
1665 			eti->selection,
1666 			"cursor_col", &cursor_col,
1667 			NULL);
1668 
1669 		e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row (eti, g_value_get_int (value)), 0);
1670 		break;
1671 	case PROP_UNIFORM_ROW_HEIGHT:
1672 		if (eti->uniform_row_height != g_value_get_boolean (value)) {
1673 			eti->uniform_row_height = g_value_get_boolean (value);
1674 			if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
1675 				free_height_cache (eti);
1676 				eti->needs_compute_height = 1;
1677 				e_canvas_item_request_reflow (item);
1678 				eti->needs_redraw = 1;
1679 				gnome_canvas_item_request_update (item);
1680 			}
1681 		}
1682 		break;
1683 	}
1684 	eti->needs_redraw = 1;
1685 	gnome_canvas_item_request_update (item);
1686 }
1687 
1688 static void
eti_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1689 eti_get_property (GObject *object,
1690                   guint property_id,
1691                   GValue *value,
1692                   GParamSpec *pspec)
1693 {
1694 	ETableItem *eti;
1695 	gint row;
1696 
1697 	eti = E_TABLE_ITEM (object);
1698 
1699 	switch (property_id) {
1700 	case PROP_WIDTH:
1701 		g_value_set_double (value, eti->width);
1702 		break;
1703 	case PROP_HEIGHT:
1704 		g_value_set_double (value, eti->height);
1705 		break;
1706 	case PROP_MINIMUM_WIDTH:
1707 		g_value_set_double (value, eti->minimum_width);
1708 		break;
1709 	case PROP_CURSOR_ROW:
1710 		g_object_get (
1711 			eti->selection,
1712 			"cursor_row", &row,
1713 			NULL);
1714 		g_value_set_int (value, model_to_view_row (eti, row));
1715 		break;
1716 	case PROP_UNIFORM_ROW_HEIGHT:
1717 		g_value_set_boolean (value, eti->uniform_row_height);
1718 		break;
1719 	case PROP_IS_EDITING:
1720 		g_value_set_boolean (value, e_table_item_is_editing (eti));
1721 		break;
1722 	default:
1723 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1724 		break;
1725 	}
1726 }
1727 
1728 static void
e_table_item_init(ETableItem * eti)1729 e_table_item_init (ETableItem *eti)
1730 {
1731 	/* eti->priv = E_TABLE_ITEM_GET_PRIVATE (eti); */
1732 
1733 	eti->motion_row = -1;
1734 	eti->motion_col = -1;
1735 	eti->editing_col = -1;
1736 	eti->editing_row = -1;
1737 	eti->height = 0;
1738 	eti->width = 0;
1739 	eti->minimum_width = 0;
1740 
1741 	eti->save_col = -1;
1742 	eti->save_row = -1;
1743 	eti->save_state = NULL;
1744 
1745 	eti->click_count = 0;
1746 
1747 	eti->height_cache = NULL;
1748 	eti->height_cache_idle_id = 0;
1749 	eti->height_cache_idle_count = 0;
1750 
1751 	eti->length_threshold = -1;
1752 	eti->uniform_row_height = FALSE;
1753 
1754 	eti->uses_source_model = 0;
1755 	eti->source_model = NULL;
1756 
1757 	eti->row_guess = -1;
1758 	eti->cursor_mode = E_CURSOR_SIMPLE;
1759 
1760 	eti->selection_change_id = 0;
1761 	eti->selection_row_change_id = 0;
1762 	eti->cursor_change_id = 0;
1763 	eti->cursor_activated_id = 0;
1764 	eti->selection = NULL;
1765 
1766 	eti->old_cursor_row = -1;
1767 
1768 	eti->needs_redraw = 0;
1769 	eti->needs_compute_height = 0;
1770 
1771 	eti->in_key_press = 0;
1772 
1773 	eti->maybe_did_something = TRUE;
1774 
1775 	eti->grabbed_count = 0;
1776 	eti->gtk_grabbed = 0;
1777 
1778 	eti->in_drag = 0;
1779 	eti->maybe_in_drag = 0;
1780 
1781 	eti->grabbed_col = -1;
1782 	eti->grabbed_row = -1;
1783 
1784 	eti->cursor_on_screen = FALSE;
1785 	eti->cursor_x1 = -1;
1786 	eti->cursor_y1 = -1;
1787 	eti->cursor_x2 = -1;
1788 	eti->cursor_y2 = -1;
1789 
1790 	eti->rows = -1;
1791 	eti->cols = -1;
1792 
1793 	eti->frozen_count = 0;
1794 	eti->queue_show_cursor = FALSE;
1795 
1796 	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow);
1797 }
1798 
1799 static gboolean
eti_tree_unfreeze(GtkWidget * widget,GdkEvent * event,ETableItem * eti)1800 eti_tree_unfreeze (GtkWidget *widget,
1801                    GdkEvent *event,
1802                    ETableItem *eti)
1803 {
1804 
1805 	if (widget)
1806 		g_object_set_data (G_OBJECT (widget), "freeze-cursor", NULL);
1807 
1808 	return FALSE;
1809 }
1810 
1811 static void
eti_realize(GnomeCanvasItem * item)1812 eti_realize (GnomeCanvasItem *item)
1813 {
1814 	ETableItem *eti = E_TABLE_ITEM (item);
1815 
1816 	if (GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->realize)
1817 		(*GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->realize)(item);
1818 
1819 	eti->rows = e_table_model_row_count (eti->table_model);
1820 
1821 	g_signal_connect (
1822 		item->canvas, "scroll_event",
1823 		G_CALLBACK (eti_tree_unfreeze), eti);
1824 
1825 	if (eti->cell_views == NULL)
1826 		eti_attach_cell_views (eti);
1827 
1828 	eti_realize_cell_views (eti);
1829 
1830 	free_height_cache (eti);
1831 
1832 	if (item->canvas->focused_item == NULL && eti->selection) {
1833 		gint row;
1834 
1835 		row = e_selection_model_cursor_row (E_SELECTION_MODEL (eti->selection));
1836 		row = model_to_view_row (eti, row);
1837 		if (row != -1) {
1838 			e_canvas_item_grab_focus (item, FALSE);
1839 			eti_show_cursor (eti, 0);
1840 			eti_check_cursor_bounds (eti);
1841 		}
1842 	}
1843 
1844 	eti->needs_compute_height = 1;
1845 	eti->needs_compute_width = 1;
1846 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
1847 	eti->needs_redraw = 1;
1848 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
1849 }
1850 
1851 static void
eti_unrealize(GnomeCanvasItem * item)1852 eti_unrealize (GnomeCanvasItem *item)
1853 {
1854 	ETableItem *eti = E_TABLE_ITEM (item);
1855 
1856 	if (eti->grabbed_count > 0) {
1857 		d (g_print ("%s: eti_ungrab\n", G_STRFUNC));
1858 		eti_ungrab (eti, -1);
1859 	}
1860 
1861 	if (eti_editing (eti))
1862 		e_table_item_leave_edit_(eti);
1863 
1864 	if (eti->height_cache_idle_id) {
1865 		g_source_remove (eti->height_cache_idle_id);
1866 		eti->height_cache_idle_id = 0;
1867 	}
1868 
1869 	g_clear_pointer (&eti->height_cache, g_free);
1870 	eti->height_cache_idle_count = 0;
1871 
1872 	eti_unrealize_cell_views (eti);
1873 
1874 	eti->height = 0;
1875 
1876 	if (GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->unrealize)
1877 		(*GNOME_CANVAS_ITEM_CLASS (e_table_item_parent_class)->unrealize)(item);
1878 }
1879 
1880 gboolean
e_table_item_get_row_selected(ETableItem * eti,gint row)1881 e_table_item_get_row_selected (ETableItem *eti,
1882 			       gint row)
1883 {
1884 	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), FALSE);
1885 
1886 	return row >= 0 && row < eti->rows &&
1887 		e_selection_model_is_row_selected (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti, row));
1888 }
1889 
1890 static void
eti_draw_grid_line(ETableItem * eti,cairo_t * cr,const GdkRGBA * rgba,gint x1,gint y1,gint x2,gint y2)1891 eti_draw_grid_line (ETableItem *eti,
1892                     cairo_t *cr,
1893                     const GdkRGBA *rgba,
1894                     gint x1,
1895                     gint y1,
1896                     gint x2,
1897                     gint y2)
1898 {
1899 	cairo_save (cr);
1900 
1901 	cairo_set_line_width (cr, 0.5);
1902 	gdk_cairo_set_source_rgba (cr, rgba);
1903 
1904 	cairo_move_to (cr, x1 - 0.5, y1 + 0.5);
1905 	cairo_line_to (cr, x2 - 0.5, y2 + 0.5);
1906 	cairo_stroke (cr);
1907 
1908 	cairo_restore (cr);
1909 }
1910 
1911 static void
eti_draw(GnomeCanvasItem * item,cairo_t * cr,gint x,gint y,gint width,gint height)1912 eti_draw (GnomeCanvasItem *item,
1913           cairo_t *cr,
1914           gint x,
1915           gint y,
1916           gint width,
1917           gint height)
1918 {
1919 	ETableItem *eti = E_TABLE_ITEM (item);
1920 	const gint rows = eti->rows;
1921 	const gint cols = eti->cols;
1922 	gint row, col;
1923 	gint first_col, last_col, x_offset;
1924 	gint first_row, last_row, y_offset, yd;
1925 	gint x1, x2;
1926 	gint f_x1, f_x2, f_y1, f_y2;
1927 	gboolean f_found;
1928 	cairo_matrix_t i2c;
1929 	gdouble eti_base_x, eti_base_y, lower_right_y, lower_right_x;
1930 	GtkWidget *canvas = GTK_WIDGET (item->canvas);
1931 	GdkRGBA line_color;
1932 	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
1933 
1934 	e_utils_get_theme_color (canvas, "theme_bg_color", E_UTILS_DEFAULT_THEME_BG_COLOR, &line_color);
1935 	e_utils_shade_color (&line_color, &line_color, E_UTILS_DARKNESS_MULT);
1936 
1937 	/*
1938 	 * Find out our real position after grouping
1939 	 */
1940 	gnome_canvas_item_i2c_matrix (item, &i2c);
1941 	eti_base_x = 0;
1942 	eti_base_y = 0;
1943 	cairo_matrix_transform_point (&i2c, &eti_base_x, &eti_base_y);
1944 
1945 	lower_right_x = eti->width;
1946 	lower_right_y = eti->height;
1947 	cairo_matrix_transform_point (&i2c, &lower_right_x, &lower_right_y);
1948 
1949 	/*
1950 	 * First column to draw, last column to draw
1951 	 */
1952 	first_col = -1;
1953 	x_offset = 0;
1954 	x1 = floor (eti_base_x);
1955 	for (col = 0; col < cols; col++, x1 = x2) {
1956 		ETableCol *ecol = e_table_header_get_column (eti->header, col);
1957 
1958 		x2 = x1 + ecol->width;
1959 
1960 		if (x1 > (x + width))
1961 			break;
1962 		if (x2 < x)
1963 			continue;
1964 		if (first_col == -1) {
1965 			x_offset = x1 - x;
1966 			first_col = col;
1967 		}
1968 	}
1969 	last_col = col;
1970 
1971 	/*
1972 	 * Nothing to paint
1973 	 */
1974 	if (first_col == -1)
1975 		return;
1976 
1977 	/*
1978 	 * Compute row span.
1979 	 */
1980 	if (eti->uniform_row_height) {
1981 		first_row = (y - floor (eti_base_y) - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
1982 		last_row = (y + height - floor (eti_base_y)               ) / (ETI_ROW_HEIGHT (eti, -1) + height_extra) + 1;
1983 		if (first_row > last_row)
1984 			return;
1985 		y_offset = floor (eti_base_y) - y + height_extra + first_row * (ETI_ROW_HEIGHT (eti, -1) + height_extra);
1986 		if (first_row < 0)
1987 			first_row = 0;
1988 		if (last_row > eti->rows)
1989 			last_row = eti->rows;
1990 	} else {
1991 		gint y1, y2;
1992 
1993 		y_offset = 0;
1994 		first_row = -1;
1995 
1996 		y1 = y2 = floor (eti_base_y) + height_extra;
1997 		for (row = 0; row < rows; row++, y1 = y2) {
1998 
1999 			y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;
2000 
2001 			if (y1 > y + height)
2002 				break;
2003 
2004 			if (y2 < y)
2005 				continue;
2006 
2007 			if (first_row == -1) {
2008 				y_offset = y1 - y;
2009 				first_row = row;
2010 			}
2011 		}
2012 		last_row = row;
2013 
2014 		if (first_row == -1)
2015 			return;
2016 	}
2017 
2018 	if (first_row == -1)
2019 		return;
2020 
2021 	/*
2022 	 * Draw cells
2023 	 */
2024 	yd = y_offset;
2025 	f_x1 = f_x2 = f_y1 = f_y2 = -1;
2026 	f_found = FALSE;
2027 
2028 	if (eti->horizontal_draw_grid && first_row == 0)
2029 		eti_draw_grid_line (eti, cr, &line_color, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);
2030 
2031 	yd += height_extra;
2032 
2033 	for (row = first_row; row < last_row; row++) {
2034 		gint xd;
2035 		gboolean selected;
2036 		gint cursor_col, cursor_row;
2037 
2038 		height = ETI_ROW_HEIGHT (eti, row);
2039 
2040 		xd = x_offset;
2041 
2042 		selected = e_table_item_get_row_selected (eti, row);
2043 
2044 		g_object_get (
2045 			eti->selection,
2046 			"cursor_col", &cursor_col,
2047 			"cursor_row", &cursor_row,
2048 			NULL);
2049 
2050 		for (col = first_col; col < last_col; col++) {
2051 			ETableCol *ecol = e_table_header_get_column (eti->header, col);
2052 			ECellView *ecell_view = eti->cell_views[col];
2053 			gboolean col_selected = selected;
2054 			gboolean cursor = FALSE;
2055 			ECellFlags flags;
2056 			GdkRGBA background;
2057 			gint x1, x2, y1, y2;
2058 			cairo_pattern_t *pat;
2059 
2060 			switch (eti->cursor_mode) {
2061 			case E_CURSOR_SIMPLE:
2062 			case E_CURSOR_SPREADSHEET:
2063 				if (cursor_col == ecol->spec->model_col && cursor_row == view_to_model_row (eti, row)) {
2064 					col_selected = !col_selected;
2065 					cursor = TRUE;
2066 				}
2067 				break;
2068 			case E_CURSOR_LINE:
2069 				/* Nothing */
2070 				break;
2071 			}
2072 
2073 			x1 = xd;
2074 			y1 = yd + 1;
2075 			x2 = x1 + ecol->width;
2076 			y2 = yd + height;
2077 
2078 			eti_get_cell_background_color (eti, row, col, col_selected, &background);
2079 
2080 			cairo_save (cr);
2081 			pat = cairo_pattern_create_linear (0, y1, 0, y2);
2082 			cairo_pattern_add_color_stop_rgba (
2083 				pat, 0.0, background.red,
2084 				background.green,
2085 				background.blue, selected ? 0.8: 1.0);
2086 			if (selected)
2087 				cairo_pattern_add_color_stop_rgba (
2088 					pat, 0.5, background.red,
2089 					background.green,
2090 					background.blue, 0.9);
2091 
2092 			cairo_pattern_add_color_stop_rgba (
2093 				pat, 1, background.red,
2094 				background.green,
2095 				background.blue, selected ? 0.8 : 1.0);
2096 			cairo_rectangle (cr, x1, y1, ecol->width, height - 1);
2097 			cairo_set_source (cr, pat);
2098 			cairo_fill_preserve (cr);
2099 			cairo_pattern_destroy (pat);
2100 			cairo_set_line_width (cr, 0);
2101 			cairo_stroke (cr);
2102 			cairo_restore (cr);
2103 
2104 			cairo_save (cr);
2105 			cairo_set_line_width (cr, 0.5);
2106 			cairo_set_source_rgba (
2107 				cr, background.red,
2108 				background.green,
2109 				background.blue, 1);
2110 			cairo_move_to (cr, x1, y1 + 0.5);
2111 			cairo_line_to (cr, x2, y1 + 0.5);
2112 			cairo_stroke (cr);
2113 
2114 			cairo_set_line_width (cr, 0.5);
2115 			cairo_set_source_rgba (
2116 				cr, background.red,
2117 				background.green,
2118 				background.blue, 1);
2119 			cairo_move_to (cr, x1, y2 + 0.5);
2120 			cairo_line_to (cr, x2, y2 + 0.5);
2121 			cairo_stroke (cr);
2122 			cairo_restore (cr);
2123 
2124 			flags = col_selected ? E_CELL_SELECTED : 0;
2125 			flags |= gtk_widget_has_focus (canvas) ? E_CELL_FOCUSED : 0;
2126 			flags |= cursor ? E_CELL_CURSOR : 0;
2127 
2128 			switch (ecol->justification) {
2129 			case GTK_JUSTIFY_LEFT:
2130 				flags |= E_CELL_JUSTIFY_LEFT;
2131 				break;
2132 			case GTK_JUSTIFY_RIGHT:
2133 				flags |= E_CELL_JUSTIFY_RIGHT;
2134 				break;
2135 			case GTK_JUSTIFY_CENTER:
2136 				flags |= E_CELL_JUSTIFY_CENTER;
2137 				break;
2138 			case GTK_JUSTIFY_FILL:
2139 				flags |= E_CELL_JUSTIFY_FILL;
2140 				break;
2141 			}
2142 
2143 			e_cell_draw (
2144 				ecell_view, cr,
2145 				ecol->spec->model_col,
2146 				col, row, flags,
2147 				xd, yd,
2148 				xd + ecol->width, yd + height);
2149 
2150 			if (!f_found && !selected) {
2151 				switch (eti->cursor_mode) {
2152 				case E_CURSOR_LINE:
2153 					if (view_to_model_row (eti, row) == cursor_row) {
2154 						f_x1 = floor (eti_base_x) - x;
2155 						f_x2 = floor (lower_right_x) - x;
2156 						f_y1 = yd + 1;
2157 						f_y2 = yd + height;
2158 						f_found = TRUE;
2159 					}
2160 					break;
2161 				case E_CURSOR_SIMPLE:
2162 				case E_CURSOR_SPREADSHEET:
2163 					if (view_to_model_col (eti, col) == cursor_col && view_to_model_row (eti, row) == cursor_row) {
2164 						f_x1 = xd;
2165 						f_x2 = xd + ecol->width;
2166 						f_y1 = yd;
2167 						f_y2 = yd + height;
2168 						f_found = TRUE;
2169 					}
2170 					break;
2171 				}
2172 			}
2173 
2174 			xd += ecol->width;
2175 		}
2176 		yd += height;
2177 
2178 		if (eti->horizontal_draw_grid) {
2179 			eti_draw_grid_line (eti, cr, &line_color, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);
2180 			yd++;
2181 		}
2182 	}
2183 
2184 	if (eti->vertical_draw_grid) {
2185 		gint xd = x_offset;
2186 
2187 		for (col = first_col; col <= last_col; col++) {
2188 			ETableCol *ecol = e_table_header_get_column (eti->header, col);
2189 
2190 			eti_draw_grid_line (eti, cr, &line_color, xd, y_offset, xd, yd - 1);
2191 
2192 			/*
2193 			 * This looks wierd, but it is to draw the last line
2194 			 */
2195 			if (ecol)
2196 				xd += ecol->width;
2197 		}
2198 	}
2199 
2200 	/*
2201 	 * Draw focus
2202 	 */
2203 	if (eti->draw_focus && f_found) {
2204 		static const double dash[] = { 1.0, 1.0 };
2205 		GdkRGBA bg, fg;
2206 
2207 		e_utils_get_theme_color (canvas, "theme_bg_color", E_UTILS_DEFAULT_THEME_BG_COLOR, &bg);
2208 		e_utils_get_theme_color (canvas, "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &fg);
2209 
2210 		cairo_set_line_width (cr, 1.0);
2211 		cairo_rectangle (
2212 			cr,
2213 			f_x1 + 0.5, f_x2 + 0.5,
2214 			f_x2 - f_x1 - 1, f_y2 - f_y1);
2215 
2216 		gdk_cairo_set_source_rgba (cr, &bg);
2217 		cairo_stroke_preserve (cr);
2218 
2219 		cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0.0);
2220 		gdk_cairo_set_source_rgba (cr, &fg);
2221 		cairo_stroke (cr);
2222 	}
2223 }
2224 
2225 static GnomeCanvasItem *
eti_point(GnomeCanvasItem * item,gdouble x,gdouble y,gint cx,gint cy)2226 eti_point (GnomeCanvasItem *item,
2227            gdouble x,
2228            gdouble y,
2229            gint cx,
2230            gint cy)
2231 {
2232 	return item;
2233 }
2234 
2235 static gboolean
find_cell(ETableItem * eti,gdouble x,gdouble y,gint * view_col_res,gint * view_row_res,gdouble * x1_res,gdouble * y1_res)2236 find_cell (ETableItem *eti,
2237            gdouble x,
2238            gdouble y,
2239            gint *view_col_res,
2240            gint *view_row_res,
2241            gdouble *x1_res,
2242            gdouble *y1_res)
2243 {
2244 	const gint cols = eti->cols;
2245 	const gint rows = eti->rows;
2246 	gdouble x1, y1, x2, y2;
2247 	gint col, row;
2248 
2249 	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
2250 
2251 	/* FIXME: this routine is inneficient, fix later */
2252 
2253 	if (eti->grabbed_col >= 0 && eti->grabbed_row >= 0) {
2254 		*view_col_res = eti->grabbed_col;
2255 		*view_row_res = eti->grabbed_row;
2256 		*x1_res = x - e_table_header_col_diff (eti->header, 0, eti->grabbed_col);
2257 		*y1_res = y - e_table_item_row_diff (eti, 0, eti->grabbed_row);
2258 		return TRUE;
2259 	}
2260 
2261 	if (cols == 0 || rows == 0)
2262 		return FALSE;
2263 
2264 	x1 = 0;
2265 	for (col = 0; col < cols - 1; col++, x1 = x2) {
2266 		ETableCol *ecol = e_table_header_get_column (eti->header, col);
2267 
2268 		if (x < x1)
2269 			return FALSE;
2270 
2271 		x2 = x1 + ecol->width;
2272 
2273 		if (x <= x2)
2274 			break;
2275 	}
2276 
2277 	if (eti->uniform_row_height) {
2278 		if (y < height_extra)
2279 			return FALSE;
2280 		row = (y - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
2281 		y1 = row * (ETI_ROW_HEIGHT (eti, -1) + height_extra) + height_extra;
2282 		if (row >= eti->rows)
2283 			return FALSE;
2284 	} else {
2285 		y1 = y2 = height_extra;
2286 		if (y < height_extra)
2287 			return FALSE;
2288 		for (row = 0; row < rows; row++, y1 = y2) {
2289 			y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;
2290 
2291 			if (y <= y2)
2292 				break;
2293 		}
2294 
2295 		if (row == rows)
2296 			return FALSE;
2297 	}
2298 	*view_col_res = col;
2299 	if (x1_res)
2300 		*x1_res = x - x1;
2301 	*view_row_res = row;
2302 	if (y1_res)
2303 		*y1_res = y - y1;
2304 	return TRUE;
2305 }
2306 
2307 static void
eti_cursor_move(ETableItem * eti,gint row,gint column)2308 eti_cursor_move (ETableItem *eti,
2309                  gint row,
2310                  gint column)
2311 {
2312 	e_table_item_leave_edit_(eti);
2313 	e_table_item_focus (eti, view_to_model_col (eti, column), view_to_model_row (eti, row), 0);
2314 }
2315 
2316 static void
eti_cursor_move_left(ETableItem * eti)2317 eti_cursor_move_left (ETableItem *eti)
2318 {
2319 	gint cursor_col, cursor_row;
2320 	g_object_get (
2321 		eti->selection,
2322 		"cursor_col", &cursor_col,
2323 		"cursor_row", &cursor_row,
2324 		NULL);
2325 
2326 	eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) - 1);
2327 }
2328 
2329 static void
eti_cursor_move_right(ETableItem * eti)2330 eti_cursor_move_right (ETableItem *eti)
2331 {
2332 	gint cursor_col, cursor_row;
2333 	g_object_get (
2334 		eti->selection,
2335 		"cursor_col", &cursor_col,
2336 		"cursor_row", &cursor_row,
2337 		NULL);
2338 
2339 	eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) + 1);
2340 }
2341 
2342 static gint
eti_e_cell_event(ETableItem * item,ECellView * ecell_view,GdkEvent * event,gint model_col,gint view_col,gint row,ECellFlags flags)2343 eti_e_cell_event (ETableItem *item,
2344                   ECellView *ecell_view,
2345                   GdkEvent *event,
2346                   gint model_col,
2347                   gint view_col,
2348                   gint row,
2349                   ECellFlags flags)
2350 {
2351 	ECellActions actions = 0;
2352 	gint ret_val;
2353 
2354 	ret_val = e_cell_event (
2355 		ecell_view, event, model_col, view_col, row, flags, &actions);
2356 
2357 	if (actions & E_CELL_GRAB) {
2358 		GdkDevice *event_device;
2359 		guint32 event_time;
2360 
2361 		d (g_print ("%s: eti_grab\n", G_STRFUNC));
2362 
2363 		event_device = gdk_event_get_device (event);
2364 		event_time = gdk_event_get_time (event);
2365 		eti_grab (item, event_device, event_time);
2366 
2367 		item->grabbed_col = view_col;
2368 		item->grabbed_row = row;
2369 	}
2370 
2371 	if (actions & E_CELL_UNGRAB) {
2372 		guint32 event_time;
2373 
2374 		d (g_print ("%s: eti_ungrab\n", G_STRFUNC));
2375 
2376 		event_time = gdk_event_get_time (event);
2377 		eti_ungrab (item, event_time);
2378 
2379 		item->grabbed_col = -1;
2380 		item->grabbed_row = -1;
2381 	}
2382 
2383 	return ret_val;
2384 }
2385 
2386 /* FIXME: cursor */
2387 static gint
eti_event(GnomeCanvasItem * item,GdkEvent * event)2388 eti_event (GnomeCanvasItem *item,
2389            GdkEvent *event)
2390 {
2391 	ETableItem *eti = E_TABLE_ITEM (item);
2392 	ECellView *ecell_view;
2393 	GdkModifierType event_state = 0;
2394 	GdkEvent *event_copy;
2395 	guint event_button = 0;
2396 	guint event_keyval = 0;
2397 	gdouble event_x_item = 0;
2398 	gdouble event_y_item = 0;
2399 	gdouble event_x_win = 0;
2400 	gdouble event_y_win = 0;
2401 	guint32 event_time;
2402 	gboolean return_val = TRUE;
2403 #if d(!)0
2404 	gboolean leave = FALSE;
2405 #endif
2406 
2407 	if (!eti->header)
2408 		return FALSE;
2409 
2410 	/* Don't fetch the device here.  GnomeCanvas frequently emits
2411 	 * synthesized events, and calling gdk_event_get_device() on them
2412 	 * will trigger a runtime warning.  Fetch the device where needed. */
2413 	gdk_event_get_button (event, &event_button);
2414 	gdk_event_get_coords (event, &event_x_win, &event_y_win);
2415 	gdk_event_get_keyval (event, &event_keyval);
2416 	gdk_event_get_state (event, &event_state);
2417 	event_time = gdk_event_get_time (event);
2418 
2419 	switch (event->type) {
2420 	case GDK_BUTTON_PRESS: {
2421 		gdouble x1, y1;
2422 		gint col, row;
2423 		gint cursor_row, cursor_col;
2424 		gint new_cursor_row, new_cursor_col;
2425 		ECellFlags flags = 0;
2426 
2427 		d (g_print ("%s: GDK_BUTTON_PRESS received, button %d\n", G_STRFUNC, event_button));
2428 
2429 		switch (event_button) {
2430 		case 1: /* Fall through. */
2431 		case 2:
2432 			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);
2433 
2434 			event_x_item = event_x_win;
2435 			event_y_item = event_y_win;
2436 
2437 			gnome_canvas_item_w2i (
2438 				item, &event_x_item, &event_y_item);
2439 
2440 			if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) {
2441 				if (eti_editing (eti))
2442 					e_table_item_leave_edit_(eti);
2443 				return TRUE;
2444 			}
2445 
2446 			ecell_view = eti->cell_views[col];
2447 
2448 			/* Clone the event and alter its position. */
2449 			event_copy = gdk_event_copy (event);
2450 			event_copy->button.x = x1;
2451 			event_copy->button.y = y1;
2452 
2453 			g_object_get (
2454 				eti->selection,
2455 				"cursor_row", &cursor_row,
2456 				"cursor_col", &cursor_col,
2457 				NULL);
2458 
2459 			if (cursor_col == view_to_model_col (eti, col) && cursor_row == view_to_model_row (eti, row)) {
2460 				flags = E_CELL_CURSOR;
2461 			} else {
2462 				flags = 0;
2463 			}
2464 
2465 			return_val = eti_e_cell_event (
2466 				eti, ecell_view, event_copy,
2467 				view_to_model_col (eti, col),
2468 				col, row, flags);
2469 			if (return_val) {
2470 				gdk_event_free (event_copy);
2471 				return TRUE;
2472 			}
2473 
2474 			g_signal_emit (
2475 				eti, eti_signals[CLICK], 0,
2476 				row, view_to_model_col (eti, col),
2477 				event_copy, &return_val);
2478 
2479 			gdk_event_free (event_copy);
2480 
2481 			if (return_val) {
2482 				eti->click_count = 0;
2483 				return TRUE;
2484 			}
2485 
2486 			g_object_get (
2487 				eti->selection,
2488 				"cursor_row", &cursor_row,
2489 				"cursor_col", &cursor_col,
2490 				NULL);
2491 
2492 			eti->maybe_did_something =
2493 				e_selection_model_maybe_do_something (
2494 				E_SELECTION_MODEL (eti->selection),
2495 				view_to_model_row (eti, row),
2496 				view_to_model_col (eti, col),
2497 				event_state);
2498 			g_object_get (
2499 				eti->selection,
2500 				"cursor_row", &new_cursor_row,
2501 				"cursor_col", &new_cursor_col,
2502 				NULL);
2503 
2504 			if (cursor_row != new_cursor_row || cursor_col != new_cursor_col) {
2505 				eti->click_count = 1;
2506 			} else {
2507 				eti->click_count++;
2508 				eti->row_guess = row;
2509 
2510 				if ((!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
2511 					e_table_item_enter_edit (eti, col, row);
2512 				}
2513 
2514 				/*
2515 				 * Adjust the event positions
2516 				 */
2517 
2518 				if (eti_editing (eti)) {
2519 					return_val = eti_e_cell_event (
2520 						eti, ecell_view, event,
2521 						view_to_model_col (eti, col),
2522 						col, row,
2523 						E_CELL_EDITING |
2524 						E_CELL_CURSOR);
2525 					if (return_val)
2526 						return TRUE;
2527 				}
2528 			}
2529 
2530 			if (event_button == 1) {
2531 				return_val = TRUE;
2532 
2533 				eti->maybe_in_drag = TRUE;
2534 				eti->drag_row = new_cursor_row;
2535 				eti->drag_col = new_cursor_col;
2536 				eti->drag_x = event_x_item;
2537 				eti->drag_y = event_y_item;
2538 				eti->drag_state = event_state;
2539 			}
2540 
2541 			break;
2542 		case 3:
2543 			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);
2544 
2545 			event_x_item = event_x_win;
2546 			event_y_item = event_y_win;
2547 
2548 			gnome_canvas_item_w2i (
2549 				item, &event_x_item, &event_y_item);
2550 
2551 			if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
2552 				return TRUE;
2553 
2554 			e_selection_model_right_click_down (
2555 				E_SELECTION_MODEL (eti->selection),
2556 				view_to_model_row (eti, row),
2557 				view_to_model_col (eti, col), 0);
2558 
2559 			/* Clone the event and alter its position. */
2560 			event_copy = gdk_event_copy (event);
2561 			event_copy->button.x = event_x_item;
2562 			event_copy->button.y = event_y_item;
2563 
2564 			g_signal_emit (
2565 				eti, eti_signals[RIGHT_CLICK], 0,
2566 				row, view_to_model_col (eti, col),
2567 				event, &return_val);
2568 
2569 			gdk_event_free (event_copy);
2570 
2571 			if (!return_val)
2572 				e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
2573 			break;
2574 		case 4:
2575 		case 5:
2576 			return FALSE;
2577 
2578 		}
2579 		break;
2580 	}
2581 
2582 	case GDK_BUTTON_RELEASE: {
2583 		gdouble x1, y1;
2584 		gint col, row;
2585 		gint cursor_row, cursor_col;
2586 
2587 		d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d\n", G_STRFUNC, event_button));
2588 
2589 		if (eti->grabbed_count > 0) {
2590 			d (g_print ("%s: eti_ungrab\n", G_STRFUNC));
2591 			eti_ungrab (eti, event_time);
2592 		}
2593 
2594 		if (event_button == 1) {
2595 			if (eti->maybe_in_drag) {
2596 				eti->maybe_in_drag = FALSE;
2597 				if (!eti->maybe_did_something)
2598 					e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
2599 			}
2600 			if (eti->in_drag) {
2601 				eti->in_drag = FALSE;
2602 			}
2603 		}
2604 
2605 		switch (event_button) {
2606 		case 1: /* Fall through. */
2607 		case 2:
2608 
2609 			event_x_item = event_x_win;
2610 			event_y_item = event_y_win;
2611 
2612 			gnome_canvas_item_w2i (
2613 				item, &event_x_item, &event_y_item);
2614 #if d(!)0
2615 			{
2616 				gboolean cell_found = find_cell (
2617 					eti, event_x_item, event_y_item,
2618 					&col, &row, &x1, &y1);
2619 				g_print (
2620 					"%s: find_cell(%f, %f) = %s(%d, %d, %f, %f)\n",
2621 					G_STRFUNC, event_x_item, event_y_item,
2622 					cell_found?"true":"false", col, row, x1, y1);
2623 			}
2624 #endif
2625 
2626 			if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
2627 				return TRUE;
2628 
2629 			g_object_get (
2630 				eti->selection,
2631 				"cursor_row", &cursor_row,
2632 				"cursor_col", &cursor_col,
2633 				NULL);
2634 
2635 			if (eti_editing (eti) && cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {
2636 
2637 				d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d, line: %d\n", G_STRFUNC, event_button, __LINE__))
2638 ;
2639 
2640 				ecell_view = eti->cell_views[col];
2641 
2642 				/* Clone the event and alter its position. */
2643 				event_copy = gdk_event_copy (event);
2644 				event_copy->button.x = x1;
2645 				event_copy->button.y = y1;
2646 
2647 				return_val = eti_e_cell_event (
2648 					eti, ecell_view, event_copy,
2649 					view_to_model_col (eti, col),
2650 					col, row,
2651 					E_CELL_EDITING |
2652 					E_CELL_CURSOR);
2653 
2654 				gdk_event_free (event_copy);
2655 			}
2656 			break;
2657 		case 3:
2658 			e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
2659 			return_val = TRUE;
2660 			break;
2661 		case 4:
2662 		case 5:
2663 			return FALSE;
2664 
2665 		}
2666 		break;
2667 	}
2668 
2669 	case GDK_2BUTTON_PRESS: {
2670 		gint model_col, model_row;
2671 #if 0
2672 		gdouble x1, y1;
2673 #endif
2674 
2675 		d (g_print ("%s: GDK_2BUTTON_PRESS received, button %d\n", G_STRFUNC, event_button));
2676 
2677 		/*
2678 		 * click_count is so that if you click on two
2679 		 * different rows we don't send a double click signal.
2680 		 */
2681 
2682 		if (eti->click_count >= 2) {
2683 
2684 			event_x_item = event_x_win;
2685 			event_y_item = event_y_win;
2686 
2687 			gnome_canvas_item_w2i (
2688 				item, &event_x_item, &event_y_item);
2689 
2690 			g_object_get (
2691 				eti->selection,
2692 				"cursor_row", &model_row,
2693 				"cursor_col", &model_col,
2694 				NULL);
2695 
2696 			/* Clone the event and alter its position. */
2697 			event_copy = gdk_event_copy (event);
2698 			event_copy->button.x = event_x_item -
2699 				e_table_header_col_diff (
2700 					eti->header, 0,
2701 					model_to_view_col (eti, model_col));
2702 			event_copy->button.y = event_y_item -
2703 				e_table_item_row_diff (
2704 					eti, 0,
2705 					model_to_view_row (eti, model_row));
2706 
2707 			if (event_button == 1) {
2708 				if (eti->maybe_in_drag) {
2709 					eti->maybe_in_drag = FALSE;
2710 					if (!eti->maybe_did_something)
2711 						e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
2712 				}
2713 				if (eti->in_drag) {
2714 					eti->in_drag = FALSE;
2715 				}
2716 				if (eti_editing (eti))
2717 					e_table_item_leave_edit_ (eti);
2718 
2719 			}
2720 
2721 			if (eti->grabbed_count > 0) {
2722 				d (g_print ("%s: eti_ungrab\n", G_STRFUNC));
2723 				eti_ungrab (eti, event_time);
2724 			}
2725 
2726 			if (model_row != -1 && model_col != -1) {
2727 				g_signal_emit (
2728 					eti, eti_signals[DOUBLE_CLICK], 0,
2729 					model_row, model_col, event_copy);
2730 			}
2731 
2732 			gdk_event_free (event_copy);
2733 		}
2734 		break;
2735 	}
2736 	case GDK_MOTION_NOTIFY: {
2737 		gint col, row, flags;
2738 		gdouble x1, y1;
2739 		gint cursor_col, cursor_row;
2740 
2741 		event_x_item = event_x_win;
2742 		event_y_item = event_y_win;
2743 
2744 		gnome_canvas_item_w2i (item, &event_x_item, &event_y_item);
2745 
2746 		if (eti->maybe_in_drag) {
2747 			if (gtk_drag_check_threshold (GTK_WIDGET (item->canvas), eti->drag_x, eti->drag_y, event_x_item, event_y_item)) {
2748 				gboolean drag_handled;
2749 
2750 				eti->maybe_in_drag = 0;
2751 
2752 				/* Clone the event and
2753 				 * alter its position. */
2754 				event_copy = gdk_event_copy (event);
2755 				event_copy->motion.x = event_x_item;
2756 				event_copy->motion.y = event_y_item;
2757 
2758 				g_signal_emit (
2759 					eti, eti_signals[START_DRAG], 0,
2760 					eti->drag_row, eti->drag_col,
2761 					event_copy, &drag_handled);
2762 
2763 				gdk_event_free (event_copy);
2764 
2765 				if (drag_handled)
2766 					eti->in_drag = 1;
2767 				else
2768 					eti->in_drag = 0;
2769 			}
2770 		}
2771 
2772 		if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
2773 			return TRUE;
2774 
2775 		if (eti->motion_row != -1 && eti->motion_col != -1 &&
2776 		    (row != eti->motion_row || col != eti->motion_col)) {
2777 			GdkEvent *cross = gdk_event_new (GDK_LEAVE_NOTIFY);
2778 			cross->crossing.time = event_time;
2779 			return_val = eti_e_cell_event (
2780 				eti, eti->cell_views[eti->motion_col],
2781 				cross,
2782 				view_to_model_col (eti, eti->motion_col),
2783 				eti->motion_col, eti->motion_row, 0);
2784 		}
2785 
2786 		eti->motion_row = row;
2787 		eti->motion_col = col;
2788 
2789 		g_object_get (
2790 			eti->selection,
2791 			"cursor_row", &cursor_row,
2792 			"cursor_col", &cursor_col,
2793 			NULL);
2794 
2795 		flags = 0;
2796 		if (cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {
2797 			flags = E_CELL_EDITING | E_CELL_CURSOR;
2798 		}
2799 
2800 		ecell_view = eti->cell_views[col];
2801 
2802 		/* Clone the event and alter its position. */
2803 		event_copy = gdk_event_copy (event);
2804 		event_copy->motion.x = x1;
2805 		event_copy->motion.y = y1;
2806 
2807 		return_val = eti_e_cell_event (
2808 			eti, ecell_view, event_copy,
2809 			view_to_model_col (eti, col), col, row, flags);
2810 
2811 		gdk_event_free (event_copy);
2812 
2813 		break;
2814 	}
2815 
2816 	case GDK_KEY_PRESS: {
2817 		gint cursor_row, cursor_col;
2818 		gint handled = TRUE;
2819 
2820 		d (g_print ("%s: GDK_KEY_PRESS received, keyval: %d\n", G_STRFUNC, (gint) e->key.keyval));
2821 
2822 		g_object_get (
2823 			eti->selection,
2824 			"cursor_row", &cursor_row,
2825 			"cursor_col", &cursor_col,
2826 			NULL);
2827 
2828 		if (cursor_row == -1 && cursor_col == -1)
2829 			return FALSE;
2830 
2831 		eti->in_key_press = TRUE;
2832 
2833 		switch (event_keyval) {
2834 		case GDK_KEY_Left:
2835 		case GDK_KEY_KP_Left:
2836 			if (eti_editing (eti)) {
2837 				handled = FALSE;
2838 				break;
2839 			}
2840 
2841 			g_signal_emit (
2842 				eti, eti_signals[KEY_PRESS], 0,
2843 				model_to_view_row (eti, cursor_row),
2844 				cursor_col, event, &return_val);
2845 			if ((!return_val) &&
2846 			   (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
2847 			   cursor_col != view_to_model_col (eti, 0))
2848 				eti_cursor_move_left (eti);
2849 			return_val = 1;
2850 			break;
2851 
2852 		case GDK_KEY_Right:
2853 		case GDK_KEY_KP_Right:
2854 			if (eti_editing (eti)) {
2855 				handled = FALSE;
2856 				break;
2857 			}
2858 
2859 			g_signal_emit (
2860 				eti, eti_signals[KEY_PRESS], 0,
2861 				model_to_view_row (eti, cursor_row),
2862 				cursor_col, event, &return_val);
2863 			if ((!return_val) &&
2864 			   (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
2865 			   cursor_col != view_to_model_col (eti, eti->cols - 1))
2866 				eti_cursor_move_right (eti);
2867 			return_val = 1;
2868 			break;
2869 
2870 		case GDK_KEY_Up:
2871 		case GDK_KEY_KP_Up:
2872 		case GDK_KEY_Down:
2873 		case GDK_KEY_KP_Down:
2874 			if ((event_state & GDK_MOD1_MASK)
2875 			    && ((event_keyval == GDK_KEY_Down) || (event_keyval == GDK_KEY_KP_Down))) {
2876 				gint view_col = model_to_view_col (eti, cursor_col);
2877 
2878 				if ((view_col >= 0) && (view_col < eti->cols))
2879 					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))
2880 						return TRUE;
2881 			} else
2882 			return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
2883 			break;
2884 		case GDK_KEY_Home:
2885 		case GDK_KEY_KP_Home:
2886 			if (eti_editing (eti)) {
2887 				handled = FALSE;
2888 				break;
2889 			}
2890 
2891 			if (eti->cursor_mode != E_CURSOR_LINE) {
2892 				eti_cursor_move (eti, model_to_view_row (eti, cursor_row), 0);
2893 				return_val = TRUE;
2894 			} else
2895 				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
2896 			break;
2897 		case GDK_KEY_End:
2898 		case GDK_KEY_KP_End:
2899 			if (eti_editing (eti)) {
2900 				handled = FALSE;
2901 				break;
2902 			}
2903 
2904 			if (eti->cursor_mode != E_CURSOR_LINE) {
2905 				eti_cursor_move (eti, model_to_view_row (eti, cursor_row), eti->cols - 1);
2906 				return_val = TRUE;
2907 			} else
2908 				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
2909 			break;
2910 		case GDK_KEY_Tab:
2911 		case GDK_KEY_KP_Tab:
2912 		case GDK_KEY_ISO_Left_Tab:
2913 			if ((event_state & GDK_CONTROL_MASK) != 0) {
2914 				return_val = FALSE;
2915 				break;
2916 			}
2917 			if (eti->cursor_mode == E_CURSOR_SPREADSHEET) {
2918 				if ((event_state & GDK_SHIFT_MASK) != 0) {
2919 				/* shift tab */
2920 					if (cursor_col != view_to_model_col (eti, 0))
2921 						eti_cursor_move_left (eti);
2922 					else if (cursor_row != view_to_model_row (eti, 0))
2923 						eti_cursor_move (eti, model_to_view_row (eti, cursor_row) - 1, eti->cols - 1);
2924 					else
2925 						return_val = FALSE;
2926 				} else {
2927 					if (cursor_col != view_to_model_col (eti, eti->cols - 1))
2928 						eti_cursor_move_right (eti);
2929 					else if (cursor_row != view_to_model_row (eti, eti->rows - 1))
2930 						eti_cursor_move (eti, model_to_view_row (eti, cursor_row) + 1, 0);
2931 					else
2932 						return_val = FALSE;
2933 				}
2934 				g_object_get (
2935 					eti->selection,
2936 					"cursor_row", &cursor_row,
2937 					"cursor_col", &cursor_col,
2938 					NULL);
2939 
2940 				if (cursor_col >= 0 && cursor_row >= 0 && return_val &&
2941 				    (!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, model_to_view_row (eti, cursor_row))) {
2942 					e_table_item_enter_edit (eti, model_to_view_col (eti, cursor_col), model_to_view_row (eti, cursor_row));
2943 				}
2944 				break;
2945 			} else {
2946 			/* Let tab send you to the next widget. */
2947 			return_val = FALSE;
2948 			break;
2949 			}
2950 
2951 		case GDK_KEY_Return:
2952 		case GDK_KEY_KP_Enter:
2953 		case GDK_KEY_ISO_Enter:
2954 		case GDK_KEY_3270_Enter:
2955 			if (eti_editing (eti)) {
2956 				ecell_view = eti->cell_views[eti->editing_col];
2957 				return_val = eti_e_cell_event (
2958 					eti, ecell_view, event,
2959 					view_to_model_col (eti, eti->editing_col),
2960 					eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR | E_CELL_PREEDIT);
2961 				if (!return_val)
2962 					break;
2963 			}
2964 			g_signal_emit (
2965 				eti, eti_signals[KEY_PRESS], 0,
2966 				model_to_view_row (eti, cursor_row),
2967 				cursor_col, event, &return_val);
2968 			if (!return_val)
2969 				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
2970 			break;
2971 
2972 		default:
2973 			handled = FALSE;
2974 			break;
2975 		}
2976 
2977 		if (!handled) {
2978 			switch (event_keyval) {
2979 			case GDK_KEY_Scroll_Lock:
2980 			case GDK_KEY_Sys_Req:
2981 			case GDK_KEY_Shift_L:
2982 			case GDK_KEY_Shift_R:
2983 			case GDK_KEY_Control_L:
2984 			case GDK_KEY_Control_R:
2985 			case GDK_KEY_Caps_Lock:
2986 			case GDK_KEY_Shift_Lock:
2987 			case GDK_KEY_Meta_L:
2988 			case GDK_KEY_Meta_R:
2989 			case GDK_KEY_Alt_L:
2990 			case GDK_KEY_Alt_R:
2991 			case GDK_KEY_Super_L:
2992 			case GDK_KEY_Super_R:
2993 			case GDK_KEY_Hyper_L:
2994 			case GDK_KEY_Hyper_R:
2995 			case GDK_KEY_ISO_Lock:
2996 				break;
2997 
2998 			default:
2999 				if (!eti_editing (eti)) {
3000 					gint col, row;
3001 					row = model_to_view_row (eti, cursor_row);
3002 					col = model_to_view_col (eti, cursor_col);
3003 					if (col != -1 && row != -1 && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
3004 						e_table_item_enter_edit (eti, col, row);
3005 					}
3006 				}
3007 				if (!eti_editing (eti)) {
3008 					g_signal_emit (
3009 						eti, eti_signals[KEY_PRESS], 0,
3010 						model_to_view_row (eti, cursor_row),
3011 						cursor_col, event, &return_val);
3012 					if (!return_val)
3013 						e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
3014 				} else {
3015 					ecell_view = eti->cell_views[eti->editing_col];
3016 					return_val = eti_e_cell_event (
3017 						eti, ecell_view, event,
3018 						view_to_model_col (eti, eti->editing_col),
3019 						eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
3020 					if (!return_val)
3021 						e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
3022 				}
3023 				break;
3024 			}
3025 		}
3026 		eti->in_key_press = FALSE;
3027 		break;
3028 	}
3029 
3030 	case GDK_KEY_RELEASE: {
3031 		gint cursor_row, cursor_col;
3032 
3033 		d (g_print ("%s: GDK_KEY_RELEASE received, keyval: %d\n", G_STRFUNC, (gint) event_keyval));
3034 
3035 		g_object_get (
3036 			eti->selection,
3037 			"cursor_row", &cursor_row,
3038 			"cursor_col", &cursor_col,
3039 			NULL);
3040 
3041 		if (cursor_col == -1)
3042 			return FALSE;
3043 
3044 		if (eti_editing (eti)) {
3045 			ecell_view = eti->cell_views[eti->editing_col];
3046 			return_val = eti_e_cell_event (
3047 				eti, ecell_view, event,
3048 				view_to_model_col (eti, eti->editing_col),
3049 				eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
3050 		}
3051 		break;
3052 	}
3053 
3054 	case GDK_LEAVE_NOTIFY:
3055 		d (leave = TRUE);
3056 	case GDK_ENTER_NOTIFY:
3057 		d (g_print ("%s: %s received\n", G_STRFUNC, leave ? "GDK_LEAVE_NOTIFY" : "GDK_ENTER_NOTIFY"));
3058 		if (eti->motion_row != -1 && eti->motion_col != -1)
3059 			return_val = eti_e_cell_event (
3060 				eti, eti->cell_views[eti->motion_col],
3061 				event,
3062 				view_to_model_col (eti, eti->motion_col),
3063 				eti->motion_col, eti->motion_row, 0);
3064 		eti->motion_row = -1;
3065 		eti->motion_col = -1;
3066 
3067 		break;
3068 
3069 	case GDK_FOCUS_CHANGE:
3070 		d (g_print ("%s: GDK_FOCUS_CHANGE received, %s\n", G_STRFUNC, e->focus_change.in ? "in": "out"));
3071 		if (event->focus_change.in) {
3072 			if (eti->save_row != -1 &&
3073 			    eti->save_col != -1 &&
3074 			    !eti_editing (eti) &&
3075 			    e_table_model_is_cell_editable (eti->table_model, view_to_model_col (eti, eti->save_col), eti->save_row)) {
3076 				e_table_item_enter_edit (eti, eti->save_col, eti->save_row);
3077 				e_cell_load_state (
3078 					eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->save_col),
3079 					eti->save_col, eti->save_row, eti->edit_ctx, eti->save_state);
3080 				eti_free_save_state (eti);
3081 			}
3082 		} else {
3083 			if (eti_editing (eti)) {
3084 				eti_free_save_state (eti);
3085 
3086 				eti->save_row = eti->editing_row;
3087 				eti->save_col = eti->editing_col;
3088 				eti->save_state = e_cell_save_state (
3089 					eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->editing_col),
3090 					eti->editing_col, eti->editing_row, eti->edit_ctx);
3091 				e_table_item_leave_edit_(eti);
3092 			}
3093 		}
3094 		return_val = FALSE;
3095 		break;
3096 
3097 	default:
3098 		return_val = FALSE;
3099 		break;
3100 	}
3101 	/* d(g_print("%s: returning: %s\n", G_STRFUNC, return_val?"true":"false"));*/
3102 
3103 	return return_val;
3104 }
3105 
3106 static void
eti_style_updated(ETableItem * eti)3107 eti_style_updated (ETableItem *eti)
3108 {
3109 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
3110 
3111 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
3112 		return;
3113 
3114 	if (eti->cell_views_realized) {
3115 		gint i;
3116 		gint n_cells = eti->n_cells;
3117 
3118 		for (i = 0; i < n_cells; i++) {
3119 			e_cell_style_updated (eti->cell_views[i]);
3120 		}
3121 	}
3122 
3123 	eti->needs_compute_height = 1;
3124 	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
3125 	eti->needs_redraw = 1;
3126 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
3127 
3128 	free_height_cache (eti);
3129 
3130 	eti_idle_maybe_show_cursor (eti);
3131 }
3132 
3133 static void
e_table_item_class_init(ETableItemClass * class)3134 e_table_item_class_init (ETableItemClass *class)
3135 {
3136 	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
3137 	GObjectClass *object_class = G_OBJECT_CLASS (class);
3138 
3139 	g_type_class_add_private (class, sizeof (ETableItemPrivate));
3140 
3141 	object_class->dispose = eti_dispose;
3142 	object_class->set_property = eti_set_property;
3143 	object_class->get_property = eti_get_property;
3144 
3145 	item_class->update = eti_update;
3146 	item_class->realize = eti_realize;
3147 	item_class->unrealize = eti_unrealize;
3148 	item_class->draw = eti_draw;
3149 	item_class->point = eti_point;
3150 	item_class->event = eti_event;
3151 
3152 	class->cursor_change = NULL;
3153 	class->cursor_activated = NULL;
3154 	class->double_click = NULL;
3155 	class->right_click = NULL;
3156 	class->click = NULL;
3157 	class->key_press = NULL;
3158 	class->start_drag = NULL;
3159 	class->style_updated = eti_style_updated;
3160 	class->selection_model_removed = NULL;
3161 	class->selection_model_added = NULL;
3162 
3163 	g_object_class_install_property (
3164 		object_class,
3165 		PROP_TABLE_HEADER,
3166 		g_param_spec_object (
3167 			"ETableHeader",
3168 			"Table header",
3169 			"Table header",
3170 			E_TYPE_TABLE_HEADER,
3171 			G_PARAM_WRITABLE));
3172 
3173 	g_object_class_install_property (
3174 		object_class,
3175 		PROP_TABLE_MODEL,
3176 		g_param_spec_object (
3177 			"ETableModel",
3178 			"Table model",
3179 			"Table model",
3180 			E_TYPE_TABLE_MODEL,
3181 			G_PARAM_WRITABLE));
3182 
3183 	g_object_class_install_property (
3184 		object_class,
3185 		PROP_SELECTION_MODEL,
3186 		g_param_spec_object (
3187 			"selection_model",
3188 			"Selection model",
3189 			"Selection model",
3190 			E_TYPE_SELECTION_MODEL,
3191 			G_PARAM_WRITABLE));
3192 
3193 	g_object_class_install_property (
3194 		object_class,
3195 		PROP_TABLE_ALTERNATING_ROW_COLORS,
3196 		g_param_spec_boolean (
3197 			"alternating_row_colors",
3198 			"Alternating Row Colors",
3199 			"Alternating Row Colors",
3200 			FALSE,
3201 			G_PARAM_WRITABLE));
3202 
3203 	g_object_class_install_property (
3204 		object_class,
3205 		PROP_TABLE_HORIZONTAL_DRAW_GRID,
3206 		g_param_spec_boolean (
3207 			"horizontal_draw_grid",
3208 			"Horizontal Draw Grid",
3209 			"Horizontal Draw Grid",
3210 			FALSE,
3211 			G_PARAM_WRITABLE));
3212 
3213 	g_object_class_install_property (
3214 		object_class,
3215 		PROP_TABLE_VERTICAL_DRAW_GRID,
3216 		g_param_spec_boolean (
3217 			"vertical_draw_grid",
3218 			"Vertical Draw Grid",
3219 			"Vertical Draw Grid",
3220 			FALSE,
3221 			G_PARAM_WRITABLE));
3222 
3223 	g_object_class_install_property (
3224 		object_class,
3225 		PROP_TABLE_DRAW_FOCUS,
3226 		g_param_spec_boolean (
3227 			"drawfocus",
3228 			"Draw focus",
3229 			"Draw focus",
3230 			FALSE,
3231 			G_PARAM_WRITABLE));
3232 
3233 	g_object_class_install_property (
3234 		object_class,
3235 		PROP_CURSOR_MODE,
3236 		g_param_spec_int (
3237 			"cursor_mode",
3238 			"Cursor mode",
3239 			"Cursor mode",
3240 			E_CURSOR_LINE,
3241 			E_CURSOR_SPREADSHEET,
3242 			E_CURSOR_LINE,
3243 			G_PARAM_WRITABLE));
3244 
3245 	g_object_class_install_property (
3246 		object_class,
3247 		PROP_LENGTH_THRESHOLD,
3248 		g_param_spec_int (
3249 			"length_threshold",
3250 			"Length Threshold",
3251 			"Length Threshold",
3252 			-1, G_MAXINT, 0,
3253 			G_PARAM_WRITABLE));
3254 
3255 	g_object_class_install_property (
3256 		object_class,
3257 		PROP_MINIMUM_WIDTH,
3258 		g_param_spec_double (
3259 			"minimum_width",
3260 			"Minimum width",
3261 			"Minimum Width",
3262 			0.0, G_MAXDOUBLE, 0.0,
3263 			G_PARAM_READWRITE));
3264 
3265 	g_object_class_install_property (
3266 		object_class,
3267 		PROP_WIDTH,
3268 		g_param_spec_double (
3269 			"width",
3270 			"Width",
3271 			"Width",
3272 			0.0, G_MAXDOUBLE, 0.0,
3273 			G_PARAM_READWRITE));
3274 
3275 	g_object_class_install_property (
3276 		object_class,
3277 		PROP_HEIGHT,
3278 		g_param_spec_double (
3279 			"height",
3280 			"Height",
3281 			"Height",
3282 			0.0, G_MAXDOUBLE, 0.0,
3283 			G_PARAM_READABLE));
3284 
3285 	g_object_class_install_property (
3286 		object_class,
3287 		PROP_CURSOR_ROW,
3288 		g_param_spec_int (
3289 			"cursor_row",
3290 			"Cursor row",
3291 			"Cursor row",
3292 			0, G_MAXINT, 0,
3293 			G_PARAM_READWRITE));
3294 
3295 	g_object_class_install_property (
3296 		object_class,
3297 		PROP_UNIFORM_ROW_HEIGHT,
3298 		g_param_spec_boolean (
3299 			"uniform_row_height",
3300 			"Uniform row height",
3301 			"Uniform row height",
3302 			FALSE,
3303 			G_PARAM_READWRITE));
3304 
3305 	g_object_class_install_property (
3306 		object_class,
3307 		PROP_IS_EDITING,
3308 		g_param_spec_boolean (
3309 			"is-editing",
3310 			"Whether is in an editing mode",
3311 			"Whether is in an editing mode",
3312 			FALSE,
3313 			G_PARAM_READABLE));
3314 
3315 	eti_signals[CURSOR_CHANGE] = g_signal_new (
3316 		"cursor_change",
3317 		G_OBJECT_CLASS_TYPE (object_class),
3318 		G_SIGNAL_RUN_LAST,
3319 		G_STRUCT_OFFSET (ETableItemClass, cursor_change),
3320 		NULL, NULL,
3321 		g_cclosure_marshal_VOID__INT,
3322 		G_TYPE_NONE, 1,
3323 		G_TYPE_INT);
3324 
3325 	eti_signals[CURSOR_ACTIVATED] = g_signal_new (
3326 		"cursor_activated",
3327 		G_OBJECT_CLASS_TYPE (object_class),
3328 		G_SIGNAL_RUN_LAST,
3329 		G_STRUCT_OFFSET (ETableItemClass, cursor_activated),
3330 		NULL, NULL,
3331 		g_cclosure_marshal_VOID__INT,
3332 		G_TYPE_NONE, 1,
3333 		G_TYPE_INT);
3334 
3335 	eti_signals[DOUBLE_CLICK] = g_signal_new (
3336 		"double_click",
3337 		G_OBJECT_CLASS_TYPE (object_class),
3338 		G_SIGNAL_RUN_LAST,
3339 		G_STRUCT_OFFSET (ETableItemClass, double_click),
3340 		NULL, NULL,
3341 		e_marshal_VOID__INT_INT_BOXED,
3342 		G_TYPE_NONE, 3,
3343 		G_TYPE_INT,
3344 		G_TYPE_INT,
3345 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3346 
3347 	eti_signals[START_DRAG] = g_signal_new (
3348 		"start_drag",
3349 		G_OBJECT_CLASS_TYPE (object_class),
3350 		G_SIGNAL_RUN_LAST,
3351 		G_STRUCT_OFFSET (ETableItemClass, start_drag),
3352 		g_signal_accumulator_true_handled, NULL,
3353 		e_marshal_BOOLEAN__INT_INT_BOXED,
3354 		G_TYPE_BOOLEAN, 3,
3355 		G_TYPE_INT,
3356 		G_TYPE_INT,
3357 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3358 
3359 	eti_signals[RIGHT_CLICK] = g_signal_new (
3360 		"right_click",
3361 		G_OBJECT_CLASS_TYPE (object_class),
3362 		G_SIGNAL_RUN_LAST,
3363 		G_STRUCT_OFFSET (ETableItemClass, right_click),
3364 		g_signal_accumulator_true_handled, NULL,
3365 		e_marshal_BOOLEAN__INT_INT_BOXED,
3366 		G_TYPE_BOOLEAN, 3,
3367 		G_TYPE_INT,
3368 		G_TYPE_INT,
3369 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3370 
3371 	eti_signals[CLICK] = g_signal_new (
3372 		"click",
3373 		G_OBJECT_CLASS_TYPE (object_class),
3374 		G_SIGNAL_RUN_LAST,
3375 		G_STRUCT_OFFSET (ETableItemClass, click),
3376 		g_signal_accumulator_true_handled, NULL,
3377 		e_marshal_BOOLEAN__INT_INT_BOXED,
3378 		G_TYPE_BOOLEAN, 3,
3379 		G_TYPE_INT,
3380 		G_TYPE_INT,
3381 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3382 
3383 	eti_signals[KEY_PRESS] = g_signal_new (
3384 		"key_press",
3385 		G_OBJECT_CLASS_TYPE (object_class),
3386 		G_SIGNAL_RUN_LAST,
3387 		G_STRUCT_OFFSET (ETableItemClass, key_press),
3388 		g_signal_accumulator_true_handled, NULL,
3389 		e_marshal_BOOLEAN__INT_INT_BOXED,
3390 		G_TYPE_BOOLEAN, 3,
3391 		G_TYPE_INT,
3392 		G_TYPE_INT,
3393 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3394 
3395 	eti_signals[STYLE_UPDATED] = g_signal_new (
3396 		"style_updated",
3397 		G_OBJECT_CLASS_TYPE (object_class),
3398 		G_SIGNAL_RUN_LAST,
3399 		G_STRUCT_OFFSET (ETableItemClass, style_updated),
3400 		NULL, NULL,
3401 		g_cclosure_marshal_VOID__VOID,
3402 		G_TYPE_NONE, 0);
3403 
3404 	eti_signals[SELECTION_MODEL_REMOVED] = g_signal_new (
3405 		"selection_model_removed",
3406 		G_TYPE_FROM_CLASS (object_class),
3407 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
3408 		G_STRUCT_OFFSET (ETableItemClass, selection_model_removed),
3409 		NULL, NULL,
3410 		g_cclosure_marshal_VOID__POINTER,
3411 		G_TYPE_NONE, 1,
3412 		G_TYPE_POINTER);
3413 
3414 	eti_signals[SELECTION_MODEL_ADDED] = g_signal_new (
3415 		"selection_model_added",
3416 		G_TYPE_FROM_CLASS (object_class),
3417 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
3418 		G_STRUCT_OFFSET (ETableItemClass, selection_model_added),
3419 		NULL, NULL,
3420 		g_cclosure_marshal_VOID__POINTER,
3421 		G_TYPE_NONE, 1,
3422 		G_TYPE_POINTER);
3423 
3424 	eti_signals[GET_BG_COLOR] = g_signal_new (
3425 		"get-bg-color",
3426 		G_OBJECT_CLASS_TYPE (object_class),
3427 		G_SIGNAL_RUN_LAST,
3428 		G_STRUCT_OFFSET (ETableItemClass, get_bg_color),
3429 		NULL, NULL,
3430 		NULL,
3431 		G_TYPE_BOOLEAN, 3, /* return TRUE when set */
3432 		G_TYPE_INT, /* row */
3433 		G_TYPE_INT, /* col */
3434 		G_TYPE_POINTER /* GdkRGBA *background, but cannot be passed as
3435 				  boxed, because it's used as (inout) argument */);
3436 
3437 	/* A11y Init */
3438 	gal_a11y_e_table_item_init ();
3439 }
3440 
3441 /**
3442  * e_table_item_set_cursor:
3443  * @eti: %ETableItem which will have the cursor set.
3444  * @col: Column to select.  -1 means the last column.
3445  * @row: Row to select.  -1 means the last row.
3446  *
3447  * This routine sets the cursor of the %ETableItem canvas item.
3448  */
3449 void
e_table_item_set_cursor(ETableItem * eti,gint col,gint row)3450 e_table_item_set_cursor (ETableItem *eti,
3451                          gint col,
3452                          gint row)
3453 {
3454 	e_table_item_focus (eti, col, view_to_model_row (eti, row), 0);
3455 }
3456 
3457 static void
e_table_item_focus(ETableItem * eti,gint col,gint row,GdkModifierType state)3458 e_table_item_focus (ETableItem *eti,
3459                     gint col,
3460                     gint row,
3461                     GdkModifierType state)
3462 {
3463 	g_return_if_fail (eti != NULL);
3464 	g_return_if_fail (E_IS_TABLE_ITEM (eti));
3465 
3466 	if (row == -1) {
3467 		row = view_to_model_row (eti, eti->rows - 1);
3468 	}
3469 
3470 	if (col == -1) {
3471 		col = eti->cols - 1;
3472 	}
3473 
3474 	if (row != -1) {
3475 		e_selection_model_do_something (
3476 			E_SELECTION_MODEL (eti->selection),
3477 			row, col, state);
3478 	}
3479 }
3480 
3481 /**
3482  * e_table_item_get_focused_column:
3483  * @eti: %ETableItem which will have the cursor retrieved.
3484  *
3485  * This routine gets the cursor of the %ETableItem canvas item.
3486  *
3487  * Returns: The current cursor column.
3488  */
3489 gint
e_table_item_get_focused_column(ETableItem * eti)3490 e_table_item_get_focused_column (ETableItem *eti)
3491 {
3492 	gint cursor_col;
3493 
3494 	g_return_val_if_fail (eti != NULL, -1);
3495 	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1);
3496 
3497 	g_object_get (
3498 		eti->selection,
3499 		"cursor_col", &cursor_col,
3500 		NULL);
3501 
3502 	return cursor_col;
3503 }
3504 
3505 static void
eti_cursor_change(ESelectionModel * selection,gint row,gint col,ETableItem * eti)3506 eti_cursor_change (ESelectionModel *selection,
3507                    gint row,
3508                    gint col,
3509                    ETableItem *eti)
3510 {
3511 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
3512 	gint view_row;
3513 
3514 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
3515 		return;
3516 
3517 	view_row = model_to_view_row (eti, row);
3518 
3519 	if (eti->old_cursor_row != -1 && view_row != eti->old_cursor_row)
3520 		e_table_item_redraw_row (eti, eti->old_cursor_row);
3521 
3522 	if (view_row == -1) {
3523 		e_table_item_leave_edit_(eti);
3524 		eti->old_cursor_row = -1;
3525 		return;
3526 	}
3527 
3528 	if (!e_table_model_has_change_pending (eti->table_model)) {
3529 		if (!eti->in_key_press) {
3530 			eti_maybe_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
3531 		} else {
3532 			eti_maybe_show_cursor (eti, 0);
3533 		}
3534 	}
3535 
3536 	e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), FALSE);
3537 	if (eti_editing (eti))
3538 		e_table_item_leave_edit_(eti);
3539 
3540 	g_signal_emit (eti, eti_signals[CURSOR_CHANGE], 0, view_row);
3541 
3542 	e_table_item_redraw_row (eti, view_row);
3543 
3544 	eti->old_cursor_row = view_row;
3545 }
3546 
3547 static void
eti_cursor_activated(ESelectionModel * selection,gint row,gint col,ETableItem * eti)3548 eti_cursor_activated (ESelectionModel *selection,
3549                       gint row,
3550                       gint col,
3551                       ETableItem *eti)
3552 {
3553 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
3554 	gint view_row;
3555 	gint view_col;
3556 
3557 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
3558 		return;
3559 
3560 	view_row = model_to_view_row (eti, row);
3561 	view_col = model_to_view_col (eti, col);
3562 
3563 	if (view_row != -1 && view_col != -1) {
3564 		if (!e_table_model_has_change_pending (eti->table_model)) {
3565 			if (!eti->in_key_press) {
3566 				eti_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
3567 			} else {
3568 				eti_show_cursor (eti, 0);
3569 			}
3570 			eti_check_cursor_bounds (eti);
3571 		}
3572 	}
3573 
3574 	if (eti_editing (eti))
3575 		e_table_item_leave_edit_(eti);
3576 
3577 	if (view_row != -1)
3578 		g_signal_emit (
3579 			eti, eti_signals[CURSOR_ACTIVATED], 0, view_row);
3580 }
3581 
3582 static void
eti_selection_change(ESelectionModel * selection,ETableItem * eti)3583 eti_selection_change (ESelectionModel *selection,
3584                       ETableItem *eti)
3585 {
3586 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
3587 
3588 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
3589 		return;
3590 
3591 	eti->needs_redraw = TRUE;
3592 	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
3593 }
3594 
3595 static void
eti_selection_row_change(ESelectionModel * selection,gint row,ETableItem * eti)3596 eti_selection_row_change (ESelectionModel *selection,
3597                           gint row,
3598                           ETableItem *eti)
3599 {
3600 	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
3601 
3602 	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
3603 		return;
3604 
3605 	if (!eti->needs_redraw) {
3606 		e_table_item_redraw_row (eti, model_to_view_row (eti, row));
3607 	}
3608 }
3609 
3610 /**
3611  * e_table_item_enter_edit
3612  * @eti: %ETableItem which will start being edited
3613  * @col: The view col to edit.
3614  * @row: The view row to edit.
3615  *
3616  * This routine starts the given %ETableItem editing at the given view
3617  * column and row.
3618  */
3619 void
e_table_item_enter_edit(ETableItem * eti,gint col,gint row)3620 e_table_item_enter_edit (ETableItem *eti,
3621                          gint col,
3622                          gint row)
3623 {
3624 	g_return_if_fail (eti != NULL);
3625 	g_return_if_fail (E_IS_TABLE_ITEM (eti));
3626 
3627 	d (g_print ("%s: %d, %d, eti_editing() = %s\n", G_STRFUNC, col, row, eti_editing (eti)?"true":"false"));
3628 
3629 	if (eti_editing (eti))
3630 		e_table_item_leave_edit_(eti);
3631 
3632 	eti->editing_col = col;
3633 	eti->editing_row = row;
3634 
3635 	if (col >= 0) {
3636 		eti->edit_ctx = e_cell_enter_edit (eti->cell_views[col], view_to_model_col (eti, col), col, row);
3637 
3638 		g_object_notify (G_OBJECT (eti), "is-editing");
3639 	}
3640 }
3641 
3642 /**
3643  * e_table_item_leave_edit_
3644  * @eti: %ETableItem which will stop being edited
3645  *
3646  * This routine stops the given %ETableItem from editing.
3647  */
3648 void
e_table_item_leave_edit(ETableItem * eti)3649 e_table_item_leave_edit (ETableItem *eti)
3650 {
3651 	gint col, row;
3652 	gpointer edit_ctx;
3653 
3654 	g_return_if_fail (eti != NULL);
3655 	g_return_if_fail (E_IS_TABLE_ITEM (eti));
3656 
3657 	d (g_print ("%s: eti_editing() = %s\n", G_STRFUNC, eti_editing (eti)?"true":"false"));
3658 
3659 	if (!eti_editing (eti))
3660 		return;
3661 
3662 	col = eti->editing_col;
3663 	row = eti->editing_row;
3664 	edit_ctx = eti->edit_ctx;
3665 
3666 	eti->editing_col = -1;
3667 	eti->editing_row = -1;
3668 	eti->edit_ctx = NULL;
3669 
3670 	e_cell_leave_edit (
3671 		eti->cell_views[col],
3672 		view_to_model_col (eti, col),
3673 		col, row, edit_ctx);
3674 
3675 	g_object_notify (G_OBJECT (eti), "is-editing");
3676 }
3677 
3678 /**
3679  * e_table_item_compute_location
3680  * @eti: %ETableItem to look in.
3681  * @x: A pointer to the x location to find in the %ETableItem.
3682  * @y: A pointer to the y location to find in the %ETableItem.
3683  * @row: A pointer to the location to store the found row in.
3684  * @col: A pointer to the location to store the found col in.
3685  *
3686  * This routine locates the pixel location (*x, *y) in the
3687  * %ETableItem.  If that location is in the %ETableItem, *row and *col
3688  * are set to the view row and column where it was found.  If that
3689  * location is not in the %ETableItem, the height of the %ETableItem
3690  * is removed from the value y points to.
3691  */
3692 void
e_table_item_compute_location(ETableItem * eti,gint * x,gint * y,gint * row,gint * col)3693 e_table_item_compute_location (ETableItem *eti,
3694                                gint *x,
3695                                gint *y,
3696                                gint *row,
3697                                gint *col)
3698 {
3699 	/* Save the grabbed row but make sure that we don't get flawed
3700 	 * results because the cursor is grabbed. */
3701 	gint grabbed_row = eti->grabbed_row;
3702 	eti->grabbed_row = -1;
3703 
3704 	if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) {
3705 		*y -= eti->height;
3706 	}
3707 
3708 	eti->grabbed_row = grabbed_row;
3709 }
3710 
3711 /**
3712  * e_table_item_compute_mouse_over:
3713  * Similar to e_table_item_compute_location, only here recalculating
3714  * the position inside the item too.
3715  **/
3716 void
e_table_item_compute_mouse_over(ETableItem * eti,gint x,gint y,gint * row,gint * col)3717 e_table_item_compute_mouse_over (ETableItem *eti,
3718                                  gint x,
3719                                  gint y,
3720                                  gint *row,
3721                                  gint *col)
3722 {
3723 	gdouble realx, realy;
3724 	/* Save the grabbed row but make sure that we don't get flawed
3725 	 * results because the cursor is grabbed. */
3726 	gint grabbed_row = eti->grabbed_row;
3727 	eti->grabbed_row = -1;
3728 
3729 	realx = x;
3730 	realy = y;
3731 
3732 	gnome_canvas_item_w2i (GNOME_CANVAS_ITEM (eti), &realx, &realy);
3733 
3734 	if (!find_cell (eti, (gint) realx, (gint) realy, col, row, NULL, NULL)) {
3735 		*row = -1;
3736 		*col = -1;
3737 	}
3738 
3739 	eti->grabbed_row = grabbed_row;
3740 }
3741 
3742 void
e_table_item_get_cell_geometry(ETableItem * eti,gint * row,gint * col,gint * x,gint * y,gint * width,gint * height)3743 e_table_item_get_cell_geometry (ETableItem *eti,
3744                                 gint *row,
3745                                 gint *col,
3746                                 gint *x,
3747                                 gint *y,
3748                                 gint *width,
3749                                 gint *height)
3750 {
3751 	if (eti->rows > *row) {
3752 		if (x)
3753 			*x = e_table_header_col_diff (eti->header, 0, *col);
3754 		if (y)
3755 			*y = e_table_item_row_diff (eti, 0, *row);
3756 		if (width)
3757 			*width = e_table_header_col_diff (eti->header, *col, *col + 1);
3758 		if (height)
3759 			*height = ETI_ROW_HEIGHT (eti, *row);
3760 		*row = -1;
3761 		*col = -1;
3762 	} else {
3763 		*row -= eti->rows;
3764 	}
3765 }
3766 
3767 typedef struct {
3768 	ETableItem *item;
3769 	gint rows_printed;
3770 } ETableItemPrintContext;
3771 
3772 static gdouble *
e_table_item_calculate_print_widths(ETableHeader * eth,gdouble width)3773 e_table_item_calculate_print_widths (ETableHeader *eth,
3774                                      gdouble width)
3775 {
3776 	gint i;
3777 	gdouble extra;
3778 	gdouble expansion;
3779 	gint last_resizable = -1;
3780 	gdouble scale = 1.0L;
3781 	gdouble *widths = g_new (gdouble, e_table_header_count (eth));
3782 	/* - 1 to account for the last pixel border. */
3783 	extra = width - 1;
3784 	expansion = 0;
3785 	for (i = 0; i < eth->col_count; i++) {
3786 		extra -= eth->columns[i]->min_width * scale;
3787 		if (eth->columns[i]->spec->resizable && eth->columns[i]->expansion > 0)
3788 			last_resizable = i;
3789 		expansion += eth->columns[i]->spec->resizable ? eth->columns[i]->expansion : 0;
3790 		widths[i] = eth->columns[i]->min_width * scale;
3791 	}
3792 	for (i = 0; i <= last_resizable; i++) {
3793 		widths[i] += extra * (eth->columns[i]->spec->resizable ? eth->columns[i]->expansion : 0) / expansion;
3794 	}
3795 
3796 	return widths;
3797 }
3798 
3799 static gdouble
eti_printed_row_height(ETableItem * eti,gdouble * widths,GtkPrintContext * context,gint row)3800 eti_printed_row_height (ETableItem *eti,
3801                         gdouble *widths,
3802                         GtkPrintContext *context,
3803                         gint row)
3804 {
3805 	gint col;
3806 	gint cols = eti->cols;
3807 	gdouble height = 0;
3808 	for (col = 0; col < cols; col++) {
3809 		ECellView *ecell_view = eti->cell_views[col];
3810 		gdouble this_height = e_cell_print_height (
3811 			ecell_view, context, view_to_model_col (eti, col), col, row,
3812 			widths[col] - 1);
3813 		if (this_height > height)
3814 			height = this_height;
3815 	}
3816 	return height;
3817 }
3818 
3819 #define CHECK(x) if((x) == -1) return -1;
3820 
3821 static gint
gp_draw_rect(GtkPrintContext * context,gdouble x,gdouble y,gdouble width,gdouble height)3822 gp_draw_rect (GtkPrintContext *context,
3823               gdouble x,
3824               gdouble y,
3825               gdouble width,
3826               gdouble height)
3827 {
3828 	cairo_t *cr;
3829 	cr = gtk_print_context_get_cairo_context (context);
3830 	cairo_save (cr);
3831 	cairo_rectangle (cr, x, y, width, height);
3832 	cairo_set_line_width (cr, 0.5);
3833 	cairo_stroke (cr);
3834 	cairo_restore (cr);
3835 	return 0;
3836 }
3837 
3838 static void
e_table_item_print_page(EPrintable * ep,GtkPrintContext * context,gdouble width,gdouble height,gboolean quantize,ETableItemPrintContext * itemcontext)3839 e_table_item_print_page (EPrintable *ep,
3840                          GtkPrintContext *context,
3841                          gdouble width,
3842                          gdouble height,
3843                          gboolean quantize,
3844                          ETableItemPrintContext *itemcontext)
3845 {
3846 	ETableItem *eti = itemcontext->item;
3847 	const gint rows = eti->rows;
3848 	const gint cols = eti->cols;
3849 	gdouble max_height;
3850 	gint rows_printed = itemcontext->rows_printed;
3851 	gint row, col, next_page = 0;
3852 	gdouble yd = height;
3853 	cairo_t *cr;
3854 	gdouble *widths;
3855 
3856 	cr = gtk_print_context_get_cairo_context (context);
3857 	max_height = gtk_print_context_get_height (context);
3858 	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);
3859 
3860 	/*
3861 	 * Draw cells
3862 	 */
3863 
3864 	if (eti->horizontal_draw_grid) {
3865 		gp_draw_rect (context, 0, yd, width, 1);
3866 	}
3867 	yd++;
3868 
3869 	for (row = rows_printed; row < rows; row++) {
3870 		gdouble xd = 1, row_height;
3871 		row_height = eti_printed_row_height (eti, widths, context, row);
3872 
3873 		if (quantize) {
3874 			if (yd + row_height + 1 > max_height && row != rows_printed) {
3875 				next_page = 1;
3876 				break;
3877 			}
3878 		} else {
3879 			if (yd > max_height) {
3880 				next_page = 1;
3881 				break;
3882 			}
3883 		}
3884 
3885 		for (col = 0; col < cols; col++) {
3886 			ECellView *ecell_view = eti->cell_views[col];
3887 
3888 			cairo_save (cr);
3889 			cairo_translate (cr, xd, yd);
3890 			cairo_rectangle (cr, 0, 0, widths[col] - 1, row_height);
3891 			cairo_clip (cr);
3892 
3893 			e_cell_print (
3894 				ecell_view, context,
3895 				view_to_model_col (eti, col),
3896 				col,
3897 				row,
3898 				widths[col] - 1,
3899 				row_height + 2);
3900 
3901 			cairo_restore (cr);
3902 
3903 			xd += widths[col];
3904 		}
3905 
3906 	yd += row_height;
3907 		if (eti->horizontal_draw_grid) {
3908 			gp_draw_rect (context, 0, yd, width, 1);
3909 		}
3910 		yd++;
3911 	}
3912 
3913 	itemcontext->rows_printed = row;
3914 	if (eti->vertical_draw_grid) {
3915 		gdouble xd = 0;
3916 		for (col = 0; col < cols; col++) {
3917 			gp_draw_rect (context, xd, height, 1, yd - height);
3918 			xd += widths[col];
3919 		}
3920 		gp_draw_rect (context, xd, height, 1, yd - height);
3921 	}
3922 
3923 	if (next_page)
3924 		cairo_show_page (cr);
3925 
3926 	g_free (widths);
3927 }
3928 
3929 static gboolean
e_table_item_data_left(EPrintable * ep,ETableItemPrintContext * itemcontext)3930 e_table_item_data_left (EPrintable *ep,
3931                         ETableItemPrintContext *itemcontext)
3932 {
3933 	ETableItem *item = itemcontext->item;
3934 	gint rows_printed = itemcontext->rows_printed;
3935 
3936 	g_signal_stop_emission_by_name (ep, "data_left");
3937 	return rows_printed < item->rows;
3938 }
3939 
3940 static void
e_table_item_reset(EPrintable * ep,ETableItemPrintContext * itemcontext)3941 e_table_item_reset (EPrintable *ep,
3942                     ETableItemPrintContext *itemcontext)
3943 {
3944 	itemcontext->rows_printed = 0;
3945 }
3946 
3947 static gdouble
e_table_item_height(EPrintable * ep,GtkPrintContext * context,gdouble width,gdouble max_height,gboolean quantize,ETableItemPrintContext * itemcontext)3948 e_table_item_height (EPrintable *ep,
3949                      GtkPrintContext *context,
3950                      gdouble width,
3951                      gdouble max_height,
3952                      gboolean quantize,
3953                      ETableItemPrintContext *itemcontext)
3954 {
3955 	ETableItem *item = itemcontext->item;
3956 	const gint rows = item->rows;
3957 	gint rows_printed = itemcontext->rows_printed;
3958 	gdouble *widths;
3959 	gint row;
3960 	gdouble yd = 0;
3961 
3962 	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);
3963 
3964 	/*
3965 	 * Draw cells
3966 	 */
3967 	yd++;
3968 
3969 	for (row = rows_printed; row < rows; row++) {
3970 		gdouble row_height;
3971 
3972 		row_height = eti_printed_row_height (item, widths, context, row);
3973 		if (quantize) {
3974 			if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
3975 				break;
3976 			}
3977 		} else {
3978 			if (max_height != -1 && yd > max_height) {
3979 				break;
3980 			}
3981 		}
3982 
3983 		yd += row_height;
3984 
3985 		yd++;
3986 	}
3987 
3988 	g_free (widths);
3989 
3990 	if (max_height != -1 && (!quantize) && yd > max_height)
3991 		yd = max_height;
3992 
3993 	g_signal_stop_emission_by_name (ep, "height");
3994 	return yd;
3995 }
3996 
3997 static gboolean
e_table_item_will_fit(EPrintable * ep,GtkPrintContext * context,gdouble width,gdouble max_height,gboolean quantize,ETableItemPrintContext * itemcontext)3998 e_table_item_will_fit (EPrintable *ep,
3999                        GtkPrintContext *context,
4000                        gdouble width,
4001                        gdouble max_height,
4002                        gboolean quantize,
4003                        ETableItemPrintContext *itemcontext)
4004 {
4005 	ETableItem *item = itemcontext->item;
4006 	const gint rows = item->rows;
4007 	gint rows_printed = itemcontext->rows_printed;
4008 	gdouble *widths;
4009 	gint row;
4010 	gdouble yd = 0;
4011 	gboolean ret_val = TRUE;
4012 
4013 	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);
4014 
4015 	/*
4016 	 * Draw cells
4017 	 */
4018 	yd++;
4019 
4020 	for (row = rows_printed; row < rows; row++) {
4021 		gdouble row_height;
4022 
4023 		row_height = eti_printed_row_height (item, widths, context, row);
4024 		if (quantize) {
4025 			if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
4026 				ret_val = FALSE;
4027 				break;
4028 			}
4029 		} else {
4030 			if (max_height != -1 && yd > max_height) {
4031 				ret_val = FALSE;
4032 				break;
4033 			}
4034 		}
4035 
4036 		yd += row_height;
4037 
4038 		yd++;
4039 	}
4040 
4041 	g_free (widths);
4042 
4043 	g_signal_stop_emission_by_name (ep, "will_fit");
4044 	return ret_val;
4045 }
4046 
4047 static void
e_table_item_printable_destroy(gpointer data,GObject * where_object_was)4048 e_table_item_printable_destroy (gpointer data,
4049                                 GObject *where_object_was)
4050 {
4051 	ETableItemPrintContext *itemcontext = data;
4052 
4053 	g_object_unref (itemcontext->item);
4054 	g_free (itemcontext);
4055 }
4056 
4057 /**
4058  * e_table_item_get_printable
4059  * @eti: %ETableItem which will be printed
4060  *
4061  * This routine creates and returns an %EPrintable that can be used to
4062  * print the given %ETableItem.
4063  *
4064  * Returns: The %EPrintable.
4065  */
4066 EPrintable *
e_table_item_get_printable(ETableItem * item)4067 e_table_item_get_printable (ETableItem *item)
4068 {
4069 	EPrintable *printable = e_printable_new ();
4070 	ETableItemPrintContext *itemcontext;
4071 
4072 	itemcontext = g_new (ETableItemPrintContext, 1);
4073 	itemcontext->item = item;
4074 	g_object_ref (item);
4075 	itemcontext->rows_printed = 0;
4076 
4077 	g_signal_connect (
4078 		printable, "print_page",
4079 		G_CALLBACK (e_table_item_print_page), itemcontext);
4080 	g_signal_connect (
4081 		printable, "data_left",
4082 		G_CALLBACK (e_table_item_data_left), itemcontext);
4083 	g_signal_connect (
4084 		printable, "reset",
4085 		G_CALLBACK (e_table_item_reset), itemcontext);
4086 	g_signal_connect (
4087 		printable, "height",
4088 		G_CALLBACK (e_table_item_height), itemcontext);
4089 	g_signal_connect (
4090 		printable, "will_fit",
4091 		G_CALLBACK (e_table_item_will_fit), itemcontext);
4092 
4093 	g_object_weak_ref (
4094 		G_OBJECT (printable),
4095 		e_table_item_printable_destroy, itemcontext);
4096 
4097 	return printable;
4098 }
4099 
4100 /**
4101  * e_table_item_is_editing:
4102  * @eti: an %ETableItem
4103  *
4104  * Returns: Whether the table item is currently editing cell content.
4105  **/
4106 gboolean
e_table_item_is_editing(ETableItem * eti)4107 e_table_item_is_editing (ETableItem *eti)
4108 {
4109 	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), FALSE);
4110 
4111 	return eti_editing (eti);
4112 }
4113 
4114 /**
4115  * e_table_item_cursor_scrolled:
4116  * @eti: an %ETableItem
4117  *
4118  * Does necessary recalculations after cursor scrolled, like whether
4119  * the cursor is on screen or not anymore.
4120  **/
4121 void
e_table_item_cursor_scrolled(ETableItem * eti)4122 e_table_item_cursor_scrolled (ETableItem *eti)
4123 {
4124 	g_return_if_fail (E_IS_TABLE_ITEM (eti));
4125 
4126 	eti_check_cursor_bounds (eti);
4127 }
4128 
4129 void
e_table_item_cancel_scroll_to_cursor(ETableItem * eti)4130 e_table_item_cancel_scroll_to_cursor (ETableItem *eti)
4131 {
4132 	ETableItemPrivate *priv;
4133 
4134 	g_return_if_fail (E_IS_TABLE_ITEM (eti));
4135 
4136 	priv = E_TABLE_ITEM_GET_PRIVATE (eti);
4137 
4138 	if (priv->show_cursor_delay_source) {
4139 		g_source_destroy (priv->show_cursor_delay_source);
4140 		g_source_unref (priv->show_cursor_delay_source);
4141 		priv->show_cursor_delay_source = NULL;
4142 	}
4143 }
4144