1 /*
2  * e-table.c - A graphical view of a Table.
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  *
17  * Authors:
18  *		Chris Lahey <clahey@ximian.com>
19  *		Miguel de Icaza <miguel@ximian.com>
20  *
21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22  *
23  */
24 
25 #include "evolution-config.h"
26 
27 #include "e-table.h"
28 
29 #include <stdlib.h>
30 #include <string.h>
31 #include <stdio.h>
32 
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 #include <glib/gstdio.h>
36 #include <gdk/gdkkeysyms.h>
37 
38 #include <libgnomecanvas/libgnomecanvas.h>
39 
40 #include "e-canvas-background.h"
41 #include "e-canvas-utils.h"
42 #include "e-canvas-vbox.h"
43 #include "e-canvas.h"
44 #include "e-table-click-to-add.h"
45 #include "e-table-column-specification.h"
46 #include "e-table-group-leaf.h"
47 #include "e-table-header-item.h"
48 #include "e-table-header-utils.h"
49 #include "e-table-subset.h"
50 #include "e-table-utils.h"
51 #include "e-text.h"
52 #include "e-unicode.h"
53 #include "e-misc-utils.h"
54 #include "gal-a11y-e-table.h"
55 
56 #define COLUMN_HEADER_HEIGHT 16
57 
58 #define d(x)
59 
60 #if d(!)0
61 #define e_table_item_leave_edit_(x) \
62 	(e_table_item_leave_edit ((x)), \
63 	 g_print ("%s: e_table_item_leave_edit\n", G_STRFUNC))
64 #else
65 #define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
66 #endif
67 
68 struct _ETablePrivate {
69 	GnomeCanvasItem *info_text;
70 	guint info_text_resize_id;
71 };
72 
73 enum {
74 	CURSOR_CHANGE,
75 	CURSOR_ACTIVATED,
76 	SELECTION_CHANGE,
77 	DOUBLE_CLICK,
78 	RIGHT_CLICK,
79 	CLICK,
80 	KEY_PRESS,
81 	START_DRAG,
82 	STATE_CHANGE,
83 	WHITE_SPACE_EVENT,
84 
85 	CUT_CLIPBOARD,
86 	COPY_CLIPBOARD,
87 	PASTE_CLIPBOARD,
88 	SELECT_ALL,
89 
90 	TABLE_DRAG_BEGIN,
91 	TABLE_DRAG_END,
92 	TABLE_DRAG_DATA_GET,
93 	TABLE_DRAG_DATA_DELETE,
94 
95 	TABLE_DRAG_LEAVE,
96 	TABLE_DRAG_MOTION,
97 	TABLE_DRAG_DROP,
98 	TABLE_DRAG_DATA_RECEIVED,
99 
100 	LAST_SIGNAL
101 };
102 
103 enum {
104 	PROP_0,
105 	PROP_LENGTH_THRESHOLD,
106 	PROP_MODEL,
107 	PROP_UNIFORM_ROW_HEIGHT,
108 	PROP_ALWAYS_SEARCH,
109 	PROP_USE_CLICK_TO_ADD,
110 	PROP_HADJUSTMENT,
111 	PROP_VADJUSTMENT,
112 	PROP_HSCROLL_POLICY,
113 	PROP_VSCROLL_POLICY,
114 	PROP_IS_EDITING
115 };
116 
117 enum {
118 	ET_SCROLL_UP = 1 << 0,
119 	ET_SCROLL_DOWN = 1 << 1,
120 	ET_SCROLL_LEFT = 1 << 2,
121 	ET_SCROLL_RIGHT = 1 << 3
122 };
123 
124 static guint et_signals[LAST_SIGNAL] = { 0 };
125 
126 static void e_table_fill_table (ETable *e_table, ETableModel *model);
127 static gboolean changed_idle (gpointer data);
128 
129 static void et_grab_focus (GtkWidget *widget);
130 
131 static void et_drag_begin (GtkWidget *widget,
132 			   GdkDragContext *context,
133 			   ETable *et);
134 static void et_drag_end (GtkWidget *widget,
135 			 GdkDragContext *context,
136 			 ETable *et);
137 static void et_drag_data_get (GtkWidget *widget,
138 			     GdkDragContext *context,
139 			     GtkSelectionData *selection_data,
140 			     guint info,
141 			     guint time,
142 			     ETable *et);
143 static void et_drag_data_delete (GtkWidget *widget,
144 				GdkDragContext *context,
145 				ETable *et);
146 
147 static void et_drag_leave (GtkWidget *widget,
148 			  GdkDragContext *context,
149 			  guint time,
150 			  ETable *et);
151 static gboolean et_drag_motion (GtkWidget *widget,
152 			       GdkDragContext *context,
153 			       gint x,
154 			       gint y,
155 			       guint time,
156 			       ETable *et);
157 static gboolean et_drag_drop (GtkWidget *widget,
158 			     GdkDragContext *context,
159 			     gint x,
160 			     gint y,
161 			     guint time,
162 			     ETable *et);
163 static void et_drag_data_received (GtkWidget *widget,
164 				  GdkDragContext *context,
165 				  gint x,
166 				  gint y,
167 				  GtkSelectionData *selection_data,
168 				  guint info,
169 				  guint time,
170 				  ETable *et);
171 
172 static gint et_focus (GtkWidget *container, GtkDirectionType direction);
173 
174 static void scroll_off (ETable *et);
175 static void scroll_on (ETable *et, guint scroll_direction);
176 
177 static void e_table_scrollable_init (GtkScrollableInterface *iface);
178 
G_DEFINE_TYPE_WITH_CODE(ETable,e_table,GTK_TYPE_TABLE,G_ADD_PRIVATE (ETable)G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE,e_table_scrollable_init))179 G_DEFINE_TYPE_WITH_CODE (ETable, e_table, GTK_TYPE_TABLE,
180 			 G_ADD_PRIVATE (ETable)
181 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, e_table_scrollable_init))
182 
183 static void
184 et_disconnect_model (ETable *et)
185 {
186 	if (et->model == NULL)
187 		return;
188 
189 	if (et->table_model_change_id != 0)
190 		g_signal_handler_disconnect (
191 			et->model, et->table_model_change_id);
192 	if (et->table_row_change_id != 0)
193 		g_signal_handler_disconnect (
194 			et->model, et->table_row_change_id);
195 	if (et->table_cell_change_id != 0)
196 		g_signal_handler_disconnect (
197 			et->model, et->table_cell_change_id);
198 	if (et->table_rows_inserted_id != 0)
199 		g_signal_handler_disconnect (
200 			et->model, et->table_rows_inserted_id);
201 	if (et->table_rows_deleted_id != 0)
202 		g_signal_handler_disconnect (
203 			et->model, et->table_rows_deleted_id);
204 
205 	et->table_model_change_id = 0;
206 	et->table_row_change_id = 0;
207 	et->table_cell_change_id = 0;
208 	et->table_rows_inserted_id = 0;
209 	et->table_rows_deleted_id = 0;
210 }
211 
212 static void
e_table_state_change(ETable * et)213 e_table_state_change (ETable *et)
214 {
215 	if (et->state_change_freeze)
216 		et->state_changed = TRUE;
217 	else
218 		g_signal_emit (et, et_signals[STATE_CHANGE], 0);
219 }
220 
221 #define CHECK_HORIZONTAL(et) \
222 	if ((et)->horizontal_scrolling || (et)->horizontal_resize) \
223 		e_table_header_update_horizontal (et->header);
224 
225 static void
clear_current_search_col(ETable * et)226 clear_current_search_col (ETable *et)
227 {
228 	et->search_col_set = FALSE;
229 }
230 
231 static ETableCol *
current_search_col(ETable * et)232 current_search_col (ETable *et)
233 {
234 	if (!et->search_col_set) {
235 		et->current_search_col =
236 			e_table_util_calculate_current_search_col (
237 				et->header,
238 				et->full_header,
239 				et->sort_info,
240 				et->always_search);
241 		et->search_col_set = TRUE;
242 	}
243 
244 	return et->current_search_col;
245 }
246 
247 static void
et_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)248 et_get_preferred_width (GtkWidget *widget,
249                         gint *minimum,
250                         gint *natural)
251 {
252 	ETable *et = E_TABLE (widget);
253 
254 	GTK_WIDGET_CLASS (e_table_parent_class)->
255 		get_preferred_width (widget, minimum, natural);
256 
257 	if (et->horizontal_resize) {
258 		*minimum = MAX (*minimum, et->header_width);
259 		*natural = MAX (*natural, et->header_width);
260 	}
261 }
262 
263 static void
et_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)264 et_get_preferred_height (GtkWidget *widget,
265                          gint *minimum,
266                          gint *natural)
267 {
268 	GTK_WIDGET_CLASS (e_table_parent_class)->
269 		get_preferred_height (widget, minimum, natural);
270 }
271 
272 static void
set_header_width(ETable * et)273 set_header_width (ETable *et)
274 {
275 	if (et->horizontal_resize) {
276 		et->header_width = e_table_header_min_width (et->header);
277 		gtk_widget_queue_resize (GTK_WIDGET (et));
278 	}
279 }
280 
281 static void
structure_changed(ETableHeader * header,ETable * et)282 structure_changed (ETableHeader *header,
283                    ETable *et)
284 {
285 	e_table_state_change (et);
286 	set_header_width (et);
287 	clear_current_search_col (et);
288 }
289 
290 static void
expansion_changed(ETableHeader * header,ETable * et)291 expansion_changed (ETableHeader *header,
292                    ETable *et)
293 {
294 	e_table_state_change (et);
295 	set_header_width (et);
296 }
297 
298 static void
dimension_changed(ETableHeader * header,gint total_width,ETable * et)299 dimension_changed (ETableHeader *header,
300                    gint total_width,
301                    ETable *et)
302 {
303 	set_header_width (et);
304 }
305 
306 static void
disconnect_header(ETable * e_table)307 disconnect_header (ETable *e_table)
308 {
309 	if (e_table->header == NULL)
310 		return;
311 
312 	if (e_table->structure_change_id)
313 		g_signal_handler_disconnect (
314 			e_table->header, e_table->structure_change_id);
315 	if (e_table->expansion_change_id)
316 		g_signal_handler_disconnect (
317 			e_table->header, e_table->expansion_change_id);
318 	if (e_table->dimension_change_id)
319 		g_signal_handler_disconnect (
320 			e_table->header, e_table->dimension_change_id);
321 
322 	g_object_unref (e_table->header);
323 	e_table->header = NULL;
324 }
325 
326 static void
connect_header(ETable * e_table,ETableState * state)327 connect_header (ETable *e_table,
328                 ETableState *state)
329 {
330 	if (e_table->header != NULL)
331 		disconnect_header (e_table);
332 
333 	e_table->header = e_table_state_to_header (
334 		GTK_WIDGET (e_table), e_table->full_header, state);
335 
336 	e_table->structure_change_id = g_signal_connect (
337 		e_table->header, "structure_change",
338 		G_CALLBACK (structure_changed), e_table);
339 	e_table->expansion_change_id = g_signal_connect (
340 		e_table->header, "expansion_change",
341 		G_CALLBACK (expansion_changed), e_table);
342 	e_table->dimension_change_id = g_signal_connect (
343 		e_table->header, "dimension_change",
344 		G_CALLBACK (dimension_changed), e_table);
345 }
346 
347 static void
et_dispose(GObject * object)348 et_dispose (GObject *object)
349 {
350 	ETable *et = E_TABLE (object);
351 
352 	et_disconnect_model (et);
353 
354 	if (et->priv->info_text != NULL) {
355 		g_object_run_dispose (G_OBJECT (et->priv->info_text));
356 		et->priv->info_text = NULL;
357 	}
358 	et->priv->info_text_resize_id = 0;
359 
360 	if (et->search) {
361 		if (et->search_search_id)
362 			g_signal_handler_disconnect (
363 				et->search, et->search_search_id);
364 		if (et->search_accept_id)
365 			g_signal_handler_disconnect (
366 				et->search, et->search_accept_id);
367 		g_object_unref (et->search);
368 		et->search = NULL;
369 	}
370 
371 	if (et->group_info_change_id) {
372 		g_signal_handler_disconnect (
373 			et->sort_info, et->group_info_change_id);
374 		et->group_info_change_id = 0;
375 	}
376 
377 	if (et->sort_info_change_id) {
378 		g_signal_handler_disconnect (
379 			et->sort_info, et->sort_info_change_id);
380 		et->sort_info_change_id = 0;
381 	}
382 
383 	if (et->reflow_idle_id) {
384 		g_source_remove (et->reflow_idle_id);
385 		et->reflow_idle_id = 0;
386 	}
387 
388 	scroll_off (et);
389 
390 	disconnect_header (et);
391 
392 	g_clear_object (&et->model);
393 	g_clear_object (&et->full_header);
394 	g_clear_object (&et->sort_info);
395 	g_clear_object (&et->sorter);
396 	g_clear_object (&et->selection);
397 	g_clear_object (&et->spec);
398 
399 	if (et->header_canvas != NULL) {
400 		gtk_widget_destroy (GTK_WIDGET (et->header_canvas));
401 		et->header_canvas = NULL;
402 	}
403 
404 	if (et->site != NULL) {
405 		e_table_drag_source_unset (et);
406 		et->site = NULL;
407 	}
408 
409 	if (et->table_canvas != NULL) {
410 		gtk_widget_destroy (GTK_WIDGET (et->table_canvas));
411 		et->table_canvas = NULL;
412 	}
413 
414 	if (et->rebuild_idle_id != 0) {
415 		g_source_remove (et->rebuild_idle_id);
416 		et->rebuild_idle_id = 0;
417 	}
418 
419 	g_free (et->click_to_add_message);
420 	et->click_to_add_message = NULL;
421 
422 	g_free (et->domain);
423 	et->domain = NULL;
424 
425 	G_OBJECT_CLASS (e_table_parent_class)->dispose (object);
426 }
427 
428 static void
et_unrealize(GtkWidget * widget)429 et_unrealize (GtkWidget *widget)
430 {
431 	scroll_off (E_TABLE (widget));
432 
433 	if (GTK_WIDGET_CLASS (e_table_parent_class)->unrealize)
434 		GTK_WIDGET_CLASS (e_table_parent_class)->unrealize (widget);
435 }
436 
437 static gboolean
check_row(ETable * et,gint model_row,gint col,ETableSearchFunc search,gchar * string)438 check_row (ETable *et,
439            gint model_row,
440            gint col,
441            ETableSearchFunc search,
442            gchar *string)
443 {
444 	gconstpointer value;
445 
446 	value = e_table_model_value_at (et->model, col, model_row);
447 
448 	return search (value, string);
449 }
450 
451 static gboolean
et_search_search(ETableSearch * search,gchar * string,ETableSearchFlags flags,ETable * et)452 et_search_search (ETableSearch *search,
453                   gchar *string,
454                   ETableSearchFlags flags,
455                   ETable *et)
456 {
457 	gint cursor;
458 	gint rows;
459 	gint i;
460 	ETableCol *col = current_search_col (et);
461 
462 	if (col == NULL)
463 		return FALSE;
464 
465 	rows = e_table_model_row_count (et->model);
466 
467 	g_object_get (
468 		et->selection,
469 		"cursor_row", &cursor,
470 		NULL);
471 
472 	if ((flags & E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST) &&
473 		cursor < rows && cursor >= 0 &&
474 		check_row (et, cursor, col->spec->model_col, col->search, string))
475 		return TRUE;
476 
477 	cursor = e_sorter_model_to_sorted (E_SORTER (et->sorter), cursor);
478 
479 	for (i = cursor + 1; i < rows; i++) {
480 		gint model_row = e_sorter_sorted_to_model (E_SORTER (et->sorter), i);
481 		if (check_row (et, model_row, col->spec->model_col, col->search, string)) {
482 			e_selection_model_select_as_key_press (
483 				E_SELECTION_MODEL (et->selection),
484 				model_row, col->spec->model_col,
485 				GDK_CONTROL_MASK);
486 			return TRUE;
487 		}
488 	}
489 
490 	for (i = 0; i < cursor; i++) {
491 		gint model_row = e_sorter_sorted_to_model (E_SORTER (et->sorter), i);
492 		if (check_row (et, model_row, col->spec->model_col, col->search, string)) {
493 			e_selection_model_select_as_key_press (
494 				E_SELECTION_MODEL (et->selection),
495 				model_row, col->spec->model_col,
496 				GDK_CONTROL_MASK);
497 			return TRUE;
498 		}
499 	}
500 
501 	cursor = e_sorter_sorted_to_model (E_SORTER (et->sorter), cursor);
502 
503 	/* Check if the cursor row is the only matching row. */
504 	return (!(flags & E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST) &&
505 		cursor < rows && cursor >= 0 &&
506 		check_row (et, cursor, col->spec->model_col, col->search, string));
507 }
508 
509 static void
et_search_accept(ETableSearch * search,ETable * et)510 et_search_accept (ETableSearch *search,
511                   ETable *et)
512 {
513 	gint cursor;
514 	ETableCol *col = current_search_col (et);
515 
516 	if (col == NULL)
517 		return;
518 
519 	g_object_get (et->selection, "cursor_row", &cursor, NULL);
520 
521 	e_selection_model_select_as_key_press (
522 		E_SELECTION_MODEL (et->selection),
523 		cursor, col->spec->model_col, 0);
524 }
525 
526 static void
init_search(ETable * e_table)527 init_search (ETable *e_table)
528 {
529 	if (e_table->search != NULL)
530 		return;
531 
532 	e_table->search = e_table_search_new ();
533 
534 	e_table->search_search_id = g_signal_connect (
535 		e_table->search, "search",
536 		G_CALLBACK (et_search_search), e_table);
537 	e_table->search_accept_id = g_signal_connect (
538 		e_table->search, "accept",
539 		G_CALLBACK (et_search_accept), e_table);
540 }
541 
542 static void
et_finalize(GObject * object)543 et_finalize (GObject *object)
544 {
545 	ETable *et = E_TABLE (object);
546 
547 	g_free (et->click_to_add_message);
548 	et->click_to_add_message = NULL;
549 
550 	g_free (et->domain);
551 	et->domain = NULL;
552 
553 	G_OBJECT_CLASS (e_table_parent_class)->finalize (object);
554 }
555 
556 static void
e_table_init(ETable * e_table)557 e_table_init (ETable *e_table)
558 {
559 	e_table->priv = e_table_get_instance_private (e_table);
560 
561 	gtk_widget_set_can_focus (GTK_WIDGET (e_table), TRUE);
562 
563 	gtk_table_set_homogeneous (GTK_TABLE (e_table), FALSE);
564 
565 	e_table->sort_info = NULL;
566 	e_table->group_info_change_id = 0;
567 	e_table->sort_info_change_id = 0;
568 	e_table->structure_change_id = 0;
569 	e_table->expansion_change_id = 0;
570 	e_table->dimension_change_id = 0;
571 	e_table->reflow_idle_id = 0;
572 	e_table->scroll_idle_id = 0;
573 
574 	e_table->alternating_row_colors = 1;
575 	e_table->horizontal_draw_grid = 1;
576 	e_table->vertical_draw_grid = 1;
577 	e_table->draw_focus = 1;
578 	e_table->cursor_mode = E_CURSOR_SIMPLE;
579 	e_table->length_threshold = 200;
580 	e_table->uniform_row_height = FALSE;
581 
582 	e_table->need_rebuild = 0;
583 	e_table->rebuild_idle_id = 0;
584 
585 	e_table->horizontal_scrolling = FALSE;
586 	e_table->horizontal_resize = FALSE;
587 
588 	e_table->click_to_add_message = NULL;
589 	e_table->domain = NULL;
590 
591 	e_table->drop_row = -1;
592 	e_table->drop_col = -1;
593 	e_table->site = NULL;
594 
595 	e_table->do_drag = 0;
596 
597 	e_table->sorter = NULL;
598 	e_table->selection = e_table_selection_model_new ();
599 	e_table->cursor_loc = E_TABLE_CURSOR_LOC_NONE;
600 	e_table->spec = NULL;
601 
602 	e_table->always_search = g_getenv ("GAL_ALWAYS_SEARCH") ? TRUE : FALSE;
603 
604 	e_table->search = NULL;
605 	e_table->search_search_id = 0;
606 	e_table->search_accept_id = 0;
607 
608 	e_table->current_search_col = NULL;
609 
610 	e_table->header_width = 0;
611 
612 	e_table->state_changed = FALSE;
613 	e_table->state_change_freeze = 0;
614 }
615 
616 /* Grab_focus handler for the ETable */
617 static void
et_grab_focus(GtkWidget * widget)618 et_grab_focus (GtkWidget *widget)
619 {
620 	ETable *e_table;
621 
622 	e_table = E_TABLE (widget);
623 
624 	gtk_widget_grab_focus (GTK_WIDGET (e_table->table_canvas));
625 }
626 
627 /* Focus handler for the ETable */
628 static gint
et_focus(GtkWidget * container,GtkDirectionType direction)629 et_focus (GtkWidget *container,
630           GtkDirectionType direction)
631 {
632 	ETable *e_table;
633 
634 	e_table = E_TABLE (container);
635 
636 	if (gtk_container_get_focus_child (GTK_CONTAINER (container))) {
637 		gtk_container_set_focus_child (GTK_CONTAINER (container), NULL);
638 		return FALSE;
639 	}
640 
641 	return gtk_widget_child_focus (GTK_WIDGET (e_table->table_canvas), direction);
642 }
643 
644 static void
set_header_canvas_width(ETable * e_table)645 set_header_canvas_width (ETable *e_table)
646 {
647 	gdouble oldwidth, oldheight, width;
648 
649 	if (!(e_table->header_item && e_table->header_canvas && e_table->table_canvas))
650 		return;
651 
652 	gnome_canvas_get_scroll_region (
653 		GNOME_CANVAS (e_table->table_canvas),
654 		NULL, NULL, &width, NULL);
655 	gnome_canvas_get_scroll_region (
656 		GNOME_CANVAS (e_table->header_canvas),
657 		NULL, NULL, &oldwidth, &oldheight);
658 
659 	if (oldwidth != width ||
660 	    oldheight != E_TABLE_HEADER_ITEM (e_table->header_item)->height - 1)
661 		gnome_canvas_set_scroll_region (
662 			GNOME_CANVAS (e_table->header_canvas),
663 			0, 0, width, /*  COLUMN_HEADER_HEIGHT - 1 */
664 			E_TABLE_HEADER_ITEM (e_table->header_item)->height - 1);
665 
666 }
667 
668 static void
header_canvas_size_allocate(GtkWidget * widget,GtkAllocation * alloc,ETable * e_table)669 header_canvas_size_allocate (GtkWidget *widget,
670                              GtkAllocation *alloc,
671                              ETable *e_table)
672 {
673 	GtkAllocation allocation;
674 
675 	set_header_canvas_width (e_table);
676 
677 	gtk_widget_get_allocation (
678 		GTK_WIDGET (e_table->header_canvas), &allocation);
679 
680 	/* When the header item is created ->height == 0,
681 	 * as the font is only created when everything is realized.
682 	 * So we set the usize here as well, so that the size of the
683 	 * header is correct */
684 	if (allocation.height != E_TABLE_HEADER_ITEM (e_table->header_item)->height)
685 		g_object_set (
686 			e_table->header_canvas, "height-request",
687 			E_TABLE_HEADER_ITEM (e_table->header_item)->height,
688 			NULL);
689 }
690 
691 static void
group_info_changed(ETableSortInfo * info,ETable * et)692 group_info_changed (ETableSortInfo *info,
693                     ETable *et)
694 {
695 	gboolean will_be_grouped = e_table_sort_info_grouping_get_count (info) > 0;
696 	clear_current_search_col (et);
697 	if (et->is_grouped || will_be_grouped) {
698 		et->need_rebuild = TRUE;
699 		if (!et->rebuild_idle_id) {
700 			g_object_run_dispose (G_OBJECT (et->group));
701 			et->group = NULL;
702 			et->rebuild_idle_id = g_idle_add_full (20, changed_idle, et, NULL);
703 		}
704 	}
705 	e_table_state_change (et);
706 }
707 
708 static void
sort_info_changed(ETableSortInfo * info,ETable * et)709 sort_info_changed (ETableSortInfo *info,
710                    ETable *et)
711 {
712 	clear_current_search_col (et);
713 	e_table_state_change (et);
714 }
715 
716 static void
e_table_setup_header(ETable * e_table)717 e_table_setup_header (ETable *e_table)
718 {
719 	gchar *pointer;
720 	e_table->header_canvas = GNOME_CANVAS (e_canvas_new ());
721 	gtk_style_context_add_class (
722 		gtk_widget_get_style_context (GTK_WIDGET (e_table->header_canvas)),
723 		"table-header");
724 
725 	gtk_widget_show (GTK_WIDGET (e_table->header_canvas));
726 
727 	pointer = g_strdup_printf ("%p", (gpointer) e_table);
728 
729 	e_table->header_item = gnome_canvas_item_new (
730 		gnome_canvas_root (e_table->header_canvas),
731 		e_table_header_item_get_type (),
732 		"ETableHeader", e_table->header,
733 		"full_header", e_table->full_header,
734 		"sort_info", e_table->sort_info,
735 		"dnd_code", pointer,
736 		"table", e_table,
737 		NULL);
738 
739 	g_free (pointer);
740 
741 	g_signal_connect (
742 		e_table->header_canvas, "size_allocate",
743 		G_CALLBACK (header_canvas_size_allocate), e_table);
744 
745 	g_object_set (
746 		e_table->header_canvas, "height-request",
747 		E_TABLE_HEADER_ITEM (e_table->header_item)->height, NULL);
748 }
749 
750 static gboolean
table_canvas_reflow_idle(ETable * e_table)751 table_canvas_reflow_idle (ETable *e_table)
752 {
753 	gdouble height, width;
754 	gdouble oldheight, oldwidth;
755 	GtkAllocation allocation;
756 
757 	gtk_widget_get_allocation (
758 		GTK_WIDGET (e_table->table_canvas), &allocation);
759 
760 	g_object_get (
761 		e_table->canvas_vbox,
762 		"height", &height, "width", &width, NULL);
763 	height = MAX ((gint) height, allocation.height);
764 	width = MAX ((gint) width, allocation.width);
765 	/* I have no idea why this needs to be -1, but it works. */
766 	gnome_canvas_get_scroll_region (
767 		GNOME_CANVAS (e_table->table_canvas),
768 		NULL, NULL, &oldwidth, &oldheight);
769 
770 	if (oldwidth != width - 1 ||
771 	    oldheight != height - 1) {
772 		gnome_canvas_set_scroll_region (
773 			GNOME_CANVAS (e_table->table_canvas),
774 			0, 0, width - 1, height - 1);
775 		set_header_canvas_width (e_table);
776 	}
777 	e_table->reflow_idle_id = 0;
778 	return FALSE;
779 }
780 
781 static void
table_canvas_size_allocate(GtkWidget * widget,GtkAllocation * alloc,ETable * e_table)782 table_canvas_size_allocate (GtkWidget *widget,
783                             GtkAllocation *alloc,
784                             ETable *e_table)
785 {
786 	gdouble width;
787 	gdouble height;
788 	GValue *val = g_new0 (GValue, 1);
789 	g_value_init (val, G_TYPE_DOUBLE);
790 
791 	width = alloc->width;
792 	g_value_set_double (val, width);
793 	g_object_get (
794 		e_table->canvas_vbox,
795 		"height", &height,
796 		NULL);
797 	height = MAX ((gint) height, alloc->height);
798 
799 	g_object_set (
800 		e_table->canvas_vbox,
801 		"width", width,
802 		NULL);
803 	g_object_set_property (G_OBJECT (e_table->header), "width", val);
804 	g_free (val);
805 	if (e_table->reflow_idle_id)
806 		g_source_remove (e_table->reflow_idle_id);
807 	table_canvas_reflow_idle (e_table);
808 
809 	e_table->size_allocated = TRUE;
810 
811 	if (e_table->need_rebuild && !e_table->rebuild_idle_id)
812 		e_table->rebuild_idle_id = g_idle_add_full (20, changed_idle, e_table, NULL);
813 }
814 
815 static void
table_canvas_reflow(GnomeCanvas * canvas,ETable * e_table)816 table_canvas_reflow (GnomeCanvas *canvas,
817                      ETable *e_table)
818 {
819 	if (!e_table->reflow_idle_id)
820 		e_table->reflow_idle_id = g_idle_add_full (
821 			400, (GSourceFunc) table_canvas_reflow_idle,
822 			e_table, NULL);
823 }
824 
825 static void
click_to_add_cursor_change(ETableClickToAdd * etcta,gint row,gint col,ETable * et)826 click_to_add_cursor_change (ETableClickToAdd *etcta,
827                             gint row,
828                             gint col,
829                             ETable *et)
830 {
831 	if (et->cursor_loc == E_TABLE_CURSOR_LOC_TABLE) {
832 		e_selection_model_clear (E_SELECTION_MODEL (et->selection));
833 	}
834 	et->cursor_loc = E_TABLE_CURSOR_LOC_ETCTA;
835 }
836 
837 static void
group_cursor_change(ETableGroup * etg,gint row,ETable * et)838 group_cursor_change (ETableGroup *etg,
839                      gint row,
840                      ETable *et)
841 {
842 	ETableCursorLoc old_cursor_loc;
843 
844 	old_cursor_loc = et->cursor_loc;
845 
846 	et->cursor_loc = E_TABLE_CURSOR_LOC_TABLE;
847 	g_signal_emit (et, et_signals[CURSOR_CHANGE], 0, row);
848 
849 	if (old_cursor_loc == E_TABLE_CURSOR_LOC_ETCTA && et->click_to_add)
850 		e_table_click_to_add_commit (E_TABLE_CLICK_TO_ADD (et->click_to_add));
851 }
852 
853 static void
group_cursor_activated(ETableGroup * etg,gint row,ETable * et)854 group_cursor_activated (ETableGroup *etg,
855                         gint row,
856                         ETable *et)
857 {
858 	g_signal_emit (et, et_signals[CURSOR_ACTIVATED], 0, row);
859 }
860 
861 static void
group_double_click(ETableGroup * etg,gint row,gint col,GdkEvent * event,ETable * et)862 group_double_click (ETableGroup *etg,
863                     gint row,
864                     gint col,
865                     GdkEvent *event,
866                     ETable *et)
867 {
868 	g_signal_emit (et, et_signals[DOUBLE_CLICK], 0, row, col, event);
869 }
870 
871 static gboolean
group_right_click(ETableGroup * etg,gint row,gint col,GdkEvent * event,ETable * et)872 group_right_click (ETableGroup *etg,
873                    gint row,
874                    gint col,
875                    GdkEvent *event,
876                    ETable *et)
877 {
878 	gboolean return_val = FALSE;
879 
880 	g_signal_emit (
881 		et, et_signals[RIGHT_CLICK], 0,
882 		row, col, event, &return_val);
883 
884 	return return_val;
885 }
886 
887 static gboolean
group_click(ETableGroup * etg,gint row,gint col,GdkEvent * event,ETable * et)888 group_click (ETableGroup *etg,
889              gint row,
890              gint col,
891              GdkEvent *event,
892              ETable *et)
893 {
894 	gboolean return_val = 0;
895 
896 	g_signal_emit (
897 		et, et_signals[CLICK], 0,
898 		row, col, event, &return_val);
899 
900 	return return_val;
901 }
902 
903 static gboolean
group_key_press(ETableGroup * etg,gint row,gint col,GdkEvent * event,ETable * et)904 group_key_press (ETableGroup *etg,
905                  gint row,
906                  gint col,
907                  GdkEvent *event,
908                  ETable *et)
909 {
910 	gboolean return_val = FALSE;
911 	GdkEventKey *key = (GdkEventKey *) event;
912 	gint y, row_local = -1, col_local = -1;
913 	GtkAdjustment *adjustment;
914 	GtkScrollable *scrollable;
915 	gdouble page_size;
916 	gdouble upper;
917 	gdouble value;
918 
919 	scrollable = GTK_SCROLLABLE (et->table_canvas);
920 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
921 
922 	switch (key->keyval) {
923 	case GDK_KEY_Page_Down:
924 	case GDK_KEY_KP_Page_Down:
925 		page_size = gtk_adjustment_get_page_size (adjustment);
926 		upper = gtk_adjustment_get_upper (adjustment);
927 		value = gtk_adjustment_get_value (adjustment);
928 
929 		y = CLAMP (value + (2 * page_size - 50), 0, upper);
930 		y -= value;
931 		e_table_get_cell_at (et, 30, y, &row_local, &col_local);
932 
933 		if (row_local == -1)
934 			row_local = e_table_model_row_count (et->model) - 1;
935 
936 		row_local = e_table_view_to_model_row (et, row_local);
937 		col_local = e_selection_model_cursor_col (E_SELECTION_MODEL (et->selection));
938 		e_selection_model_select_as_key_press (
939 			E_SELECTION_MODEL (et->selection),
940 			row_local, col_local, key->state);
941 		return_val = 1;
942 		break;
943 	case GDK_KEY_Page_Up:
944 	case GDK_KEY_KP_Page_Up:
945 		page_size = gtk_adjustment_get_page_size (adjustment);
946 		upper = gtk_adjustment_get_upper (adjustment);
947 		value = gtk_adjustment_get_value (adjustment);
948 
949 		y = CLAMP (value - (page_size - 50), 0, upper);
950 		y -= value;
951 		e_table_get_cell_at (et, 30, y, &row_local, &col_local);
952 
953 		if (row_local == -1)
954 			row_local = 0;
955 
956 		row_local = e_table_view_to_model_row (et, row_local);
957 		col_local = e_selection_model_cursor_col (E_SELECTION_MODEL (et->selection));
958 		e_selection_model_select_as_key_press (
959 			E_SELECTION_MODEL (et->selection),
960 			row_local, col_local, key->state);
961 		return_val = 1;
962 		break;
963 	case GDK_KEY_BackSpace:
964 		init_search (et);
965 		if (e_table_search_backspace (et->search))
966 			return TRUE;
967 		/* Fall through */
968 	default:
969 		init_search (et);
970 		if ((key->state & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK |
971 			GDK_MOD1_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK |
972 			GDK_MOD4_MASK | GDK_MOD5_MASK)) == 0
973 		    && ((key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) ||
974 			(key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) ||
975 			(key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9)))
976 			e_table_search_input_character (et->search, key->keyval);
977 		g_signal_emit (
978 			et, et_signals[KEY_PRESS], 0,
979 			row, col, event, &return_val);
980 		break;
981 	}
982 	return return_val;
983 }
984 
985 static gboolean
group_start_drag(ETableGroup * etg,gint row,gint col,GdkEvent * event,ETable * et)986 group_start_drag (ETableGroup *etg,
987                   gint row,
988                   gint col,
989                   GdkEvent *event,
990                   ETable *et)
991 {
992 	gboolean return_val = TRUE;
993 
994 	g_signal_emit (
995 		et, et_signals[START_DRAG], 0,
996 		row, col, event, &return_val);
997 
998 	return return_val;
999 }
1000 
1001 static void
et_table_model_changed(ETableModel * model,ETable * et)1002 et_table_model_changed (ETableModel *model,
1003                         ETable *et)
1004 {
1005 	et->need_rebuild = TRUE;
1006 	if (!et->rebuild_idle_id) {
1007 		g_object_run_dispose (G_OBJECT (et->group));
1008 		et->group = NULL;
1009 		et->rebuild_idle_id = g_idle_add_full (20, changed_idle, et, NULL);
1010 	}
1011 }
1012 
1013 static void
et_table_row_changed(ETableModel * table_model,gint row,ETable * et)1014 et_table_row_changed (ETableModel *table_model,
1015                       gint row,
1016                       ETable *et)
1017 {
1018 	if (!et->need_rebuild) {
1019 		if (e_table_group_remove (et->group, row))
1020 			e_table_group_add (et->group, row);
1021 		CHECK_HORIZONTAL (et);
1022 	}
1023 }
1024 
1025 static void
et_table_cell_changed(ETableModel * table_model,gint view_col,gint row,ETable * et)1026 et_table_cell_changed (ETableModel *table_model,
1027                        gint view_col,
1028                        gint row,
1029                        ETable *et)
1030 {
1031 	et_table_row_changed (table_model, row, et);
1032 }
1033 
1034 static void
et_table_rows_inserted(ETableModel * table_model,gint row,gint count,ETable * et)1035 et_table_rows_inserted (ETableModel *table_model,
1036                         gint row,
1037                         gint count,
1038                         ETable *et)
1039 {
1040 	/* This number has already been decremented. */
1041 	gint row_count = e_table_model_row_count (table_model);
1042 	if (!et->need_rebuild) {
1043 		gint i;
1044 		if (row != row_count - count)
1045 			e_table_group_increment (et->group, row, count);
1046 		for (i = 0; i < count; i++)
1047 			e_table_group_add (et->group, row + i);
1048 		CHECK_HORIZONTAL (et);
1049 	}
1050 }
1051 
1052 static void
et_table_rows_deleted(ETableModel * table_model,gint row,gint count,ETable * et)1053 et_table_rows_deleted (ETableModel *table_model,
1054                        gint row,
1055                        gint count,
1056                        ETable *et)
1057 {
1058 	gint row_count = e_table_model_row_count (table_model);
1059 	if (!et->need_rebuild) {
1060 		gint i;
1061 		for (i = 0; i < count; i++)
1062 			e_table_group_remove (et->group, row + i);
1063 		if (row != row_count)
1064 			e_table_group_decrement (et->group, row, count);
1065 		CHECK_HORIZONTAL (et);
1066 	}
1067 }
1068 
1069 static void
group_is_editing_changed_cb(ETableClickToAdd * etcta,GParamSpec * param,ETable * table)1070 group_is_editing_changed_cb (ETableClickToAdd *etcta,
1071                              GParamSpec *param,
1072                              ETable *table)
1073 {
1074 	g_return_if_fail (E_IS_TABLE (table));
1075 
1076 	g_object_notify (G_OBJECT (table), "is-editing");
1077 }
1078 
1079 static void
et_build_groups(ETable * et)1080 et_build_groups (ETable *et)
1081 {
1082 	gboolean was_grouped = et->is_grouped;
1083 	gboolean alternating_row_colors;
1084 
1085 	et->is_grouped = e_table_sort_info_grouping_get_count (et->sort_info) > 0;
1086 
1087 	et->group = e_table_group_new (
1088 		GNOME_CANVAS_GROUP (et->canvas_vbox),
1089 		et->full_header,
1090 		et->header,
1091 		et->model,
1092 		et->sort_info,
1093 		0);
1094 
1095 	if (et->use_click_to_add_end)
1096 		e_canvas_vbox_add_item_start (
1097 			E_CANVAS_VBOX (et->canvas_vbox),
1098 			GNOME_CANVAS_ITEM (et->group));
1099 	else
1100 		e_canvas_vbox_add_item (
1101 			E_CANVAS_VBOX (et->canvas_vbox),
1102 			GNOME_CANVAS_ITEM (et->group));
1103 
1104 	alternating_row_colors = et->alternating_row_colors;
1105 	if (alternating_row_colors) {
1106 		gboolean bvalue = TRUE;
1107 
1108 		/* user can only disable this option, if it's enabled by the specification */
1109 		gtk_widget_style_get (GTK_WIDGET (et), "alternating-row-colors", &bvalue, NULL);
1110 
1111 		alternating_row_colors = bvalue ? 1 : 0;
1112 	}
1113 
1114 	gnome_canvas_item_set (
1115 		GNOME_CANVAS_ITEM (et->group),
1116 		"alternating_row_colors", alternating_row_colors,
1117 		"horizontal_draw_grid", et->horizontal_draw_grid,
1118 		"vertical_draw_grid", et->vertical_draw_grid,
1119 		"drawfocus", et->draw_focus,
1120 		"cursor_mode", et->cursor_mode,
1121 		"length_threshold", et->length_threshold,
1122 		"uniform_row_height", et->uniform_row_height && !et->is_grouped,
1123 		"selection_model", et->selection,
1124 		NULL);
1125 
1126 	g_signal_connect (
1127 		et->group, "cursor_change",
1128 		G_CALLBACK (group_cursor_change), et);
1129 	g_signal_connect (
1130 		et->group, "cursor_activated",
1131 		G_CALLBACK (group_cursor_activated), et);
1132 	g_signal_connect (
1133 		et->group, "double_click",
1134 		G_CALLBACK (group_double_click), et);
1135 	g_signal_connect (
1136 		et->group, "right_click",
1137 		G_CALLBACK (group_right_click), et);
1138 	g_signal_connect (
1139 		et->group, "click",
1140 		G_CALLBACK (group_click), et);
1141 	g_signal_connect (
1142 		et->group, "key_press",
1143 		G_CALLBACK (group_key_press), et);
1144 	g_signal_connect (
1145 		et->group, "start_drag",
1146 		G_CALLBACK (group_start_drag), et);
1147 	e_signal_connect_notify (
1148 		et->group, "notify::is-editing",
1149 		G_CALLBACK (group_is_editing_changed_cb), et);
1150 
1151 	if (!(et->is_grouped) && was_grouped)
1152 		et_disconnect_model (et);
1153 
1154 	if (et->is_grouped && (!was_grouped)) {
1155 		et->table_model_change_id = g_signal_connect (
1156 			et->model, "model_changed",
1157 			G_CALLBACK (et_table_model_changed), et);
1158 
1159 		et->table_row_change_id = g_signal_connect (
1160 			et->model, "model_row_changed",
1161 			G_CALLBACK (et_table_row_changed), et);
1162 
1163 		et->table_cell_change_id = g_signal_connect (
1164 			et->model, "model_cell_changed",
1165 			G_CALLBACK (et_table_cell_changed), et);
1166 
1167 		et->table_rows_inserted_id = g_signal_connect (
1168 			et->model, "model_rows_inserted",
1169 			G_CALLBACK (et_table_rows_inserted), et);
1170 
1171 		et->table_rows_deleted_id = g_signal_connect (
1172 			et->model, "model_rows_deleted",
1173 			G_CALLBACK (et_table_rows_deleted), et);
1174 
1175 	}
1176 
1177 	if (et->is_grouped)
1178 		e_table_fill_table (et, et->model);
1179 }
1180 
1181 static gboolean
changed_idle(gpointer data)1182 changed_idle (gpointer data)
1183 {
1184 	ETable *et = E_TABLE (data);
1185 
1186 	/* Wait until we have a valid size allocation. */
1187 	if (et->need_rebuild && et->size_allocated) {
1188 		GtkWidget *widget;
1189 		GtkAllocation allocation;
1190 
1191 		if (et->group)
1192 			g_object_run_dispose (G_OBJECT (et->group));
1193 		et_build_groups (et);
1194 
1195 		widget = GTK_WIDGET (et->table_canvas);
1196 		gtk_widget_get_allocation (widget, &allocation);
1197 
1198 		g_object_set (
1199 			et->canvas_vbox,
1200 			"width", (gdouble) allocation.width,
1201 			NULL);
1202 
1203 		table_canvas_size_allocate (widget, &allocation, et);
1204 
1205 		et->need_rebuild = 0;
1206 	}
1207 
1208 	et->rebuild_idle_id = 0;
1209 
1210 	CHECK_HORIZONTAL (et);
1211 
1212 	return FALSE;
1213 }
1214 
1215 static void
et_canvas_style_updated(GtkWidget * widget)1216 et_canvas_style_updated (GtkWidget *widget)
1217 {
1218 	GdkColor color;
1219 
1220 	GTK_WIDGET_CLASS (e_table_parent_class)->style_updated (widget);
1221 
1222 	e_utils_get_theme_color_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &color);
1223 
1224 	gnome_canvas_item_set (
1225 		E_TABLE (widget)->white_item,
1226 		"fill_color_gdk", &color,
1227 		NULL);
1228 }
1229 
1230 static void
et_canvas_realize(GtkWidget * canvas,ETable * e_table)1231 et_canvas_realize (GtkWidget *canvas,
1232                    ETable *e_table)
1233 {
1234 	GtkWidget *widget;
1235 	GdkColor color;
1236 
1237 	widget = GTK_WIDGET (e_table->table_canvas);
1238 
1239 	e_utils_get_theme_color_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &color);
1240 
1241 	gnome_canvas_item_set (
1242 		e_table->white_item,
1243 		"fill_color_gdk", &color,
1244 		NULL);
1245 
1246 	CHECK_HORIZONTAL (e_table);
1247 	set_header_width (e_table);
1248 }
1249 
1250 static ETableItem *
get_first_etable_item(ETableGroup * table_group)1251 get_first_etable_item (ETableGroup *table_group)
1252 {
1253 	ETableItem *res = NULL;
1254 	GnomeCanvasGroup *group;
1255 	GList *link;
1256 
1257 	g_return_val_if_fail (E_IS_TABLE_GROUP (table_group), NULL);
1258 
1259 	group = GNOME_CANVAS_GROUP (table_group);
1260 	g_return_val_if_fail (group != NULL, NULL);
1261 
1262 	for (link = group->item_list; link && !res; link = g_list_next (link)) {
1263 		GnomeCanvasItem *item;
1264 
1265 		item = GNOME_CANVAS_ITEM (link->data);
1266 
1267 		if (E_IS_TABLE_GROUP (item))
1268 			res = get_first_etable_item (E_TABLE_GROUP (item));
1269 		else if (E_IS_TABLE_ITEM (item)) {
1270 			res = E_TABLE_ITEM (item);
1271 		}
1272 	}
1273 
1274 	return res;
1275 }
1276 
1277 /* Finds the first descendant of the group that is an ETableItem and focuses it */
1278 static void
focus_first_etable_item(ETableGroup * group)1279 focus_first_etable_item (ETableGroup *group)
1280 {
1281 	ETableItem *item;
1282 
1283 	item = get_first_etable_item (group);
1284 	if (item) {
1285 		e_table_item_set_cursor (item, 0, 0);
1286 		gnome_canvas_item_grab_focus (GNOME_CANVAS_ITEM (item));
1287 	}
1288 }
1289 
1290 static gboolean
white_item_event(GnomeCanvasItem * white_item,GdkEvent * event,ETable * e_table)1291 white_item_event (GnomeCanvasItem *white_item,
1292                   GdkEvent *event,
1293                   ETable *e_table)
1294 {
1295 	gboolean return_val = FALSE;
1296 
1297 	g_signal_emit (
1298 		e_table, et_signals[WHITE_SPACE_EVENT], 0,
1299 		event, &return_val);
1300 
1301 	if (!return_val && event && e_table->group) {
1302 		guint event_button = 0;
1303 
1304 		gdk_event_get_button (event, &event_button);
1305 
1306 		if (event->type == GDK_BUTTON_PRESS && (event_button == 1 || event_button == 2)) {
1307 			focus_first_etable_item (e_table->group);
1308 			return_val = TRUE;
1309 		}
1310 	}
1311 
1312 	return return_val;
1313 }
1314 
1315 static void
et_eti_leave_edit(ETable * et)1316 et_eti_leave_edit (ETable *et)
1317 {
1318 	GnomeCanvas *canvas = et->table_canvas;
1319 
1320 	if (gtk_widget_has_focus (GTK_WIDGET (canvas))) {
1321 		GnomeCanvasItem *item = GNOME_CANVAS (canvas)->focused_item;
1322 
1323 		if (E_IS_TABLE_ITEM (item)) {
1324 			e_table_item_leave_edit_(E_TABLE_ITEM (item));
1325 		}
1326 	}
1327 }
1328 
1329 static gint
et_canvas_root_event(GnomeCanvasItem * root,GdkEvent * event,ETable * e_table)1330 et_canvas_root_event (GnomeCanvasItem *root,
1331                       GdkEvent *event,
1332                       ETable *e_table)
1333 {
1334 	switch (event->type) {
1335 	case GDK_BUTTON_PRESS:
1336 	case GDK_2BUTTON_PRESS:
1337 	case GDK_BUTTON_RELEASE:
1338 		if (event->button.button != 4 && event->button.button != 5) {
1339 			et_eti_leave_edit (e_table);
1340 			return TRUE;
1341 		}
1342 		break;
1343 	default:
1344 		break;
1345 	}
1346 
1347 	return FALSE;
1348 }
1349 
1350 /* Handler for focus events in the table_canvas; we have to repaint ourselves
1351  * always, and also give the focus to some ETableItem if we get focused.
1352  */
1353 static gint
table_canvas_focus_event_cb(GtkWidget * widget,GdkEventFocus * event,gpointer data)1354 table_canvas_focus_event_cb (GtkWidget *widget,
1355                              GdkEventFocus *event,
1356                              gpointer data)
1357 {
1358 	GnomeCanvas *canvas;
1359 	ECanvas *ecanvas;
1360 	ETable *etable;
1361 
1362 	gtk_widget_queue_draw (widget);
1363 	canvas = GNOME_CANVAS (widget);
1364 	ecanvas = E_CANVAS (widget);
1365 
1366 	if (!event->in) {
1367 		gtk_im_context_focus_out (ecanvas->im_context);
1368 		return FALSE;
1369 	} else {
1370 		gtk_im_context_focus_in (ecanvas->im_context);
1371 	}
1372 
1373 	etable = E_TABLE (data);
1374 
1375 	if (e_table_model_row_count (etable->model) < 1
1376 	    && (etable->click_to_add)
1377 	    && !(E_TABLE_CLICK_TO_ADD (etable->click_to_add)->row)) {
1378 		gnome_canvas_item_grab_focus (etable->canvas_vbox);
1379 		gnome_canvas_item_grab_focus (etable->click_to_add);
1380 	} else if (!canvas->focused_item && etable->group) {
1381 		focus_first_etable_item (etable->group);
1382 	} else if (canvas->focused_item) {
1383 		ESelectionModel *selection = (ESelectionModel *) etable->selection;
1384 
1385 		/* check whether click_to_add already got the focus */
1386 		if (etable->click_to_add) {
1387 			GnomeCanvasItem *row = E_TABLE_CLICK_TO_ADD (etable->click_to_add)->row;
1388 			if (canvas->focused_item == row)
1389 				return TRUE;
1390 		}
1391 
1392 		if (e_selection_model_cursor_row (selection) == -1)
1393 			focus_first_etable_item (etable->group);
1394 	}
1395 
1396 	return FALSE;
1397 }
1398 
1399 static gboolean
canvas_vbox_event(ECanvasVbox * vbox,GdkEventKey * key,ETable * etable)1400 canvas_vbox_event (ECanvasVbox *vbox,
1401                    GdkEventKey *key,
1402                    ETable *etable)
1403 {
1404 	if (key->type != GDK_KEY_PRESS  &&
1405 		key->type != GDK_KEY_RELEASE) {
1406 		return FALSE;
1407 	}
1408 	switch (key->keyval) {
1409 		case GDK_KEY_Tab:
1410 		case GDK_KEY_KP_Tab:
1411 		case GDK_KEY_ISO_Left_Tab:
1412 			if ((key->state & GDK_CONTROL_MASK) && etable->click_to_add) {
1413 				gnome_canvas_item_grab_focus (etable->click_to_add);
1414 				return TRUE;
1415 			}
1416 			break;
1417 		default:
1418 			break;
1419 	}
1420 
1421 	return FALSE;
1422 }
1423 
1424 static void
click_to_add_is_editing_changed_cb(ETableClickToAdd * etcta,GParamSpec * param,ETable * table)1425 click_to_add_is_editing_changed_cb (ETableClickToAdd *etcta,
1426                                     GParamSpec *param,
1427                                     ETable *table)
1428 {
1429 	g_return_if_fail (E_IS_TABLE (table));
1430 
1431 	g_object_notify (G_OBJECT (table), "is-editing");
1432 }
1433 
1434 static gboolean
click_to_add_event(ETableClickToAdd * etcta,GdkEventKey * key,ETable * etable)1435 click_to_add_event (ETableClickToAdd *etcta,
1436                     GdkEventKey *key,
1437                     ETable *etable)
1438 {
1439 	if (key->type != GDK_KEY_PRESS  &&
1440 		key->type != GDK_KEY_RELEASE) {
1441 		return FALSE;
1442 	}
1443 	switch (key->keyval) {
1444 		case GDK_KEY_Tab:
1445 		case GDK_KEY_KP_Tab:
1446 		case GDK_KEY_ISO_Left_Tab:
1447 			if (key->state & GDK_CONTROL_MASK) {
1448 				if (etable->group) {
1449 					if (e_table_model_row_count (etable->model) > 0)
1450 						focus_first_etable_item (etable->group);
1451 					else
1452 						gtk_widget_child_focus (
1453 							gtk_widget_get_toplevel (
1454 							GTK_WIDGET (etable->table_canvas)),
1455 							GTK_DIR_TAB_FORWARD);
1456 				}
1457 			}
1458 			break;
1459 		default:
1460 			break;
1461 	}
1462 
1463 	return FALSE;
1464 }
1465 
1466 static void
e_table_setup_table(ETable * e_table,ETableHeader * full_header,ETableHeader * header,ETableModel * model)1467 e_table_setup_table (ETable *e_table,
1468                      ETableHeader *full_header,
1469                      ETableHeader *header,
1470                      ETableModel *model)
1471 {
1472 	GtkWidget *widget;
1473 	GdkColor color;
1474 
1475 	e_table->table_canvas = GNOME_CANVAS (e_canvas_new ());
1476 	g_signal_connect (
1477 		e_table->table_canvas, "size_allocate",
1478 		G_CALLBACK (table_canvas_size_allocate), e_table);
1479 	g_signal_connect (
1480 		e_table->table_canvas, "focus_in_event",
1481 		G_CALLBACK (table_canvas_focus_event_cb), e_table);
1482 	g_signal_connect (
1483 		e_table->table_canvas, "focus_out_event",
1484 		G_CALLBACK (table_canvas_focus_event_cb), e_table);
1485 
1486 	g_signal_connect (
1487 		e_table, "drag_begin",
1488 		G_CALLBACK (et_drag_begin), e_table);
1489 	g_signal_connect (
1490 		e_table, "drag_end",
1491 		G_CALLBACK (et_drag_end), e_table);
1492 	g_signal_connect (
1493 		e_table, "drag_data_get",
1494 		G_CALLBACK (et_drag_data_get), e_table);
1495 	g_signal_connect (
1496 		e_table, "drag_data_delete",
1497 		G_CALLBACK (et_drag_data_delete), e_table);
1498 	g_signal_connect (
1499 		e_table, "drag_motion",
1500 		G_CALLBACK (et_drag_motion), e_table);
1501 	g_signal_connect (
1502 		e_table, "drag_leave",
1503 		G_CALLBACK (et_drag_leave), e_table);
1504 	g_signal_connect (
1505 		e_table, "drag_drop",
1506 		G_CALLBACK (et_drag_drop), e_table);
1507 	g_signal_connect (
1508 		e_table, "drag_data_received",
1509 		G_CALLBACK (et_drag_data_received), e_table);
1510 
1511 	g_signal_connect (
1512 		e_table->table_canvas, "reflow",
1513 		G_CALLBACK (table_canvas_reflow), e_table);
1514 
1515 	widget = GTK_WIDGET (e_table->table_canvas);
1516 
1517 	gtk_widget_show (widget);
1518 
1519 	e_utils_get_theme_color_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &color);
1520 
1521 	e_table->white_item = gnome_canvas_item_new (
1522 		gnome_canvas_root (e_table->table_canvas),
1523 		e_canvas_background_get_type (),
1524 		"fill_color_gdk", &color,
1525 		NULL);
1526 
1527 	g_signal_connect (
1528 		e_table->white_item, "event",
1529 		G_CALLBACK (white_item_event), e_table);
1530 
1531 	g_signal_connect (
1532 		e_table->table_canvas, "realize",
1533 		G_CALLBACK (et_canvas_realize), e_table);
1534 
1535 	g_signal_connect (
1536 		gnome_canvas_root (e_table->table_canvas), "event",
1537 		G_CALLBACK (et_canvas_root_event), e_table);
1538 
1539 	e_table->canvas_vbox = gnome_canvas_item_new (
1540 		gnome_canvas_root (e_table->table_canvas),
1541 		e_canvas_vbox_get_type (),
1542 		"spacing", 10.0,
1543 		NULL);
1544 
1545 	g_signal_connect (
1546 		e_table->canvas_vbox, "event",
1547 		G_CALLBACK (canvas_vbox_event), e_table);
1548 
1549 	et_build_groups (e_table);
1550 
1551 	if (e_table->use_click_to_add) {
1552 		e_table->click_to_add = gnome_canvas_item_new (
1553 			GNOME_CANVAS_GROUP (e_table->canvas_vbox),
1554 			e_table_click_to_add_get_type (),
1555 			"header", e_table->header,
1556 			"model", e_table->model,
1557 			"message", e_table->click_to_add_message,
1558 			NULL);
1559 
1560 		if (e_table->use_click_to_add_end)
1561 			e_canvas_vbox_add_item (
1562 				E_CANVAS_VBOX (e_table->canvas_vbox),
1563 				e_table->click_to_add);
1564 		else
1565 			e_canvas_vbox_add_item_start (
1566 				E_CANVAS_VBOX (e_table->canvas_vbox),
1567 				e_table->click_to_add);
1568 
1569 		g_signal_connect (
1570 			e_table->click_to_add, "event",
1571 			G_CALLBACK (click_to_add_event), e_table);
1572 		g_signal_connect (
1573 			e_table->click_to_add, "cursor_change",
1574 			G_CALLBACK (click_to_add_cursor_change), e_table);
1575 		e_signal_connect_notify (
1576 			e_table->click_to_add, "notify::is-editing",
1577 			G_CALLBACK (click_to_add_is_editing_changed_cb), e_table);
1578 	}
1579 }
1580 
1581 static void
e_table_fill_table(ETable * e_table,ETableModel * model)1582 e_table_fill_table (ETable *e_table,
1583                     ETableModel *model)
1584 {
1585 	e_table_group_add_all (e_table->group);
1586 }
1587 
1588 /**
1589  * e_table_set_state_object:
1590  * @e_table: The #ETable object to modify
1591  * @state: The #ETableState to use
1592  *
1593  * This routine sets the state of the #ETable from the given
1594  * #ETableState.
1595  *
1596  **/
1597 void
e_table_set_state_object(ETable * e_table,ETableState * state)1598 e_table_set_state_object (ETable *e_table,
1599                           ETableState *state)
1600 {
1601 	GValue *val;
1602 	GtkWidget *widget;
1603 	GtkAllocation allocation;
1604 
1605 	val = g_new0 (GValue, 1);
1606 	g_value_init (val, G_TYPE_DOUBLE);
1607 
1608 	connect_header (e_table, state);
1609 
1610 	widget = GTK_WIDGET (e_table->table_canvas);
1611 	gtk_widget_get_allocation (widget, &allocation);
1612 
1613 	g_value_set_double (val, (gdouble) allocation.width);
1614 	g_object_set_property (G_OBJECT (e_table->header), "width", val);
1615 	g_free (val);
1616 
1617 	if (e_table->sort_info) {
1618 		if (e_table->group_info_change_id)
1619 			g_signal_handler_disconnect (
1620 				e_table->sort_info,
1621 				e_table->group_info_change_id);
1622 		if (e_table->sort_info_change_id)
1623 			g_signal_handler_disconnect (
1624 				e_table->sort_info,
1625 				e_table->sort_info_change_id);
1626 		g_object_unref (e_table->sort_info);
1627 	}
1628 	if (state->sort_info) {
1629 		e_table->sort_info = e_table_sort_info_duplicate (state->sort_info);
1630 		e_table_sort_info_set_can_group (
1631 			e_table->sort_info, e_table->allow_grouping);
1632 		e_table->group_info_change_id = g_signal_connect (
1633 			e_table->sort_info, "group_info_changed",
1634 			G_CALLBACK (group_info_changed), e_table);
1635 
1636 		e_table->sort_info_change_id = g_signal_connect (
1637 			e_table->sort_info, "sort_info_changed",
1638 			G_CALLBACK (sort_info_changed), e_table);
1639 	}
1640 	else
1641 		e_table->sort_info = NULL;
1642 
1643 	if (e_table->sorter)
1644 		g_object_set (
1645 			e_table->sorter,
1646 			"sort_info", e_table->sort_info,
1647 			NULL);
1648 	if (e_table->header_item)
1649 		g_object_set (
1650 			e_table->header_item,
1651 			"ETableHeader", e_table->header,
1652 			"sort_info", e_table->sort_info,
1653 			NULL);
1654 	if (e_table->click_to_add)
1655 		g_object_set (
1656 			e_table->click_to_add,
1657 			"header", e_table->header,
1658 			NULL);
1659 
1660 	e_table->need_rebuild = TRUE;
1661 	if (!e_table->rebuild_idle_id)
1662 		e_table->rebuild_idle_id = g_idle_add_full (20, changed_idle, e_table, NULL);
1663 
1664 	e_table_state_change (e_table);
1665 }
1666 
1667 /**
1668  * e_table_load_state:
1669  * @e_table: The #ETable object to modify
1670  * @filename: name of the file to use
1671  *
1672  * This routine sets the state of the #ETable from a file.
1673  *
1674  **/
1675 void
e_table_load_state(ETable * e_table,const gchar * filename)1676 e_table_load_state (ETable *e_table,
1677                     const gchar *filename)
1678 {
1679 	ETableState *state;
1680 
1681 	g_return_if_fail (E_IS_TABLE (e_table));
1682 	g_return_if_fail (filename != NULL);
1683 
1684 	state = e_table_state_new (e_table->spec);
1685 	e_table_state_load_from_file (state, filename);
1686 
1687 	if (state->col_count > 0)
1688 		e_table_set_state_object (e_table, state);
1689 
1690 	g_object_unref (state);
1691 }
1692 
1693 /**
1694  * e_table_get_state_object:
1695  * @e_table: #ETable object to act on
1696  *
1697  * Builds an #ETableState corresponding to the current state of the
1698  * #ETable.
1699  *
1700  * Return value:
1701  * The %ETableState object generated.
1702  **/
1703 ETableState *
e_table_get_state_object(ETable * e_table)1704 e_table_get_state_object (ETable *e_table)
1705 {
1706 	ETableState *state;
1707 	GPtrArray *columns;
1708 	gint full_col_count;
1709 	gint i, j;
1710 
1711 	columns = e_table_specification_ref_columns (e_table->spec);
1712 
1713 	state = e_table_state_new (e_table->spec);
1714 
1715 	g_clear_object (&state->sort_info);
1716 	state->sort_info = g_object_ref (e_table->sort_info);
1717 
1718 	state->col_count = e_table_header_count (e_table->header);
1719 	full_col_count = e_table_header_count (e_table->full_header);
1720 
1721 	state->column_specs = g_new (
1722 		ETableColumnSpecification *, state->col_count);
1723 	state->expansions = g_new (gdouble, state->col_count);
1724 
1725 	for (i = 0; i < state->col_count; i++) {
1726 		ETableCol *col = e_table_header_get_column (e_table->header, i);
1727 		state->column_specs[i] = NULL;
1728 		for (j = 0; j < full_col_count; j++) {
1729 			if (col->spec->model_col == e_table_header_index (e_table->full_header, j)) {
1730 				state->column_specs[i] =
1731 					g_object_ref (columns->pdata[j]);
1732 				break;
1733 			}
1734 		}
1735 		state->expansions[i] = col->expansion;
1736 	}
1737 
1738 	g_ptr_array_unref (columns);
1739 
1740 	return state;
1741 }
1742 
1743 /**
1744  * e_table_save_state:
1745  * @e_table: The #ETable to act on
1746  * @filename: name of the file to save to
1747  *
1748  * Saves the state of the @e_table object into the file pointed by
1749  * @filename.
1750  *
1751  **/
1752 void
e_table_save_state(ETable * e_table,const gchar * filename)1753 e_table_save_state (ETable *e_table,
1754                     const gchar *filename)
1755 {
1756 	ETableState *state;
1757 
1758 	state = e_table_get_state_object (e_table);
1759 	e_table_state_save_to_file (state, filename);
1760 	g_object_unref (state);
1761 }
1762 
1763 static void
et_selection_model_selection_changed(ETableGroup * etg,ETable * et)1764 et_selection_model_selection_changed (ETableGroup *etg,
1765                                       ETable *et)
1766 {
1767 	g_signal_emit (et, et_signals[SELECTION_CHANGE], 0);
1768 }
1769 
1770 static void
et_selection_model_selection_row_changed(ETableGroup * etg,gint row,ETable * et)1771 et_selection_model_selection_row_changed (ETableGroup *etg,
1772                                           gint row,
1773                                           ETable *et)
1774 {
1775 	g_signal_emit (et, et_signals[SELECTION_CHANGE], 0);
1776 }
1777 
1778 static ETable *
et_real_construct(ETable * e_table,ETableModel * etm,ETableExtras * ete,ETableSpecification * specification,ETableState * state)1779 et_real_construct (ETable *e_table,
1780                    ETableModel *etm,
1781                    ETableExtras *ete,
1782                    ETableSpecification *specification,
1783                    ETableState *state)
1784 {
1785 	gint row = 0;
1786 	gint col_count, i;
1787 	GValue *val;
1788 	GtkAdjustment *adjustment;
1789 	GtkScrollable *scrollable;
1790 
1791 	val = g_new0 (GValue, 1);
1792 	g_value_init (val, G_TYPE_OBJECT);
1793 
1794 	if (ete)
1795 		g_object_ref (ete);
1796 	else {
1797 		ete = e_table_extras_new ();
1798 	}
1799 
1800 	e_table->domain = g_strdup (specification->domain);
1801 
1802 	e_table->use_click_to_add = specification->click_to_add;
1803 	e_table->use_click_to_add_end = specification->click_to_add_end;
1804 	e_table->click_to_add_message = specification->click_to_add_message ?
1805 		g_strdup (
1806 			dgettext (e_table->domain,
1807 		specification->click_to_add_message)) : NULL;
1808 	e_table->alternating_row_colors = specification->alternating_row_colors;
1809 	e_table->horizontal_draw_grid = specification->horizontal_draw_grid;
1810 	e_table->vertical_draw_grid = specification->vertical_draw_grid;
1811 	e_table->draw_focus = specification->draw_focus;
1812 	e_table->cursor_mode = specification->cursor_mode;
1813 	e_table->full_header = e_table_spec_to_full_header (specification, ete);
1814 
1815 	col_count = e_table_header_count (e_table->full_header);
1816 	for (i = 0; i < col_count; i++) {
1817 		ETableCol *col = e_table_header_get_column (e_table->full_header, i);
1818 		if (col && col->search) {
1819 			e_table->current_search_col = col;
1820 			e_table->search_col_set = TRUE;
1821 			break;
1822 		}
1823 	}
1824 
1825 	e_table->model = etm;
1826 	g_object_ref (etm);
1827 
1828 	connect_header (e_table, state);
1829 	e_table->horizontal_scrolling = specification->horizontal_scrolling;
1830 	e_table->horizontal_resize = specification->horizontal_resize;
1831 	e_table->allow_grouping = specification->allow_grouping;
1832 
1833 	e_table->sort_info = g_object_ref (state->sort_info);
1834 
1835 	e_table_sort_info_set_can_group (
1836 		e_table->sort_info, e_table->allow_grouping);
1837 
1838 	e_table->group_info_change_id = g_signal_connect (
1839 		e_table->sort_info, "group_info_changed",
1840 		G_CALLBACK (group_info_changed), e_table);
1841 
1842 	e_table->sort_info_change_id = g_signal_connect (
1843 		e_table->sort_info, "sort_info_changed",
1844 		G_CALLBACK (sort_info_changed), e_table);
1845 
1846 	g_value_set_object (val, e_table->sort_info);
1847 	g_object_set_property (G_OBJECT (e_table->header), "sort_info", val);
1848 	g_free (val);
1849 
1850 	e_table->sorter = e_table_sorter_new (
1851 		etm, e_table->full_header, e_table->sort_info);
1852 
1853 	g_object_set (
1854 		e_table->selection,
1855 		"model", etm,
1856 		"selection_mode", specification->selection_mode,
1857 		"cursor_mode", specification->cursor_mode,
1858 		"sorter", e_table->sorter,
1859 		"header", e_table->header,
1860 		NULL);
1861 
1862 	g_signal_connect (
1863 		e_table->selection, "selection_changed",
1864 		G_CALLBACK (et_selection_model_selection_changed), e_table);
1865 	g_signal_connect (
1866 		e_table->selection, "selection_row_changed",
1867 		G_CALLBACK (et_selection_model_selection_row_changed), e_table);
1868 
1869 	if (!specification->no_headers)
1870 		e_table_setup_header (e_table);
1871 
1872 	e_table_setup_table (
1873 		e_table, e_table->full_header, e_table->header, etm);
1874 	e_table_fill_table (e_table, etm);
1875 
1876 	scrollable = GTK_SCROLLABLE (e_table->table_canvas);
1877 
1878 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
1879 	gtk_adjustment_set_step_increment (adjustment, 20);
1880 
1881 	adjustment = gtk_scrollable_get_hadjustment (scrollable);
1882 	gtk_adjustment_set_step_increment (adjustment, 20);
1883 
1884 	if (!specification->no_headers) {
1885 		/* The header */
1886 		gtk_table_attach (
1887 			GTK_TABLE (e_table), GTK_WIDGET (e_table->header_canvas),
1888 			0, 1, 0 + row, 1 + row,
1889 			GTK_FILL | GTK_EXPAND,
1890 			GTK_FILL, 0, 0);
1891 		row++;
1892 	}
1893 	gtk_table_attach (
1894 		GTK_TABLE (e_table), GTK_WIDGET (e_table->table_canvas),
1895 		0, 1, 0 + row, 1 + row,
1896 		GTK_FILL | GTK_EXPAND,
1897 		GTK_FILL | GTK_EXPAND,
1898 		0, 0);
1899 
1900 	g_object_unref (ete);
1901 
1902 	return e_table;
1903 }
1904 
1905 /**
1906  * e_table_construct:
1907  * @e_table: The newly created #ETable object.
1908  * @etm: The model for this table.
1909  * @ete: An optional #ETableExtras.  (%NULL is valid.)
1910  * @specification: an #ETableSpecification
1911  *
1912  * This is the internal implementation of e_table_new() for use by
1913  * subclasses or language bindings.  See e_table_new() for details.
1914  *
1915  * Return value:
1916  * The passed in value @e_table or %NULL if there's an error.
1917  **/
1918 ETable *
e_table_construct(ETable * e_table,ETableModel * etm,ETableExtras * ete,ETableSpecification * specification)1919 e_table_construct (ETable *e_table,
1920                    ETableModel *etm,
1921                    ETableExtras *ete,
1922                    ETableSpecification *specification)
1923 {
1924 	ETableState *state;
1925 
1926 	g_return_val_if_fail (E_IS_TABLE (e_table), NULL);
1927 	g_return_val_if_fail (E_IS_TABLE_MODEL (etm), NULL);
1928 	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
1929 	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
1930 
1931 	state = g_object_ref (specification->state);
1932 
1933 	e_table = et_real_construct (e_table, etm, ete, specification, state);
1934 
1935 	e_table->spec = g_object_ref (specification);
1936 	g_object_unref (state);
1937 
1938 	return e_table;
1939 }
1940 
1941 /**
1942  * e_table_new:
1943  * @etm: The model for this table.
1944  * @ete: An optional #ETableExtras.  (%NULL is valid.)
1945  * @specification: an #ETableSpecification
1946  *
1947  * This function creates an #ETable from the given parameters.  The
1948  * #ETableModel is a table model to be represented.  The #ETableExtras
1949  * is an optional set of pixbufs, cells, and sorting functions to be
1950  * used when interpreting the spec.  If you pass in %NULL it uses the
1951  * default #ETableExtras.  (See e_table_extras_new()).
1952  *
1953  * @specification is the specification of the set of viewable columns and the
1954  * default sorting state and such.  @state is an optional string specifying
1955  * the current sorting state and such.
1956  *
1957  * Return value:
1958  * The newly created #ETable or %NULL if there's an error.
1959  **/
1960 GtkWidget *
e_table_new(ETableModel * etm,ETableExtras * ete,ETableSpecification * specification)1961 e_table_new (ETableModel *etm,
1962              ETableExtras *ete,
1963              ETableSpecification *specification)
1964 {
1965 	ETable *e_table;
1966 
1967 	g_return_val_if_fail (E_IS_TABLE_MODEL (etm), NULL);
1968 	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
1969 	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
1970 
1971 	e_table = g_object_new (E_TYPE_TABLE, NULL);
1972 
1973 	e_table = e_table_construct (e_table, etm, ete, specification);
1974 
1975 	return GTK_WIDGET (e_table);
1976 }
1977 
1978 /**
1979  * e_table_set_cursor_row:
1980  * @e_table: The #ETable to set the cursor row of
1981  * @row: The row number
1982  *
1983  * Sets the cursor row and the selection to the given row number.
1984  **/
1985 void
e_table_set_cursor_row(ETable * e_table,gint row)1986 e_table_set_cursor_row (ETable *e_table,
1987                         gint row)
1988 {
1989 	g_return_if_fail (E_IS_TABLE (e_table));
1990 	g_return_if_fail (row >= 0);
1991 
1992 	g_object_set (
1993 		e_table->selection,
1994 		"cursor_row", row,
1995 		NULL);
1996 }
1997 
1998 /**
1999  * e_table_get_cursor_row:
2000  * @e_table: The #ETable to query
2001  *
2002  * Calculates the cursor row.  -1 means that we don't have a cursor.
2003  *
2004  * Return value:
2005  * Cursor row
2006  **/
2007 gint
e_table_get_cursor_row(ETable * e_table)2008 e_table_get_cursor_row (ETable *e_table)
2009 {
2010 	gint row;
2011 	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
2012 
2013 	g_object_get (
2014 		e_table->selection,
2015 		"cursor_row", &row,
2016 		NULL);
2017 	return row;
2018 }
2019 
2020 /**
2021  * e_table_selected_row_foreach:
2022  * @e_table: The #ETable to act on
2023  * @callback: The callback function to call
2024  * @closure: The value passed to the callback's closure argument
2025  *
2026  * Calls the given @callback function once for every selected row.
2027  *
2028  * If you change the selection or delete or add rows to the table
2029  * during these callbacks, problems can occur.  A standard thing to do
2030  * is to create a list of rows or objects the function is called upon
2031  * and then act upon that list. (In inverse order if it's rows.)
2032  **/
2033 void
e_table_selected_row_foreach(ETable * e_table,EForeachFunc callback,gpointer closure)2034 e_table_selected_row_foreach (ETable *e_table,
2035                               EForeachFunc callback,
2036                               gpointer closure)
2037 {
2038 	g_return_if_fail (E_IS_TABLE (e_table));
2039 
2040 	e_selection_model_foreach (E_SELECTION_MODEL (e_table->selection),
2041 						     callback,
2042 						     closure);
2043 }
2044 
2045 /**
2046  * e_table_selected_count:
2047  * @e_table: The #ETable to query
2048  *
2049  * Counts the number of selected rows.
2050  *
2051  * Return value:
2052  * The number of rows selected.
2053  **/
2054 gint
e_table_selected_count(ETable * e_table)2055 e_table_selected_count (ETable *e_table)
2056 {
2057 	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
2058 
2059 	return e_selection_model_selected_count (E_SELECTION_MODEL (e_table->selection));
2060 }
2061 
2062 /**
2063  * e_table_select_all:
2064  * @table: The #ETable to modify
2065  *
2066  * Selects all the rows in @table.
2067  **/
2068 void
e_table_select_all(ETable * table)2069 e_table_select_all (ETable *table)
2070 {
2071 	g_return_if_fail (E_IS_TABLE (table));
2072 
2073 	e_selection_model_select_all (E_SELECTION_MODEL (table->selection));
2074 }
2075 
2076 /**
2077  * e_table_get_printable:
2078  * @e_table: #ETable to query
2079  *
2080  * Used for printing your #ETable.
2081  *
2082  * Return value:
2083  * The #EPrintable to print.
2084  **/
2085 EPrintable *
e_table_get_printable(ETable * e_table)2086 e_table_get_printable (ETable *e_table)
2087 {
2088 	g_return_val_if_fail (E_IS_TABLE (e_table), NULL);
2089 
2090 	return e_table_group_get_printable (e_table->group);
2091 }
2092 
2093 /**
2094  * e_table_right_click_up:
2095  * @table: The #ETable to modify.
2096  *
2097  * Call this function when you're done handling the right click if you
2098  * return TRUE from the "right_click" signal.
2099  **/
2100 void
e_table_right_click_up(ETable * table)2101 e_table_right_click_up (ETable *table)
2102 {
2103 	e_selection_model_right_click_up (E_SELECTION_MODEL (table->selection));
2104 }
2105 
2106 /**
2107  * e_table_commit_click_to_add:
2108  * @table: The #ETable to modify
2109  *
2110  * Commits the current values in the click to add to the table.
2111  **/
2112 void
e_table_commit_click_to_add(ETable * table)2113 e_table_commit_click_to_add (ETable *table)
2114 {
2115 	et_eti_leave_edit (table);
2116 	if (table->click_to_add)
2117 		e_table_click_to_add_commit (
2118 			E_TABLE_CLICK_TO_ADD (table->click_to_add));
2119 }
2120 
2121 static void
et_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)2122 et_get_property (GObject *object,
2123                  guint property_id,
2124                  GValue *value,
2125                  GParamSpec *pspec)
2126 {
2127 	ETable *etable = E_TABLE (object);
2128 
2129 	switch (property_id) {
2130 	case PROP_MODEL:
2131 		g_value_set_object (value, etable->model);
2132 		break;
2133 	case PROP_UNIFORM_ROW_HEIGHT:
2134 		g_value_set_boolean (value, etable->uniform_row_height);
2135 		break;
2136 	case PROP_ALWAYS_SEARCH:
2137 		g_value_set_boolean (value, etable->always_search);
2138 		break;
2139 	case PROP_USE_CLICK_TO_ADD:
2140 		g_value_set_boolean (value, etable->use_click_to_add);
2141 		break;
2142 	case PROP_HADJUSTMENT:
2143 		if (etable->table_canvas)
2144 			g_object_get_property (
2145 				G_OBJECT (etable->table_canvas),
2146 				"hadjustment", value);
2147 		else
2148 			g_value_set_object (value, NULL);
2149 		break;
2150 	case PROP_VADJUSTMENT:
2151 		if (etable->table_canvas)
2152 			g_object_get_property (
2153 				G_OBJECT (etable->table_canvas),
2154 				"vadjustment", value);
2155 		else
2156 			g_value_set_object (value, NULL);
2157 		break;
2158 	case PROP_HSCROLL_POLICY:
2159 		if (etable->table_canvas)
2160 			g_object_get_property (
2161 				G_OBJECT (etable->table_canvas),
2162 				"hscroll-policy", value);
2163 		else
2164 			g_value_set_enum (value, 0);
2165 		break;
2166 	case PROP_VSCROLL_POLICY:
2167 		if (etable->table_canvas)
2168 			g_object_get_property (
2169 				G_OBJECT (etable->table_canvas),
2170 				"vscroll-policy", value);
2171 		else
2172 			g_value_set_enum (value, 0);
2173 		break;
2174 	case PROP_IS_EDITING:
2175 		g_value_set_boolean (value, e_table_is_editing (etable));
2176 		break;
2177 	default:
2178 		break;
2179 	}
2180 }
2181 
2182 typedef struct {
2183 	gchar     *arg;
2184 	gboolean  setting;
2185 } bool_closure;
2186 
2187 static void
et_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)2188 et_set_property (GObject *object,
2189                  guint property_id,
2190                  const GValue *value,
2191                  GParamSpec *pspec)
2192 {
2193 	ETable *etable = E_TABLE (object);
2194 
2195 	switch (property_id) {
2196 	case PROP_LENGTH_THRESHOLD:
2197 		etable->length_threshold = g_value_get_int (value);
2198 		if (etable->group) {
2199 			gnome_canvas_item_set (
2200 				GNOME_CANVAS_ITEM (etable->group),
2201 				"length_threshold",
2202 				etable->length_threshold,
2203 				NULL);
2204 		}
2205 		break;
2206 	case PROP_UNIFORM_ROW_HEIGHT:
2207 		etable->uniform_row_height = g_value_get_boolean (value);
2208 		if (etable->group) {
2209 			gnome_canvas_item_set (
2210 				GNOME_CANVAS_ITEM (etable->group),
2211 				"uniform_row_height", etable->uniform_row_height && !etable->is_grouped,
2212 				NULL);
2213 		}
2214 		break;
2215 	case PROP_ALWAYS_SEARCH:
2216 		if (etable->always_search == g_value_get_boolean (value))
2217 			return;
2218 
2219 		etable->always_search = g_value_get_boolean (value);
2220 		clear_current_search_col (etable);
2221 		break;
2222 	case PROP_USE_CLICK_TO_ADD:
2223 		if (etable->use_click_to_add == g_value_get_boolean (value))
2224 			return;
2225 
2226 		etable->use_click_to_add = g_value_get_boolean (value);
2227 		clear_current_search_col (etable);
2228 
2229 		if (etable->use_click_to_add) {
2230 			etable->click_to_add = gnome_canvas_item_new (
2231 				GNOME_CANVAS_GROUP (etable->canvas_vbox),
2232 				e_table_click_to_add_get_type (),
2233 				"header", etable->header,
2234 				"model", etable->model,
2235 				"message", etable->click_to_add_message,
2236 				NULL);
2237 
2238 			if (etable->use_click_to_add_end)
2239 				e_canvas_vbox_add_item (
2240 					E_CANVAS_VBOX (etable->canvas_vbox),
2241 					etable->click_to_add);
2242 			else
2243 				e_canvas_vbox_add_item_start (
2244 					E_CANVAS_VBOX (etable->canvas_vbox),
2245 					etable->click_to_add);
2246 
2247 			g_signal_connect (
2248 				etable->click_to_add, "event",
2249 				G_CALLBACK (click_to_add_event), etable);
2250 			g_signal_connect (
2251 				etable->click_to_add, "cursor_change",
2252 				G_CALLBACK (click_to_add_cursor_change),
2253 				etable);
2254 			e_signal_connect_notify (
2255 				etable->click_to_add, "notify::is-editing",
2256 				G_CALLBACK (click_to_add_is_editing_changed_cb), etable);
2257 		} else {
2258 			g_object_run_dispose (G_OBJECT (etable->click_to_add));
2259 			etable->click_to_add = NULL;
2260 		}
2261 		break;
2262 	case PROP_HADJUSTMENT:
2263 		if (etable->table_canvas)
2264 			g_object_set_property (
2265 				G_OBJECT (etable->table_canvas),
2266 				"hadjustment", value);
2267 		break;
2268 	case PROP_VADJUSTMENT:
2269 		if (etable->table_canvas)
2270 			g_object_set_property (
2271 				G_OBJECT (etable->table_canvas),
2272 				"vadjustment", value);
2273 		break;
2274 	case PROP_HSCROLL_POLICY:
2275 		if (etable->table_canvas)
2276 			g_object_set_property (
2277 				G_OBJECT (etable->table_canvas),
2278 				"hscroll-policy", value);
2279 		break;
2280 	case PROP_VSCROLL_POLICY:
2281 		if (etable->table_canvas)
2282 			g_object_set_property (
2283 				G_OBJECT (etable->table_canvas),
2284 				"vscroll-policy", value);
2285 		break;
2286 	}
2287 }
2288 
2289 /**
2290  * e_table_get_next_row:
2291  * @e_table: The #ETable to query
2292  * @model_row: The model row to go from
2293  *
2294  * This function is used when your table is sorted, but you're using
2295  * model row numbers.  It returns the next row in sorted order as a model row.
2296  *
2297  * Return value:
2298  * The model row number.
2299  **/
2300 gint
e_table_get_next_row(ETable * e_table,gint model_row)2301 e_table_get_next_row (ETable *e_table,
2302                       gint model_row)
2303 {
2304 	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
2305 
2306 	if (e_table->sorter) {
2307 		gint i;
2308 		i = e_sorter_model_to_sorted (E_SORTER (e_table->sorter), model_row);
2309 		i++;
2310 		if (i < e_table_model_row_count (e_table->model)) {
2311 			return e_sorter_sorted_to_model (E_SORTER (e_table->sorter), i);
2312 		} else
2313 			return -1;
2314 	} else
2315 		if (model_row < e_table_model_row_count (e_table->model) - 1)
2316 			return model_row + 1;
2317 		else
2318 			return -1;
2319 }
2320 
2321 /**
2322  * e_table_get_prev_row:
2323  * @e_table: The #ETable to query
2324  * @model_row: The model row to go from
2325  *
2326  * This function is used when your table is sorted, but you're using
2327  * model row numbers.  It returns the previous row in sorted order as
2328  * a model row.
2329  *
2330  * Return value:
2331  * The model row number.
2332  **/
2333 gint
e_table_get_prev_row(ETable * e_table,gint model_row)2334 e_table_get_prev_row (ETable *e_table,
2335                       gint model_row)
2336 {
2337 	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
2338 
2339 	if (e_table->sorter) {
2340 		gint i;
2341 		i = e_sorter_model_to_sorted (E_SORTER (e_table->sorter), model_row);
2342 		i--;
2343 		if (i >= 0)
2344 			return e_sorter_sorted_to_model (E_SORTER (e_table->sorter), i);
2345 		else
2346 			return -1;
2347 	} else
2348 		return model_row - 1;
2349 }
2350 
2351 /**
2352  * e_table_model_to_view_row:
2353  * @e_table: The #ETable to query
2354  * @model_row: The model row number
2355  *
2356  * Turns a model row into a view row.
2357  *
2358  * Return value:
2359  * The view row number.
2360  **/
2361 gint
e_table_model_to_view_row(ETable * e_table,gint model_row)2362 e_table_model_to_view_row (ETable *e_table,
2363                            gint model_row)
2364 {
2365 	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
2366 
2367 	if (e_table->sorter)
2368 		return e_sorter_model_to_sorted (E_SORTER (e_table->sorter), model_row);
2369 	else
2370 		return model_row;
2371 }
2372 
2373 /**
2374  * e_table_view_to_model_row:
2375  * @e_table: The #ETable to query
2376  * @view_row: The view row number
2377  *
2378  * Turns a view row into a model row.
2379  *
2380  * Return value:
2381  * The model row number.
2382  **/
2383 gint
e_table_view_to_model_row(ETable * e_table,gint view_row)2384 e_table_view_to_model_row (ETable *e_table,
2385                            gint view_row)
2386 {
2387 	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
2388 
2389 	if (e_table->sorter)
2390 		return e_sorter_sorted_to_model (E_SORTER (e_table->sorter), view_row);
2391 	else
2392 		return view_row;
2393 }
2394 
2395 /**
2396  * e_table_get_cell_at:
2397  * @table: An #ETable widget
2398  * @x: X coordinate for the pixel
2399  * @y: Y coordinate for the pixel
2400  * @row_return: Pointer to return the row value
2401  * @col_return: Pointer to return the column value
2402  *
2403  * Return the row and column for the cell in which the pixel at (@x, @y) is
2404  * contained.
2405  **/
2406 void
e_table_get_cell_at(ETable * table,gint x,gint y,gint * row_return,gint * col_return)2407 e_table_get_cell_at (ETable *table,
2408                      gint x,
2409                      gint y,
2410                      gint *row_return,
2411                      gint *col_return)
2412 {
2413 	GtkAdjustment *adjustment;
2414 	GtkScrollable *scrollable;
2415 
2416 	g_return_if_fail (E_IS_TABLE (table));
2417 	g_return_if_fail (row_return != NULL);
2418 	g_return_if_fail (col_return != NULL);
2419 
2420 	/* FIXME it would be nice if it could handle a NULL row_return or
2421 	 * col_return gracefully.  */
2422 
2423 	scrollable = GTK_SCROLLABLE (table->table_canvas);
2424 
2425 	adjustment = gtk_scrollable_get_hadjustment (scrollable);
2426 	x += gtk_adjustment_get_value (adjustment);
2427 
2428 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
2429 	y += gtk_adjustment_get_value (adjustment);
2430 
2431 	e_table_group_compute_location (
2432 		table->group, &x, &y, row_return, col_return);
2433 }
2434 
2435 /**
2436  * e_table_get_cell_geometry:
2437  * @table: The #ETable.
2438  * @row: The row to get the geometry of.
2439  * @col: The col to get the geometry of.
2440  * @x_return: Returns the x coordinate of the upper left hand corner
2441  *            of the cell with respect to the widget.
2442  * @y_return: Returns the y coordinate of the upper left hand corner
2443  *            of the cell with respect to the widget.
2444  * @width_return: Returns the width of the cell.
2445  * @height_return: Returns the height of the cell.
2446  *
2447  * Returns the x, y, width, and height of the given cell.  These can
2448  * all be #NULL and they just won't be set.
2449  **/
2450 void
e_table_get_cell_geometry(ETable * table,gint row,gint col,gint * x_return,gint * y_return,gint * width_return,gint * height_return)2451 e_table_get_cell_geometry (ETable *table,
2452                            gint row,
2453                            gint col,
2454                            gint *x_return,
2455                            gint *y_return,
2456                            gint *width_return,
2457                            gint *height_return)
2458 {
2459 	GtkAllocation allocation;
2460 	GtkAdjustment *adjustment;
2461 	GtkScrollable *scrollable;
2462 
2463 	g_return_if_fail (E_IS_TABLE (table));
2464 
2465 	scrollable = GTK_SCROLLABLE (table->table_canvas);
2466 
2467 	e_table_group_get_cell_geometry (
2468 		table->group, &row, &col, x_return, y_return,
2469 		width_return, height_return);
2470 
2471 	if (x_return && table->table_canvas) {
2472 		adjustment = gtk_scrollable_get_hadjustment (scrollable);
2473 		(*x_return) -= gtk_adjustment_get_value (adjustment);
2474 	}
2475 
2476 	if (y_return) {
2477 		if (table->table_canvas) {
2478 			adjustment = gtk_scrollable_get_vadjustment (scrollable);
2479 			(*y_return) -= gtk_adjustment_get_value (adjustment);
2480 		}
2481 
2482 		if (table->header_canvas) {
2483 			gtk_widget_get_allocation (
2484 				GTK_WIDGET (table->header_canvas),
2485 				&allocation);
2486 			(*y_return) += allocation.height;
2487 		}
2488 	}
2489 }
2490 
2491 /**
2492  * e_table_get_mouse_over_cell:
2493  *
2494  * Similar to e_table_get_cell_at, only here we check
2495  * based on the mouse motion information in the group.
2496  **/
2497 void
e_table_get_mouse_over_cell(ETable * table,gint * row,gint * col)2498 e_table_get_mouse_over_cell (ETable *table,
2499                              gint *row,
2500                              gint *col)
2501 {
2502 	g_return_if_fail (E_IS_TABLE (table));
2503 
2504 	if (!table->group)
2505 		return;
2506 
2507 	e_table_group_get_mouse_over (table->group, row, col);
2508 }
2509 
2510 /**
2511  * e_table_get_selection_model:
2512  * @table: The #ETable to query
2513  *
2514  * Returns the table's #ESelectionModel in case you want to access it
2515  * directly.
2516  *
2517  * Return value:
2518  * The #ESelectionModel.
2519  **/
2520 ESelectionModel *
e_table_get_selection_model(ETable * table)2521 e_table_get_selection_model (ETable *table)
2522 {
2523 	g_return_val_if_fail (E_IS_TABLE (table), NULL);
2524 
2525 	return E_SELECTION_MODEL (table->selection);
2526 }
2527 
2528 struct _ETableDragSourceSite
2529 {
2530 	GdkModifierType    start_button_mask;
2531 	GtkTargetList     *target_list;        /* Targets for drag data */
2532 	GdkDragAction      actions;            /* Possible actions */
2533 	GdkPixbuf         *pixbuf;             /* Icon for drag data */
2534 
2535 	/* Stored button press information to detect drag beginning */
2536 	gint               state;
2537 	gint               x, y;
2538 	gint               row, col;
2539 };
2540 
2541 typedef enum
2542 {
2543   GTK_DRAG_STATUS_DRAG,
2544   GTK_DRAG_STATUS_WAIT,
2545   GTK_DRAG_STATUS_DROP
2546 } GtkDragStatus;
2547 
2548 typedef struct _GtkDragDestInfo GtkDragDestInfo;
2549 typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
2550 
2551 struct _GtkDragDestInfo
2552 {
2553   GtkWidget         *widget;	   /* Widget in which drag is in */
2554   GdkDragContext    *context;	   /* Drag context */
2555   GtkDragSourceInfo *proxy_source; /* Set if this is a proxy drag */
2556   GtkSelectionData  *proxy_data;   /* Set while retrieving proxied data */
2557   guint              dropped : 1;     /* Set after we receive a drop */
2558   guint32            proxy_drop_time; /* Timestamp for proxied drop */
2559   guint              proxy_drop_wait : 1; /* Set if we are waiting for a
2560 					   * status reply before sending
2561 					   * a proxied drop on.
2562 					   */
2563   gint               drop_x, drop_y; /* Position of drop */
2564 };
2565 
2566 struct _GtkDragSourceInfo
2567 {
2568   GtkWidget         *widget;
2569   GtkTargetList     *target_list; /* Targets for drag data */
2570   GdkDragAction      possible_actions; /* Actions allowed by source */
2571   GdkDragContext    *context;	  /* drag context */
2572   GtkWidget         *icon_window; /* Window for drag */
2573   GtkWidget         *ipc_widget;  /* GtkInvisible for grab, message passing */
2574   GdkCursor         *cursor;	  /* Cursor for drag */
2575   gint hot_x, hot_y;		  /* Hot spot for drag */
2576   gint button;			  /* mouse button starting drag */
2577 
2578   GtkDragStatus      status;	  /* drag status */
2579   GdkEvent          *last_event;  /* motion event waiting for response */
2580 
2581   gint               start_x, start_y; /* Initial position */
2582   gint               cur_x, cur_y;     /* Current Position */
2583 
2584   GList             *selections;  /* selections we've claimed */
2585 
2586   GtkDragDestInfo   *proxy_dest;  /* Set if this is a proxy drag */
2587 
2588   guint              drop_timeout;     /* Timeout for aborting drop */
2589   guint              destroy_icon : 1; /* If true, destroy icon_window
2590 					*/
2591 };
2592 
2593 /* Drag & drop stuff. */
2594 /* Target */
2595 
2596 /**
2597  * e_table_drag_get_data:
2598  * @table:
2599  * @row:
2600  * @col:
2601  * @context:
2602  * @target:
2603  * @time:
2604  *
2605  *
2606  **/
2607 void
e_table_drag_get_data(ETable * table,gint row,gint col,GdkDragContext * context,GdkAtom target,guint32 time)2608 e_table_drag_get_data (ETable *table,
2609                        gint row,
2610                        gint col,
2611                        GdkDragContext *context,
2612                        GdkAtom target,
2613                        guint32 time)
2614 {
2615 	g_return_if_fail (E_IS_TABLE (table));
2616 
2617 	gtk_drag_get_data (
2618 		GTK_WIDGET (table),
2619 		context,
2620 		target,
2621 		time);
2622 }
2623 
2624 /**
2625  * e_table_drag_highlight:
2626  * @table: The #ETable to highlight
2627  * @row: The row number of the cell to highlight
2628  * @col: The column number of the cell to highlight
2629  *
2630  * Set col to -1 to highlight the entire row.  If row is -1, this is
2631  * identical to e_table_drag_unhighlight().
2632  **/
2633 void
e_table_drag_highlight(ETable * table,gint row,gint col)2634 e_table_drag_highlight (ETable *table,
2635                         gint row,
2636                         gint col)
2637 {
2638 	GtkAllocation allocation;
2639 	GtkAdjustment *adjustment;
2640 	GtkScrollable *scrollable;
2641 
2642 	g_return_if_fail (E_IS_TABLE (table));
2643 
2644 	scrollable = GTK_SCROLLABLE (table->table_canvas);
2645 	gtk_widget_get_allocation (GTK_WIDGET (scrollable), &allocation);
2646 
2647 	if (row != -1) {
2648 		gint x, y, width, height;
2649 		if (col == -1) {
2650 			e_table_get_cell_geometry (table, row, 0, &x, &y, &width, &height);
2651 			x = 0;
2652 			width = allocation.width;
2653 		} else {
2654 			e_table_get_cell_geometry (table, row, col, &x, &y, &width, &height);
2655 			adjustment = gtk_scrollable_get_hadjustment (scrollable);
2656 			x += gtk_adjustment_get_value (adjustment);
2657 		}
2658 
2659 		adjustment = gtk_scrollable_get_vadjustment (scrollable);
2660 		y += gtk_adjustment_get_value (adjustment);
2661 
2662 		if (table->drop_highlight == NULL) {
2663 			GdkColor fg;
2664 
2665 			e_utils_get_theme_color_color (GTK_WIDGET (table), "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &fg);
2666 
2667 			table->drop_highlight = gnome_canvas_item_new (
2668 				gnome_canvas_root (table->table_canvas),
2669 				gnome_canvas_rect_get_type (),
2670 				"fill_color", NULL,
2671 				"outline_color_gdk", &fg,
2672 				NULL);
2673 		}
2674 		gnome_canvas_item_set (
2675 			table->drop_highlight,
2676 			"x1", (gdouble) x,
2677 			"x2", (gdouble) x + width - 1,
2678 			"y1", (gdouble) y,
2679 			"y2", (gdouble) y + height - 1,
2680 			NULL);
2681 	} else {
2682 		if (table->drop_highlight) {
2683 			g_object_run_dispose (G_OBJECT (table->drop_highlight));
2684 			table->drop_highlight = NULL;
2685 		}
2686 	}
2687 }
2688 
2689 /**
2690  * e_table_drag_unhighlight:
2691  * @table: The #ETable to unhighlight
2692  *
2693  * Removes the highlight from an #ETable.
2694  **/
2695 void
e_table_drag_unhighlight(ETable * table)2696 e_table_drag_unhighlight (ETable *table)
2697 {
2698 	g_return_if_fail (E_IS_TABLE (table));
2699 
2700 	if (table->drop_highlight) {
2701 		g_object_run_dispose (G_OBJECT (table->drop_highlight));
2702 		table->drop_highlight = NULL;
2703 	}
2704 }
2705 
2706 void
e_table_drag_dest_set(ETable * table,GtkDestDefaults flags,const GtkTargetEntry * targets,gint n_targets,GdkDragAction actions)2707 e_table_drag_dest_set (ETable *table,
2708                        GtkDestDefaults flags,
2709                        const GtkTargetEntry *targets,
2710                        gint n_targets,
2711                        GdkDragAction actions)
2712 {
2713 	g_return_if_fail (E_IS_TABLE (table));
2714 
2715 	gtk_drag_dest_set (
2716 		GTK_WIDGET (table), flags, targets, n_targets, actions);
2717 }
2718 
2719 void
e_table_drag_dest_set_proxy(ETable * table,GdkWindow * proxy_window,GdkDragProtocol protocol,gboolean use_coordinates)2720 e_table_drag_dest_set_proxy (ETable *table,
2721                              GdkWindow *proxy_window,
2722                              GdkDragProtocol protocol,
2723                              gboolean use_coordinates)
2724 {
2725 	g_return_if_fail (E_IS_TABLE (table));
2726 
2727 	gtk_drag_dest_set_proxy (
2728 		GTK_WIDGET (table), proxy_window, protocol, use_coordinates);
2729 }
2730 
2731 /*
2732  * There probably should be functions for setting the targets
2733  * as a GtkTargetList
2734  */
2735 
2736 void
e_table_drag_dest_unset(GtkWidget * widget)2737 e_table_drag_dest_unset (GtkWidget *widget)
2738 {
2739 	g_return_if_fail (E_IS_TABLE (widget));
2740 
2741 	gtk_drag_dest_unset (widget);
2742 }
2743 
2744 /* Source side */
2745 
2746 static gint
et_real_start_drag(ETable * table,gint row,gint col,GdkEvent * event)2747 et_real_start_drag (ETable *table,
2748                     gint row,
2749                     gint col,
2750                     GdkEvent *event)
2751 {
2752 	GtkDragSourceInfo *info;
2753 	GdkDragContext *context;
2754 	ETableDragSourceSite *site;
2755 
2756 	if (table->do_drag) {
2757 		site = table->site;
2758 
2759 		site->state = 0;
2760 		context = e_table_drag_begin (
2761 			table, row, col,
2762 			site->target_list,
2763 			site->actions,
2764 			1, event);
2765 
2766 		if (context) {
2767 			info = g_dataset_get_data (context, "gtk-info");
2768 
2769 			if (info && !info->icon_window) {
2770 				if (site->pixbuf)
2771 					gtk_drag_set_icon_pixbuf (
2772 						context,
2773 						site->pixbuf,
2774 						-2, -2);
2775 				else
2776 					gtk_drag_set_icon_default (context);
2777 			}
2778 		}
2779 		return TRUE;
2780 	}
2781 	return FALSE;
2782 }
2783 
2784 /**
2785  * e_table_drag_source_set:
2786  * @table: The #ETable to set up as a drag site
2787  * @start_button_mask: Mask of allowed buttons to start drag
2788  * @targets: Table of targets for this source
2789  * @n_targets: Number of targets in @targets
2790  * @actions: Actions allowed for this source
2791  *
2792  * Registers this table as a drag site, and possibly adds default behaviors.
2793  **/
2794 void
e_table_drag_source_set(ETable * table,GdkModifierType start_button_mask,const GtkTargetEntry * targets,gint n_targets,GdkDragAction actions)2795 e_table_drag_source_set (ETable *table,
2796                          GdkModifierType start_button_mask,
2797                          const GtkTargetEntry *targets,
2798                          gint n_targets,
2799                          GdkDragAction actions)
2800 {
2801 	ETableDragSourceSite *site;
2802 	GtkWidget *canvas;
2803 
2804 	g_return_if_fail (E_IS_TABLE (table));
2805 
2806 	canvas = GTK_WIDGET (table->table_canvas);
2807 	site = table->site;
2808 
2809 	gtk_widget_add_events (
2810 		canvas,
2811 		gtk_widget_get_events (canvas) |
2812 		GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
2813 		GDK_BUTTON_MOTION_MASK | GDK_STRUCTURE_MASK);
2814 
2815 	table->do_drag = TRUE;
2816 
2817 	if (site) {
2818 		if (site->target_list)
2819 			gtk_target_list_unref (site->target_list);
2820 	} else {
2821 		site = g_new0 (ETableDragSourceSite, 1);
2822 		table->site = site;
2823 	}
2824 
2825 	site->start_button_mask = start_button_mask;
2826 
2827 	if (targets)
2828 		site->target_list = gtk_target_list_new (targets, n_targets);
2829 	else
2830 		site->target_list = NULL;
2831 
2832 	site->actions = actions;
2833 }
2834 
2835 /**
2836  * e_table_drag_source_unset:
2837  * @table: The #ETable to un set up as a drag site
2838  *
2839  * Unregisters this #ETable as a drag site.
2840  **/
2841 void
e_table_drag_source_unset(ETable * table)2842 e_table_drag_source_unset (ETable *table)
2843 {
2844 	ETableDragSourceSite *site;
2845 
2846 	g_return_if_fail (E_IS_TABLE (table));
2847 
2848 	site = table->site;
2849 
2850 	if (site) {
2851 		if (site->target_list)
2852 			gtk_target_list_unref (site->target_list);
2853 		g_free (site);
2854 		table->site = NULL;
2855 	}
2856 	table->do_drag = FALSE;
2857 }
2858 
2859 /* There probably should be functions for setting the targets
2860  * as a GtkTargetList
2861  */
2862 
2863 /**
2864  * e_table_drag_begin:
2865  * @table: The #ETable to drag from
2866  * @row: The row number of the cell
2867  * @col: The col number of the cell
2868  * @targets: The list of targets supported by the drag
2869  * @actions: The available actions supported by the drag
2870  * @button: The button held down for the drag
2871  * @event: The event that initiated the drag
2872  *
2873  * Start a drag from this cell.
2874  *
2875  * Return value:
2876  * The drag context.
2877  **/
2878 GdkDragContext *
e_table_drag_begin(ETable * table,gint row,gint col,GtkTargetList * targets,GdkDragAction actions,gint button,GdkEvent * event)2879 e_table_drag_begin (ETable *table,
2880                     gint row,
2881                     gint col,
2882                     GtkTargetList *targets,
2883                     GdkDragAction actions,
2884                     gint button,
2885                     GdkEvent *event)
2886 {
2887 	g_return_val_if_fail (E_IS_TABLE (table), NULL);
2888 
2889 	table->drag_row = row;
2890 	table->drag_col = col;
2891 
2892 	return gtk_drag_begin (
2893 		GTK_WIDGET (table), targets, actions, button, event);
2894 }
2895 
2896 static void
et_drag_begin(GtkWidget * widget,GdkDragContext * context,ETable * et)2897 et_drag_begin (GtkWidget *widget,
2898                GdkDragContext *context,
2899                ETable *et)
2900 {
2901 	g_signal_emit (
2902 		et, et_signals[TABLE_DRAG_BEGIN], 0,
2903 		et->drag_row, et->drag_col, context);
2904 }
2905 
2906 static void
et_drag_end(GtkWidget * widget,GdkDragContext * context,ETable * et)2907 et_drag_end (GtkWidget *widget,
2908              GdkDragContext *context,
2909              ETable *et)
2910 {
2911 	g_signal_emit (
2912 		et, et_signals[TABLE_DRAG_END], 0,
2913 		et->drag_row, et->drag_col, context);
2914 }
2915 
2916 static void
et_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time,ETable * et)2917 et_drag_data_get (GtkWidget *widget,
2918                   GdkDragContext *context,
2919                   GtkSelectionData *selection_data,
2920                   guint info,
2921                   guint time,
2922                   ETable *et)
2923 {
2924 	g_signal_emit (
2925 		et, et_signals[TABLE_DRAG_DATA_GET], 0,
2926 		et->drag_row, et->drag_col, context, selection_data,
2927 		info, time);
2928 }
2929 
2930 static void
et_drag_data_delete(GtkWidget * widget,GdkDragContext * context,ETable * et)2931 et_drag_data_delete (GtkWidget *widget,
2932                      GdkDragContext *context,
2933                      ETable *et)
2934 {
2935 	g_signal_emit (
2936 		et, et_signals[TABLE_DRAG_DATA_DELETE], 0,
2937 		et->drag_row, et->drag_col, context);
2938 }
2939 
2940 static gboolean
do_drag_motion(ETable * et,GdkDragContext * context,gint x,gint y,guint time)2941 do_drag_motion (ETable *et,
2942                 GdkDragContext *context,
2943                 gint x,
2944                 gint y,
2945                 guint time)
2946 {
2947 	gboolean ret_val;
2948 	gint row = -1, col = -1;
2949 
2950 	e_table_get_cell_at (et, x, y, &row, &col);
2951 
2952 	if (row != et->drop_row && col != et->drop_row) {
2953 		g_signal_emit (
2954 			et, et_signals[TABLE_DRAG_LEAVE], 0,
2955 			et->drop_row, et->drop_col, context, time);
2956 	}
2957 
2958 	et->drop_row = row;
2959 	et->drop_col = col;
2960 
2961 	g_signal_emit (
2962 		et, et_signals[TABLE_DRAG_MOTION], 0,
2963 		et->drop_row, et->drop_col, context, x, y, time, &ret_val);
2964 
2965 	return ret_val;
2966 }
2967 
2968 static gboolean
scroll_timeout(gpointer data)2969 scroll_timeout (gpointer data)
2970 {
2971 	ETable *et = data;
2972 	gint dx = 0, dy = 0;
2973 	GtkAdjustment *adjustment;
2974 	GtkScrollable *scrollable;
2975 	gdouble old_h_value;
2976 	gdouble new_h_value;
2977 	gdouble old_v_value;
2978 	gdouble new_v_value;
2979 	gdouble page_size;
2980 	gdouble lower;
2981 	gdouble upper;
2982 
2983 	if (et->scroll_direction & ET_SCROLL_DOWN)
2984 		dy += 20;
2985 	if (et->scroll_direction & ET_SCROLL_UP)
2986 		dy -= 20;
2987 
2988 	if (et->scroll_direction & ET_SCROLL_RIGHT)
2989 		dx += 20;
2990 	if (et->scroll_direction & ET_SCROLL_LEFT)
2991 		dx -= 20;
2992 
2993 	scrollable = GTK_SCROLLABLE (et->table_canvas);
2994 
2995 	adjustment = gtk_scrollable_get_hadjustment (scrollable);
2996 
2997 	lower = gtk_adjustment_get_lower (adjustment);
2998 	upper = gtk_adjustment_get_upper (adjustment);
2999 	page_size = gtk_adjustment_get_page_size (adjustment);
3000 
3001 	old_h_value = gtk_adjustment_get_value (adjustment);
3002 	new_h_value = CLAMP (old_h_value + dx, lower, upper - page_size);
3003 
3004 	gtk_adjustment_set_value (adjustment, new_h_value);
3005 
3006 	adjustment = gtk_scrollable_get_vadjustment (scrollable);
3007 
3008 	lower = gtk_adjustment_get_lower (adjustment);
3009 	upper = gtk_adjustment_get_upper (adjustment);
3010 	page_size = gtk_adjustment_get_page_size (adjustment);
3011 
3012 	old_v_value = gtk_adjustment_get_value (adjustment);
3013 	new_v_value = CLAMP (old_v_value + dy, lower, upper - page_size);
3014 
3015 	gtk_adjustment_set_value (adjustment, new_v_value);
3016 
3017 	if (new_h_value != old_h_value || new_v_value != old_v_value)
3018 		do_drag_motion (
3019 			et,
3020 			et->last_drop_context,
3021 			et->last_drop_x,
3022 			et->last_drop_y,
3023 			et->last_drop_time);
3024 
3025 	return TRUE;
3026 }
3027 
3028 static void
scroll_on(ETable * et,guint scroll_direction)3029 scroll_on (ETable *et,
3030            guint scroll_direction)
3031 {
3032 	if (et->scroll_idle_id == 0 || scroll_direction != et->scroll_direction) {
3033 		if (et->scroll_idle_id != 0)
3034 			g_source_remove (et->scroll_idle_id);
3035 		et->scroll_direction = scroll_direction;
3036 		et->scroll_idle_id = e_named_timeout_add (
3037 			100, scroll_timeout, et);
3038 	}
3039 }
3040 
3041 static void
scroll_off(ETable * et)3042 scroll_off (ETable *et)
3043 {
3044 	if (et->scroll_idle_id) {
3045 		g_source_remove (et->scroll_idle_id);
3046 		et->scroll_idle_id = 0;
3047 	}
3048 }
3049 
3050 static void
context_destroyed(gpointer data)3051 context_destroyed (gpointer data)
3052 {
3053 	ETable *et = data;
3054 	/* if (!G_OBJECT_DESTROYED (et)) */
3055 /* FIXME: */
3056 	{
3057 		et->last_drop_x = 0;
3058 		et->last_drop_y = 0;
3059 		et->last_drop_time = 0;
3060 		et->last_drop_context = NULL;
3061 		scroll_off (et);
3062 	}
3063 	g_object_unref (et);
3064 }
3065 
3066 static void
context_connect(ETable * et,GdkDragContext * context)3067 context_connect (ETable *et,
3068                  GdkDragContext *context)
3069 {
3070 	if (g_dataset_get_data (context, "e-table") == NULL) {
3071 		g_object_ref (et);
3072 		g_dataset_set_data_full (context, "e-table", et, context_destroyed);
3073 	}
3074 }
3075 
3076 static void
et_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time,ETable * et)3077 et_drag_leave (GtkWidget *widget,
3078                GdkDragContext *context,
3079                guint time,
3080                ETable *et)
3081 {
3082 	g_signal_emit (
3083 		et, et_signals[TABLE_DRAG_LEAVE], 0,
3084 		et->drop_row, et->drop_col, context, time);
3085 
3086 	et->drop_row = -1;
3087 	et->drop_col = -1;
3088 
3089 	scroll_off (et);
3090 }
3091 
3092 static gboolean
et_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,ETable * et)3093 et_drag_motion (GtkWidget *widget,
3094                 GdkDragContext *context,
3095                 gint x,
3096                 gint y,
3097                 guint time,
3098                 ETable *et)
3099 {
3100 	GtkAllocation allocation;
3101 	gboolean ret_val;
3102 	guint direction = 0;
3103 
3104 	gtk_widget_get_allocation (widget, &allocation);
3105 
3106 	et->last_drop_x = x;
3107 	et->last_drop_y = y;
3108 	et->last_drop_time = time;
3109 	et->last_drop_context = context;
3110 	context_connect (et, context);
3111 
3112 	ret_val = do_drag_motion (et, context, x, y, time);
3113 
3114 	if (y < 20)
3115 		direction |= ET_SCROLL_UP;
3116 	if (y > allocation.height - 20)
3117 		direction |= ET_SCROLL_DOWN;
3118 	if (x < 20)
3119 		direction |= ET_SCROLL_LEFT;
3120 	if (x > allocation.width - 20)
3121 		direction |= ET_SCROLL_RIGHT;
3122 
3123 	if (direction != 0)
3124 		scroll_on (et, direction);
3125 	else
3126 		scroll_off (et);
3127 
3128 	return ret_val;
3129 }
3130 
3131 static gboolean
et_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,ETable * et)3132 et_drag_drop (GtkWidget *widget,
3133               GdkDragContext *context,
3134               gint x,
3135               gint y,
3136               guint time,
3137               ETable *et)
3138 {
3139 	gboolean ret_val;
3140 	gint row, col;
3141 
3142 	e_table_get_cell_at (et, x, y, &row, &col);
3143 
3144 	if (row != et->drop_row && col != et->drop_row) {
3145 		g_signal_emit (
3146 			et, et_signals[TABLE_DRAG_LEAVE], 0,
3147 			et->drop_row, et->drop_col, context, time);
3148 		g_signal_emit (
3149 			et, et_signals[TABLE_DRAG_MOTION], 0,
3150 			row, col, context, x, y, time, &ret_val);
3151 	}
3152 	et->drop_row = row;
3153 	et->drop_col = col;
3154 	g_signal_emit (
3155 		et, et_signals[TABLE_DRAG_DROP], 0,
3156 		et->drop_row, et->drop_col, context, x, y, time, &ret_val);
3157 	et->drop_row = -1;
3158 	et->drop_col = -1;
3159 
3160 	scroll_off (et);
3161 
3162 	return ret_val;
3163 }
3164 
3165 static void
et_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time,ETable * et)3166 et_drag_data_received (GtkWidget *widget,
3167                        GdkDragContext *context,
3168                        gint x,
3169                        gint y,
3170                        GtkSelectionData *selection_data,
3171                        guint info,
3172                        guint time,
3173                        ETable *et)
3174 {
3175 	gint row, col;
3176 
3177 	e_table_get_cell_at (et, x, y, &row, &col);
3178 
3179 	g_signal_emit (
3180 		et, et_signals[TABLE_DRAG_DATA_RECEIVED], 0,
3181 		row, col, context, x, y, selection_data, info, time);
3182 }
3183 
3184 static void
e_table_class_init(ETableClass * class)3185 e_table_class_init (ETableClass *class)
3186 {
3187 	GObjectClass *object_class;
3188 	GtkWidgetClass *widget_class;
3189 
3190 	object_class = (GObjectClass *) class;
3191 	widget_class = (GtkWidgetClass *) class;
3192 
3193 	object_class->dispose = et_dispose;
3194 	object_class->finalize = et_finalize;
3195 	object_class->set_property = et_set_property;
3196 	object_class->get_property = et_get_property;
3197 
3198 	widget_class->grab_focus = et_grab_focus;
3199 	widget_class->unrealize = et_unrealize;
3200 	widget_class->get_preferred_width = et_get_preferred_width;
3201 	widget_class->get_preferred_height = et_get_preferred_height;
3202 	widget_class->style_updated = et_canvas_style_updated;
3203 
3204 	widget_class->focus = et_focus;
3205 
3206 	class->cursor_change = NULL;
3207 	class->cursor_activated = NULL;
3208 	class->selection_change = NULL;
3209 	class->double_click = NULL;
3210 	class->right_click = NULL;
3211 	class->click = NULL;
3212 	class->key_press = NULL;
3213 	class->start_drag = et_real_start_drag;
3214 	class->state_change = NULL;
3215 	class->white_space_event = NULL;
3216 
3217 	class->table_drag_begin = NULL;
3218 	class->table_drag_end = NULL;
3219 	class->table_drag_data_get = NULL;
3220 	class->table_drag_data_delete = NULL;
3221 
3222 	class->table_drag_leave = NULL;
3223 	class->table_drag_motion = NULL;
3224 	class->table_drag_drop = NULL;
3225 	class->table_drag_data_received = NULL;
3226 
3227 	et_signals[CURSOR_CHANGE] = g_signal_new (
3228 		"cursor_change",
3229 		G_OBJECT_CLASS_TYPE (object_class),
3230 		G_SIGNAL_RUN_LAST,
3231 		G_STRUCT_OFFSET (ETableClass, cursor_change),
3232 		NULL, NULL,
3233 		g_cclosure_marshal_VOID__INT,
3234 		G_TYPE_NONE, 1, G_TYPE_INT);
3235 
3236 	et_signals[CURSOR_ACTIVATED] = g_signal_new (
3237 		"cursor_activated",
3238 		G_OBJECT_CLASS_TYPE (object_class),
3239 		G_SIGNAL_RUN_LAST,
3240 		G_STRUCT_OFFSET (ETableClass, cursor_activated),
3241 		NULL, NULL,
3242 		g_cclosure_marshal_VOID__INT,
3243 		G_TYPE_NONE, 1, G_TYPE_INT);
3244 
3245 	et_signals[SELECTION_CHANGE] = g_signal_new (
3246 		"selection_change",
3247 		G_OBJECT_CLASS_TYPE (object_class),
3248 		G_SIGNAL_RUN_LAST,
3249 		G_STRUCT_OFFSET (ETableClass, selection_change),
3250 		NULL, NULL,
3251 		g_cclosure_marshal_VOID__VOID,
3252 		G_TYPE_NONE, 0);
3253 
3254 	et_signals[DOUBLE_CLICK] = g_signal_new (
3255 		"double_click",
3256 		G_OBJECT_CLASS_TYPE (object_class),
3257 		G_SIGNAL_RUN_LAST,
3258 		G_STRUCT_OFFSET (ETableClass, double_click),
3259 		NULL, NULL,
3260 		e_marshal_VOID__INT_INT_BOXED,
3261 		G_TYPE_NONE, 3,
3262 		G_TYPE_INT,
3263 		G_TYPE_INT,
3264 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3265 
3266 	et_signals[RIGHT_CLICK] = g_signal_new (
3267 		"right_click",
3268 		G_OBJECT_CLASS_TYPE (object_class),
3269 		G_SIGNAL_RUN_LAST,
3270 		G_STRUCT_OFFSET (ETableClass, right_click),
3271 		g_signal_accumulator_true_handled, NULL,
3272 		e_marshal_BOOLEAN__INT_INT_BOXED,
3273 		G_TYPE_BOOLEAN, 3,
3274 		G_TYPE_INT,
3275 		G_TYPE_INT,
3276 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3277 
3278 	et_signals[CLICK] = g_signal_new (
3279 		"click",
3280 		G_OBJECT_CLASS_TYPE (object_class),
3281 		G_SIGNAL_RUN_LAST,
3282 		G_STRUCT_OFFSET (ETableClass, click),
3283 		g_signal_accumulator_true_handled, NULL,
3284 		e_marshal_BOOLEAN__INT_INT_BOXED,
3285 		G_TYPE_BOOLEAN, 3,
3286 		G_TYPE_INT,
3287 		G_TYPE_INT,
3288 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3289 
3290 	et_signals[KEY_PRESS] = g_signal_new (
3291 		"key_press",
3292 		G_OBJECT_CLASS_TYPE (object_class),
3293 		G_SIGNAL_RUN_LAST,
3294 		G_STRUCT_OFFSET (ETableClass, key_press),
3295 		g_signal_accumulator_true_handled, NULL,
3296 		e_marshal_BOOLEAN__INT_INT_BOXED,
3297 		G_TYPE_BOOLEAN, 3,
3298 		G_TYPE_INT,
3299 		G_TYPE_INT,
3300 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3301 
3302 	et_signals[START_DRAG] = g_signal_new (
3303 		"start_drag",
3304 		G_OBJECT_CLASS_TYPE (object_class),
3305 		G_SIGNAL_RUN_LAST,
3306 		G_STRUCT_OFFSET (ETableClass, start_drag),
3307 		g_signal_accumulator_true_handled, NULL,
3308 		e_marshal_BOOLEAN__INT_INT_BOXED,
3309 		G_TYPE_BOOLEAN, 3,
3310 		G_TYPE_INT,
3311 		G_TYPE_INT,
3312 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3313 
3314 	et_signals[STATE_CHANGE] = g_signal_new (
3315 		"state_change",
3316 		G_OBJECT_CLASS_TYPE (object_class),
3317 		G_SIGNAL_RUN_LAST,
3318 		G_STRUCT_OFFSET (ETableClass, state_change),
3319 		NULL, NULL,
3320 		g_cclosure_marshal_VOID__VOID,
3321 		G_TYPE_NONE, 0);
3322 
3323 	et_signals[WHITE_SPACE_EVENT] = g_signal_new (
3324 		"white_space_event",
3325 		G_OBJECT_CLASS_TYPE (object_class),
3326 		G_SIGNAL_RUN_LAST,
3327 		G_STRUCT_OFFSET (ETableClass, white_space_event),
3328 		g_signal_accumulator_true_handled, NULL,
3329 		e_marshal_BOOLEAN__BOXED,
3330 		G_TYPE_BOOLEAN, 1,
3331 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
3332 
3333 	et_signals[TABLE_DRAG_BEGIN] = g_signal_new (
3334 		"table_drag_begin",
3335 		G_OBJECT_CLASS_TYPE (object_class),
3336 		G_SIGNAL_RUN_LAST,
3337 		G_STRUCT_OFFSET (ETableClass, table_drag_begin),
3338 		NULL, NULL,
3339 		e_marshal_VOID__INT_INT_OBJECT,
3340 		G_TYPE_NONE, 3,
3341 		G_TYPE_INT,
3342 		G_TYPE_INT,
3343 		GDK_TYPE_DRAG_CONTEXT);
3344 
3345 	et_signals[TABLE_DRAG_END] = g_signal_new (
3346 		"table_drag_end",
3347 		G_OBJECT_CLASS_TYPE (object_class),
3348 		G_SIGNAL_RUN_LAST,
3349 		G_STRUCT_OFFSET (ETableClass, table_drag_end),
3350 		NULL, NULL,
3351 		e_marshal_VOID__INT_INT_OBJECT,
3352 		G_TYPE_NONE, 3,
3353 		G_TYPE_INT,
3354 		G_TYPE_INT,
3355 		GDK_TYPE_DRAG_CONTEXT);
3356 
3357 	et_signals[TABLE_DRAG_DATA_GET] = g_signal_new (
3358 		"table_drag_data_get",
3359 		G_OBJECT_CLASS_TYPE (object_class),
3360 		G_SIGNAL_RUN_LAST,
3361 		G_STRUCT_OFFSET (ETableClass, table_drag_data_get),
3362 		NULL, NULL,
3363 		e_marshal_VOID__INT_INT_OBJECT_BOXED_UINT_UINT,
3364 		G_TYPE_NONE, 6,
3365 		G_TYPE_INT,
3366 		G_TYPE_INT,
3367 		GDK_TYPE_DRAG_CONTEXT,
3368 		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
3369 		G_TYPE_UINT,
3370 		G_TYPE_UINT);
3371 
3372 	et_signals[TABLE_DRAG_DATA_DELETE] = g_signal_new (
3373 		"table_drag_data_delete",
3374 		G_OBJECT_CLASS_TYPE (object_class),
3375 		G_SIGNAL_RUN_LAST,
3376 		G_STRUCT_OFFSET (ETableClass, table_drag_data_delete),
3377 		NULL, NULL,
3378 		e_marshal_VOID__INT_INT_OBJECT,
3379 		G_TYPE_NONE, 3,
3380 		G_TYPE_INT,
3381 		G_TYPE_INT,
3382 		GDK_TYPE_DRAG_CONTEXT);
3383 
3384 	et_signals[TABLE_DRAG_LEAVE] = g_signal_new (
3385 		"table_drag_leave",
3386 		G_OBJECT_CLASS_TYPE (object_class),
3387 		G_SIGNAL_RUN_LAST,
3388 		G_STRUCT_OFFSET (ETableClass, table_drag_leave),
3389 		NULL, NULL,
3390 		e_marshal_VOID__INT_INT_OBJECT_UINT,
3391 		G_TYPE_NONE, 4,
3392 		G_TYPE_INT,
3393 		G_TYPE_INT,
3394 		GDK_TYPE_DRAG_CONTEXT,
3395 		G_TYPE_UINT);
3396 
3397 	et_signals[TABLE_DRAG_MOTION] = g_signal_new (
3398 		"table_drag_motion",
3399 		G_OBJECT_CLASS_TYPE (object_class),
3400 		G_SIGNAL_RUN_LAST,
3401 		G_STRUCT_OFFSET (ETableClass, table_drag_motion),
3402 		NULL, NULL,
3403 		e_marshal_BOOLEAN__INT_INT_OBJECT_INT_INT_UINT,
3404 		G_TYPE_BOOLEAN, 6,
3405 		G_TYPE_INT,
3406 		G_TYPE_INT,
3407 		GDK_TYPE_DRAG_CONTEXT,
3408 		G_TYPE_INT,
3409 		G_TYPE_INT,
3410 		G_TYPE_UINT);
3411 
3412 	et_signals[TABLE_DRAG_DROP] = g_signal_new (
3413 		"table_drag_drop",
3414 		G_OBJECT_CLASS_TYPE (object_class),
3415 		G_SIGNAL_RUN_LAST,
3416 		G_STRUCT_OFFSET (ETableClass, table_drag_drop),
3417 		NULL, NULL,
3418 		e_marshal_BOOLEAN__INT_INT_OBJECT_INT_INT_UINT,
3419 		G_TYPE_BOOLEAN, 6,
3420 		G_TYPE_INT,
3421 		G_TYPE_INT,
3422 		GDK_TYPE_DRAG_CONTEXT,
3423 		G_TYPE_INT,
3424 		G_TYPE_INT,
3425 		G_TYPE_UINT);
3426 
3427 	et_signals[TABLE_DRAG_DATA_RECEIVED] = g_signal_new (
3428 		"table_drag_data_received",
3429 		G_OBJECT_CLASS_TYPE (object_class),
3430 		G_SIGNAL_RUN_LAST,
3431 		G_STRUCT_OFFSET (ETableClass, table_drag_data_received),
3432 		NULL, NULL,
3433 		e_marshal_VOID__INT_INT_OBJECT_INT_INT_BOXED_UINT_UINT,
3434 		G_TYPE_NONE, 8,
3435 		G_TYPE_INT,
3436 		G_TYPE_INT,
3437 		GDK_TYPE_DRAG_CONTEXT,
3438 		G_TYPE_INT,
3439 		G_TYPE_INT,
3440 		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
3441 		G_TYPE_UINT,
3442 		G_TYPE_UINT);
3443 
3444 	g_object_class_install_property (
3445 		object_class,
3446 		PROP_LENGTH_THRESHOLD,
3447 		g_param_spec_int (
3448 			"length_threshold",
3449 			"Length Threshold",
3450 			NULL,
3451 			0, G_MAXINT, 0,
3452 			G_PARAM_WRITABLE));
3453 
3454 	g_object_class_install_property (
3455 		object_class,
3456 		PROP_UNIFORM_ROW_HEIGHT,
3457 		g_param_spec_boolean (
3458 			"uniform_row_height",
3459 			"Uniform row height",
3460 			NULL,
3461 			FALSE,
3462 			G_PARAM_READWRITE));
3463 
3464 	g_object_class_install_property (
3465 		object_class,
3466 		PROP_ALWAYS_SEARCH,
3467 		g_param_spec_boolean (
3468 			"always_search",
3469 			"Always search",
3470 			NULL,
3471 			FALSE,
3472 			G_PARAM_READWRITE));
3473 
3474 	g_object_class_install_property (
3475 		object_class,
3476 		PROP_USE_CLICK_TO_ADD,
3477 		g_param_spec_boolean (
3478 			"use_click_to_add",
3479 			"Use click to add",
3480 			NULL,
3481 			FALSE,
3482 			G_PARAM_READWRITE));
3483 
3484 	g_object_class_install_property (
3485 		object_class,
3486 		PROP_MODEL,
3487 		g_param_spec_object (
3488 			"model",
3489 			"Model",
3490 			NULL,
3491 			E_TYPE_TABLE_MODEL,
3492 			G_PARAM_READABLE));
3493 
3494 	g_object_class_install_property (
3495 		object_class,
3496 		PROP_IS_EDITING,
3497 		g_param_spec_boolean (
3498 			"is-editing",
3499 			"Whether is in an editing mode",
3500 			"Whether is in an editing mode",
3501 			FALSE,
3502 			G_PARAM_READABLE));
3503 
3504 	gtk_widget_class_install_style_property (
3505 		widget_class,
3506 		g_param_spec_int (
3507 			"vertical-spacing",
3508 			"Vertical Row Spacing",
3509 			"Vertical space between rows. "
3510 			"It is added to top and to bottom of a row",
3511 			0, G_MAXINT, 3,
3512 			G_PARAM_READABLE |
3513 			G_PARAM_STATIC_STRINGS));
3514 
3515 	gtk_widget_class_install_style_property (
3516 		widget_class,
3517 		g_param_spec_boolean (
3518 			"alternating-row-colors",
3519 			"Alternating Row Colors",
3520 			"Whether to use alternating row colors",
3521 			TRUE,
3522 			G_PARAM_READABLE));
3523 
3524 	/* Scrollable interface */
3525 	g_object_class_override_property (
3526 		object_class, PROP_HADJUSTMENT, "hadjustment");
3527 	g_object_class_override_property (
3528 		object_class, PROP_VADJUSTMENT, "vadjustment");
3529 	g_object_class_override_property (
3530 		object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
3531 	g_object_class_override_property (
3532 		object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
3533 
3534 	gtk_widget_class_set_accessible_type (widget_class,
3535 		GAL_A11Y_TYPE_E_TABLE);
3536 }
3537 
3538 static gboolean
e_table_scrollable_get_border(GtkScrollable * scrollable,GtkBorder * border)3539 e_table_scrollable_get_border (GtkScrollable *scrollable,
3540 			       GtkBorder *border)
3541 {
3542 	ETable *table;
3543 	ETableHeaderItem *header_item;
3544 
3545 	g_return_val_if_fail (E_IS_TABLE (scrollable), FALSE);
3546 	g_return_val_if_fail (border != NULL, FALSE);
3547 
3548 	table = E_TABLE (scrollable);
3549 	if (!table->header_item)
3550 		return FALSE;
3551 
3552 	g_return_val_if_fail (E_IS_TABLE_HEADER_ITEM (table->header_item), FALSE);
3553 
3554 	header_item = E_TABLE_HEADER_ITEM (table->header_item);
3555 
3556 	border->top = header_item->height;
3557 
3558 	return TRUE;
3559 }
3560 
3561 static void
e_table_scrollable_init(GtkScrollableInterface * iface)3562 e_table_scrollable_init (GtkScrollableInterface *iface)
3563 {
3564 	iface->get_border = e_table_scrollable_get_border;
3565 }
3566 
3567 void
e_table_freeze_state_change(ETable * table)3568 e_table_freeze_state_change (ETable *table)
3569 {
3570 	g_return_if_fail (table != NULL);
3571 
3572 	table->state_change_freeze++;
3573 	if (table->state_change_freeze == 1)
3574 		table->state_changed = FALSE;
3575 
3576 	g_return_if_fail (table->state_change_freeze != 0);
3577 }
3578 
3579 void
e_table_thaw_state_change(ETable * table)3580 e_table_thaw_state_change (ETable *table)
3581 {
3582 	g_return_if_fail (table != NULL);
3583 	g_return_if_fail (table->state_change_freeze != 0);
3584 
3585 	table->state_change_freeze--;
3586 	if (table->state_change_freeze == 0 && table->state_changed) {
3587 		table->state_changed = FALSE;
3588 		e_table_state_change (table);
3589 	}
3590 }
3591 
3592 gboolean
e_table_is_editing(ETable * table)3593 e_table_is_editing (ETable *table)
3594 {
3595 	g_return_val_if_fail (E_IS_TABLE (table), FALSE);
3596 
3597 	return (table->click_to_add && e_table_click_to_add_is_editing (E_TABLE_CLICK_TO_ADD (table->click_to_add))) ||
3598 	       (table->group && e_table_group_is_editing (table->group));
3599 }
3600 
3601 void
e_table_customize_view(ETable * table)3602 e_table_customize_view (ETable *table)
3603 {
3604 	g_return_if_fail (E_IS_TABLE (table));
3605 
3606 	if (table->header_item)
3607 		e_table_header_item_customize_view (E_TABLE_HEADER_ITEM (table->header_item));
3608 }
3609 
3610 static void
table_size_allocate(GtkWidget * widget,GtkAllocation * alloc,ETable * table)3611 table_size_allocate (GtkWidget *widget,
3612 		     GtkAllocation *alloc,
3613 		     ETable *table)
3614 {
3615 	gdouble width;
3616 
3617 	g_return_if_fail (E_IS_TABLE (table));
3618 	g_return_if_fail (table->priv->info_text != NULL);
3619 
3620 	gnome_canvas_get_scroll_region (
3621 		GNOME_CANVAS (table->table_canvas),
3622 		NULL, NULL, &width, NULL);
3623 
3624 	width -= 60.0;
3625 
3626 	g_object_set (table->priv->info_text,
3627 		"width", width,
3628 		"clip_width", width,
3629 		NULL);
3630 }
3631 
3632 /**
3633  * e_table_set_info_message:
3634  * @table: #ETable instance
3635  * @info_message: Message to set. Can be NULL.
3636  *
3637  * Creates an info message in table area, or removes old.
3638  **/
3639 void
e_table_set_info_message(ETable * table,const gchar * info_message)3640 e_table_set_info_message (ETable *table,
3641 			  const gchar *info_message)
3642 {
3643 	GtkAllocation allocation;
3644 	GtkWidget *widget;
3645 
3646 	g_return_if_fail (E_IS_TABLE (table));
3647 
3648 	if (!table->priv->info_text && (!info_message || !*info_message))
3649 		return;
3650 
3651 	if (!info_message || !*info_message) {
3652 		g_signal_handler_disconnect (table, table->priv->info_text_resize_id);
3653 		g_object_run_dispose (G_OBJECT (table->priv->info_text));
3654 		table->priv->info_text = NULL;
3655 		return;
3656 	}
3657 
3658 	widget = GTK_WIDGET (table->table_canvas);
3659 	gtk_widget_get_allocation (widget, &allocation);
3660 
3661 	if (!table->priv->info_text) {
3662 		if (allocation.width > 60) {
3663 			table->priv->info_text = gnome_canvas_item_new (
3664 				GNOME_CANVAS_GROUP (gnome_canvas_root (table->table_canvas)),
3665 				e_text_get_type (),
3666 				"line_wrap", TRUE,
3667 				"clip", TRUE,
3668 				"justification", GTK_JUSTIFY_LEFT,
3669 				"text", info_message,
3670 				"width", (gdouble) allocation.width - 60.0,
3671 				"clip_width", (gdouble) allocation.width - 60.0,
3672 				NULL);
3673 
3674 			e_canvas_item_move_absolute (table->priv->info_text, 30, 30);
3675 
3676 			table->priv->info_text_resize_id = g_signal_connect_object (
3677 				table, "size_allocate",
3678 				G_CALLBACK (table_size_allocate), table, 0);
3679 		}
3680 	} else
3681 		gnome_canvas_item_set (table->priv->info_text, "text", info_message, NULL);
3682 }
3683