1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Chris Lahey <clahey@ximian.com>
17  *		Miguel de Icaza <miguel@ximian.com>
18  *
19  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20  *
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <string.h>
26 
27 #include <gtk/gtk.h>
28 
29 #include "e-marshal.h"
30 #include "e-table-defines.h"
31 #include "e-table-header.h"
32 
33 enum {
34 	PROP_0,
35 	PROP_SORT_INFO,
36 	PROP_WIDTH,
37 	PROP_WIDTH_EXTRAS
38 };
39 
40 enum {
41 	STRUCTURE_CHANGE,
42 	DIMENSION_CHANGE,
43 	EXPANSION_CHANGE,
44 	REQUEST_WIDTH,
45 	LAST_SIGNAL
46 };
47 
48 static void eth_set_size (ETableHeader *eth, gint idx, gint size);
49 static void eth_calc_widths (ETableHeader *eth);
50 
51 static guint eth_signals[LAST_SIGNAL] = { 0, };
52 
53 G_DEFINE_TYPE (ETableHeader, e_table_header, G_TYPE_OBJECT)
54 
55 struct two_ints {
56 	gint column;
57 	gint width;
58 };
59 
60 static void
eth_set_width(ETableHeader * eth,gint width)61 eth_set_width (ETableHeader *eth,
62                gint width)
63 {
64 	eth->width = width;
65 }
66 
67 static void
dequeue(ETableHeader * eth,gint * column,gint * width)68 dequeue (ETableHeader *eth,
69          gint *column,
70          gint *width)
71 {
72 	GSList *head;
73 	struct two_ints *store;
74 	head = eth->change_queue;
75 	eth->change_queue = eth->change_queue->next;
76 	if (!eth->change_queue)
77 		eth->change_tail = NULL;
78 	store = head->data;
79 	g_slist_free_1 (head);
80 	if (column)
81 		*column = store->column;
82 	if (width)
83 		*width = store->width;
84 	g_free (store);
85 }
86 
87 static gboolean
dequeue_idle(ETableHeader * eth)88 dequeue_idle (ETableHeader *eth)
89 {
90 	gint column, width;
91 
92 	dequeue (eth, &column, &width);
93 	while (eth->change_queue && ((struct two_ints *)
94 		eth->change_queue->data)->column == column)
95 		dequeue (eth, &column, &width);
96 
97 	if (column == -1)
98 		eth_set_width (eth, width);
99 	else if (column < eth->col_count)
100 		eth_set_size (eth, column, width);
101 	if (eth->change_queue)
102 		return TRUE;
103 	else {
104 		eth_calc_widths (eth);
105 		eth->idle = 0;
106 		return FALSE;
107 	}
108 }
109 
110 static void
enqueue(ETableHeader * eth,gint column,gint width)111 enqueue (ETableHeader *eth,
112          gint column,
113          gint width)
114 {
115 	struct two_ints *store;
116 	store = g_new (struct two_ints, 1);
117 	store->column = column;
118 	store->width = width;
119 
120 	eth->change_tail = g_slist_last (g_slist_append (eth->change_tail, store));
121 	if (!eth->change_queue)
122 		eth->change_queue = eth->change_tail;
123 
124 	if (!eth->idle) {
125 		eth->idle = g_idle_add_full (
126 			G_PRIORITY_LOW, (GSourceFunc)
127 			dequeue_idle, eth, NULL);
128 	}
129 }
130 
131 void
e_table_header_set_size(ETableHeader * eth,gint idx,gint size)132 e_table_header_set_size (ETableHeader *eth,
133                          gint idx,
134                          gint size)
135 {
136 	g_return_if_fail (eth != NULL);
137 	g_return_if_fail (E_IS_TABLE_HEADER (eth));
138 
139 	enqueue (eth, idx, size);
140 }
141 
142 static void
eth_do_remove(ETableHeader * eth,gint idx,gboolean do_unref)143 eth_do_remove (ETableHeader *eth,
144                gint idx,
145                gboolean do_unref)
146 {
147 	if (do_unref)
148 		g_object_unref (eth->columns[idx]);
149 
150 	memmove (
151 		&eth->columns[idx], &eth->columns[idx + 1],
152 		sizeof (ETableCol *) * (eth->col_count - idx - 1));
153 	eth->col_count--;
154 }
155 
156 static void
eth_finalize(GObject * object)157 eth_finalize (GObject *object)
158 {
159 	ETableHeader *eth = E_TABLE_HEADER (object);
160 	const gint cols = eth->col_count;
161 	gint i;
162 
163 	if (eth->sort_info) {
164 		if (eth->sort_info_group_change_id)
165 			g_signal_handler_disconnect (
166 				eth->sort_info,
167 				eth->sort_info_group_change_id);
168 		g_object_unref (eth->sort_info);
169 		eth->sort_info = NULL;
170 	}
171 
172 	if (eth->idle)
173 		g_source_remove (eth->idle);
174 	eth->idle = 0;
175 
176 	if (eth->change_queue) {
177 		g_slist_foreach (eth->change_queue, (GFunc) g_free, NULL);
178 		g_slist_free (eth->change_queue);
179 		eth->change_queue = NULL;
180 	}
181 
182 	/*
183 	 * Destroy columns
184 	 */
185 	for (i = cols - 1; i >= 0; i--) {
186 		eth_do_remove (eth, i, TRUE);
187 	}
188 	g_free (eth->columns);
189 
190 	eth->col_count = 0;
191 	eth->columns = NULL;
192 
193 	/* Chain up to parent's finalize() method. */
194 	G_OBJECT_CLASS (e_table_header_parent_class)->finalize (object);
195 }
196 
197 static void
eth_group_info_changed(ETableSortInfo * info,ETableHeader * eth)198 eth_group_info_changed (ETableSortInfo *info,
199                         ETableHeader *eth)
200 {
201 	enqueue (eth, -1, eth->nominal_width);
202 }
203 
204 static void
eth_set_property(GObject * object,guint property_id,const GValue * val,GParamSpec * pspec)205 eth_set_property (GObject *object,
206                   guint property_id,
207                   const GValue *val,
208                   GParamSpec *pspec)
209 {
210 	ETableHeader *eth = E_TABLE_HEADER (object);
211 
212 	switch (property_id) {
213 	case PROP_WIDTH:
214 		eth->nominal_width = g_value_get_double (val);
215 		enqueue (eth, -1, eth->nominal_width);
216 		break;
217 	case PROP_WIDTH_EXTRAS:
218 		eth->width_extras = g_value_get_double (val);
219 		enqueue (eth, -1, eth->nominal_width);
220 		break;
221 	case PROP_SORT_INFO:
222 		if (eth->sort_info) {
223 			if (eth->sort_info_group_change_id)
224 				g_signal_handler_disconnect (
225 					eth->sort_info,
226 					eth->sort_info_group_change_id);
227 			g_object_unref (eth->sort_info);
228 		}
229 		eth->sort_info = E_TABLE_SORT_INFO (g_value_get_object (val));
230 		if (eth->sort_info) {
231 			g_object_ref (eth->sort_info);
232 			eth->sort_info_group_change_id = g_signal_connect (
233 				eth->sort_info, "group_info_changed",
234 				G_CALLBACK (eth_group_info_changed), eth);
235 		}
236 		enqueue (eth, -1, eth->nominal_width);
237 		break;
238 	default:
239 		break;
240 	}
241 }
242 
243 static void
eth_get_property(GObject * object,guint property_id,GValue * val,GParamSpec * pspec)244 eth_get_property (GObject *object,
245                   guint property_id,
246                   GValue *val,
247                   GParamSpec *pspec)
248 {
249 	ETableHeader *eth = E_TABLE_HEADER (object);
250 
251 	switch (property_id) {
252 	case PROP_SORT_INFO:
253 		g_value_set_object (val, eth->sort_info);
254 		break;
255 	case PROP_WIDTH:
256 		g_value_set_double (val, eth->nominal_width);
257 		break;
258 	case PROP_WIDTH_EXTRAS:
259 		g_value_set_double (val, eth->width_extras);
260 		break;
261 	default:
262 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
263 		break;
264 	}
265 }
266 
267 static void
e_table_header_class_init(ETableHeaderClass * class)268 e_table_header_class_init (ETableHeaderClass *class)
269 {
270 	GObjectClass *object_class = G_OBJECT_CLASS (class);
271 
272 	object_class->finalize = eth_finalize;
273 	object_class->set_property = eth_set_property;
274 	object_class->get_property = eth_get_property;
275 
276 	g_object_class_install_property (
277 		object_class,
278 		PROP_WIDTH,
279 		g_param_spec_double (
280 			"width", "Width", "Width",
281 			0.0, G_MAXDOUBLE, 0.0,
282 			G_PARAM_READWRITE));
283 
284 	g_object_class_install_property (
285 		object_class,
286 		PROP_WIDTH_EXTRAS,
287 		g_param_spec_double (
288 			"width_extras",
289 			"Width of Extras",
290 			"Width of Extras",
291 			0.0, G_MAXDOUBLE, 0.0,
292 			G_PARAM_READWRITE));
293 
294 	g_object_class_install_property (
295 		object_class,
296 		PROP_SORT_INFO,
297 		g_param_spec_object (
298 			"sort_info",
299 			"Sort Info",
300 			"Sort Info",
301 			E_TYPE_TABLE_SORT_INFO,
302 			G_PARAM_READWRITE));
303 
304 	eth_signals[STRUCTURE_CHANGE] = g_signal_new (
305 		"structure_change",
306 		G_TYPE_FROM_CLASS (object_class),
307 		G_SIGNAL_RUN_LAST,
308 		G_STRUCT_OFFSET (ETableHeaderClass, structure_change),
309 		(GSignalAccumulator) NULL, NULL,
310 		g_cclosure_marshal_VOID__VOID,
311 		G_TYPE_NONE, 0);
312 
313 	eth_signals[DIMENSION_CHANGE] = g_signal_new (
314 		"dimension_change",
315 		G_TYPE_FROM_CLASS (object_class),
316 		G_SIGNAL_RUN_LAST,
317 		G_STRUCT_OFFSET (ETableHeaderClass, dimension_change),
318 		(GSignalAccumulator) NULL, NULL,
319 		g_cclosure_marshal_VOID__INT,
320 		G_TYPE_NONE, 1,
321 		G_TYPE_INT);
322 
323 	eth_signals[EXPANSION_CHANGE] = g_signal_new (
324 		"expansion_change",
325 		G_TYPE_FROM_CLASS (object_class),
326 		G_SIGNAL_RUN_LAST,
327 		G_STRUCT_OFFSET (ETableHeaderClass, expansion_change),
328 		(GSignalAccumulator) NULL, NULL,
329 		g_cclosure_marshal_VOID__VOID,
330 		G_TYPE_NONE, 0);
331 
332 	eth_signals[REQUEST_WIDTH] = g_signal_new (
333 		"request_width",
334 		G_TYPE_FROM_CLASS (object_class),
335 		G_SIGNAL_RUN_LAST,
336 		G_STRUCT_OFFSET (ETableHeaderClass, request_width),
337 		(GSignalAccumulator) NULL, NULL,
338 		e_marshal_INT__INT,
339 		G_TYPE_INT, 1,
340 		G_TYPE_INT);
341 
342 	class->structure_change = NULL;
343 	class->dimension_change = NULL;
344 	class->expansion_change = NULL;
345 	class->request_width = NULL;
346 }
347 
348 static void
e_table_header_init(ETableHeader * eth)349 e_table_header_init (ETableHeader *eth)
350 {
351 	eth->col_count = 0;
352 	eth->width = 0;
353 
354 	eth->sort_info = NULL;
355 	eth->sort_info_group_change_id = 0;
356 
357 	eth->columns = NULL;
358 
359 	eth->change_queue = NULL;
360 	eth->change_tail = NULL;
361 
362 	eth->width_extras = 0;
363 }
364 
365 /**
366  * e_table_header_new:
367  *
368  * Returns: A new @ETableHeader object.
369  */
370 ETableHeader *
e_table_header_new(void)371 e_table_header_new (void)
372 {
373 
374 	return g_object_new (E_TYPE_TABLE_HEADER, NULL);
375 }
376 
377 static void
eth_update_offsets(ETableHeader * eth)378 eth_update_offsets (ETableHeader *eth)
379 {
380 	gint i;
381 	gint x = 0;
382 
383 	for (i = 0; i < eth->col_count; i++) {
384 		ETableCol *etc = eth->columns[i];
385 
386 		etc->x = x;
387 		x += etc->width;
388 	}
389 }
390 
391 static void
eth_do_insert(ETableHeader * eth,gint pos,ETableCol * val)392 eth_do_insert (ETableHeader *eth,
393                gint pos,
394                ETableCol *val)
395 {
396 	memmove (
397 		&eth->columns[pos + 1], &eth->columns[pos],
398 		sizeof (ETableCol *) * (eth->col_count - pos));
399 	eth->columns[pos] = val;
400 	eth->col_count++;
401 }
402 
403 /**
404  * e_table_header_add_column:
405  * @eth: the table header to add the column to.
406  * @tc: the ETableCol definition
407  * @pos: position where the ETableCol will go.
408  *
409  * This function adds the @tc ETableCol definition into the @eth ETableHeader
410  * at position @pos.  This is the way you add new ETableCols to the
411  * ETableHeader.  The header will assume ownership of the @tc; you should not
412  * unref it after you add it.
413  *
414  * This function will emit the "structure_change" signal on the @eth object.
415  * The ETableCol is assumed
416  */
417 void
e_table_header_add_column(ETableHeader * eth,ETableCol * tc,gint pos)418 e_table_header_add_column (ETableHeader *eth,
419                            ETableCol *tc,
420                            gint pos)
421 {
422 	g_return_if_fail (eth != NULL);
423 	g_return_if_fail (E_IS_TABLE_HEADER (eth));
424 	g_return_if_fail (tc != NULL);
425 	g_return_if_fail (E_IS_TABLE_COL (tc));
426 	g_return_if_fail (pos >= -1 && pos <= eth->col_count);
427 
428 	if (pos == -1)
429 		pos = eth->col_count;
430 	eth->columns = g_realloc (
431 		eth->columns, sizeof (ETableCol *) * (eth->col_count + 1));
432 
433 	/*
434 	 * We are the primary owners of the column
435 	 */
436 	g_object_ref (tc);
437 
438 	eth_do_insert (eth, pos, tc);
439 
440 	enqueue (eth, -1, eth->nominal_width);
441 	g_signal_emit (eth, eth_signals[STRUCTURE_CHANGE], 0);
442 }
443 
444 /**
445  * e_table_header_get_column:
446  * @eth: the ETableHeader to query
447  * @column: the column inside the @eth.
448  *
449  * Returns: The ETableCol at @column in the @eth object
450  */
451 ETableCol *
e_table_header_get_column(ETableHeader * eth,gint column)452 e_table_header_get_column (ETableHeader *eth,
453                            gint column)
454 {
455 	g_return_val_if_fail (eth != NULL, NULL);
456 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
457 
458 	if (column < 0)
459 		return NULL;
460 
461 	if (column >= eth->col_count)
462 		return NULL;
463 
464 	return eth->columns[column];
465 }
466 
467 /**
468  * e_table_header_get_column_by_spec:
469  * @eth: the ETableHeader to query
470  * @spec: an #ETableColumnSpecification
471  *
472  * Returns the #ETableCol having @spec as the column specification, or
473  * %NULL if there is no matching #ETableCol.
474  *
475  * Returns: an #ETableCol, or %NULL
476  **/
477 ETableCol *
e_table_header_get_column_by_spec(ETableHeader * eth,ETableColumnSpecification * spec)478 e_table_header_get_column_by_spec (ETableHeader *eth,
479                                    ETableColumnSpecification *spec)
480 {
481 	gint ii;
482 
483 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
484 	g_return_val_if_fail (E_IS_TABLE_COLUMN_SPECIFICATION (spec), NULL);
485 
486 	for (ii = 0; ii < eth->col_count; ii++) {
487 		gboolean column_specs_equal;
488 
489 		column_specs_equal = e_table_column_specification_equal (
490 			spec, eth->columns[ii]->spec);
491 
492 		if (column_specs_equal)
493 			return eth->columns[ii];
494 	}
495 
496 	return NULL;
497 }
498 
499 /**
500  * e_table_header_get_column_by_col_id:
501  * @eth: the ETableHeader to query
502  * @col_id: the col_id to search for.
503  *
504  * Returns: The ETableCol with col_idx = @col_idx in the @eth object
505  */
506 ETableCol *
e_table_header_get_column_by_col_idx(ETableHeader * eth,gint col_idx)507 e_table_header_get_column_by_col_idx (ETableHeader *eth,
508                                       gint col_idx)
509 {
510 	gint i;
511 	g_return_val_if_fail (eth != NULL, NULL);
512 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
513 
514 	for (i = 0; i < eth->col_count; i++) {
515 		if (eth->columns[i]->spec->model_col == col_idx) {
516 			return eth->columns[i];
517 		}
518 	}
519 
520 	return NULL;
521 }
522 
523 /**
524  * e_table_header_count:
525  * @eth: the ETableHeader to query
526  *
527  * Returns: the number of columns in this ETableHeader.
528  */
529 gint
e_table_header_count(ETableHeader * eth)530 e_table_header_count (ETableHeader *eth)
531 {
532 	g_return_val_if_fail (eth != NULL, 0);
533 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
534 
535 	return eth->col_count;
536 }
537 
538 /**
539  * e_table_header_index:
540  * @eth: the ETableHeader to query
541  * @col: the column to fetch.
542  *
543  * ETableHeaders contain the visual list of columns that the user will
544  * view.  The visible columns will typically map to different columns
545  * in the ETableModel (because the user reordered the data for
546  * example).
547  *
548  * Returns: the column in the model that the @col column
549  * in the ETableHeader points to.  */
550 gint
e_table_header_index(ETableHeader * eth,gint col)551 e_table_header_index (ETableHeader *eth,
552                       gint col)
553 {
554 	g_return_val_if_fail (eth != NULL, -1);
555 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), -1);
556 	g_return_val_if_fail (col >= 0 && col < eth->col_count, -1);
557 
558 	return eth->columns[col]->spec->model_col;
559 }
560 
561 /**
562  * e_table_header_get_index_at:
563  * @eth: the ETableHeader to query
564  * @x_offset: a pixel count from the beginning of the ETableHeader
565  *
566  * This will return the ETableHeader column that would contain
567  * the @x_offset pixel.
568  *
569  * Returns: the column that contains pixel @x_offset, or -1
570  * if no column inside this ETableHeader contains that pixel.
571  */
572 gint
e_table_header_get_index_at(ETableHeader * eth,gint x_offset)573 e_table_header_get_index_at (ETableHeader *eth,
574                              gint x_offset)
575 {
576 	gint i, total;
577 
578 	g_return_val_if_fail (eth != NULL, 0);
579 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
580 
581 	total = 0;
582 	for (i = 0; i < eth->col_count; i++) {
583 		total += eth->columns[i]->width;
584 
585 		if (x_offset < total)
586 			return i;
587 	}
588 
589 	return -1;
590 }
591 
592 /**
593  * e_table_header_get_columns:
594  * @eth: The ETableHeader to query
595  *
596  * Returns: A NULL terminated array of the ETableCols
597  * contained in the ETableHeader @eth.  Note that every
598  * returned ETableCol in the array has been referenced, to release
599  * this information you need to g_free the buffer returned
600  * and you need to g_object_unref every element returned
601  */
602 ETableCol **
e_table_header_get_columns(ETableHeader * eth)603 e_table_header_get_columns (ETableHeader *eth)
604 {
605 	ETableCol **ret;
606 	gint i;
607 
608 	g_return_val_if_fail (eth != NULL, NULL);
609 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
610 
611 	ret = g_new (ETableCol *, eth->col_count + 1);
612 	memcpy (ret, eth->columns, sizeof (ETableCol *) * eth->col_count);
613 	ret[eth->col_count] = NULL;
614 
615 	for (i = 0; i < eth->col_count; i++) {
616 		g_object_ref (ret[i]);
617 	}
618 
619 	return ret;
620 }
621 
622 /**
623  * e_table_header_get_selected:
624  * @eth: The ETableHeader to query
625  *
626  * Returns: The number of selected columns in the @eth object.
627  */
628 gint
e_table_header_get_selected(ETableHeader * eth)629 e_table_header_get_selected (ETableHeader *eth)
630 {
631 	gint i;
632 	gint selected = 0;
633 
634 	g_return_val_if_fail (eth != NULL, 0);
635 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
636 
637 	for (i = 0; i < eth->col_count; i++) {
638 		if (eth->columns[i]->selected)
639 			selected++;
640 	}
641 
642 	return selected;
643 }
644 
645 /**
646  * e_table_header_total_width:
647  * @eth: The ETableHeader to query
648  *
649  * Returns: the number of pixels used by the @eth object
650  * when rendered on screen
651  */
652 gint
e_table_header_total_width(ETableHeader * eth)653 e_table_header_total_width (ETableHeader *eth)
654 {
655 	gint total, i;
656 
657 	g_return_val_if_fail (eth != NULL, 0);
658 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
659 
660 	total = 0;
661 	for (i = 0; i < eth->col_count; i++)
662 		total += eth->columns[i]->width;
663 
664 	return total;
665 }
666 
667 /**
668  * e_table_header_min_width:
669  * @eth: The ETableHeader to query
670  *
671  * Returns: the minimum number of pixels required by the @eth object.
672  **/
673 gint
e_table_header_min_width(ETableHeader * eth)674 e_table_header_min_width (ETableHeader *eth)
675 {
676 	gint total, i;
677 
678 	g_return_val_if_fail (eth != NULL, 0);
679 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
680 
681 	total = 0;
682 	for (i = 0; i < eth->col_count; i++)
683 		total += eth->columns[i]->min_width;
684 
685 	return total;
686 }
687 
688 /**
689  * e_table_header_move:
690  * @eth: The ETableHeader to operate on.
691  * @source_index: the source column to move.
692  * @target_index: the target location for the column
693  *
694  * This function moves the column @source_index to @target_index
695  * inside the @eth ETableHeader.  The signals "dimension_change"
696  * and "structure_change" will be emmited
697  */
698 void
e_table_header_move(ETableHeader * eth,gint source_index,gint target_index)699 e_table_header_move (ETableHeader *eth,
700                      gint source_index,
701                      gint target_index)
702 {
703 	ETableCol *old;
704 
705 	g_return_if_fail (eth != NULL);
706 	g_return_if_fail (E_IS_TABLE_HEADER (eth));
707 	g_return_if_fail (source_index >= 0);
708 	g_return_if_fail (target_index >= 0);
709 	g_return_if_fail (source_index < eth->col_count);
710 
711 	/* Can be moved beyond the last item. */
712 	g_return_if_fail (target_index < eth->col_count + 1);
713 
714 	if (source_index < target_index)
715 		target_index--;
716 
717 	old = eth->columns[source_index];
718 	eth_do_remove (eth, source_index, FALSE);
719 	eth_do_insert (eth, target_index, old);
720 	eth_update_offsets (eth);
721 
722 	g_signal_emit (eth, eth_signals[DIMENSION_CHANGE], 0, eth->width);
723 	g_signal_emit (eth, eth_signals[STRUCTURE_CHANGE], 0);
724 }
725 
726 /**
727  * e_table_header_remove:
728  * @eth: The ETableHeader to operate on.
729  * @idx: the index to the column to be removed.
730  *
731  * Removes the column at @idx position in the ETableHeader @eth.
732  * This emmits the "structure_change" signal on the @eth object.
733  */
734 void
e_table_header_remove(ETableHeader * eth,gint idx)735 e_table_header_remove (ETableHeader *eth,
736                        gint idx)
737 {
738 	g_return_if_fail (eth != NULL);
739 	g_return_if_fail (E_IS_TABLE_HEADER (eth));
740 	g_return_if_fail (idx >= 0);
741 	g_return_if_fail (idx < eth->col_count);
742 
743 	eth_do_remove (eth, idx, TRUE);
744 	enqueue (eth, -1, eth->nominal_width);
745 	g_signal_emit (eth, eth_signals[STRUCTURE_CHANGE], 0);
746 }
747 
748 /*
749  * FIXME: deprecated?
750  */
751 void
e_table_header_set_selection(ETableHeader * eth,gboolean allow_selection)752 e_table_header_set_selection (ETableHeader *eth,
753                               gboolean allow_selection)
754 {
755 	g_return_if_fail (eth != NULL);
756 	g_return_if_fail (E_IS_TABLE_HEADER (eth));
757 }
758 
759 static void
eth_set_size(ETableHeader * eth,gint idx,gint size)760 eth_set_size (ETableHeader *eth,
761               gint idx,
762               gint size)
763 {
764 	gdouble expansion;
765 	gdouble old_expansion;
766 	gint min_width;
767 	gint left_width;
768 	gint total_extra;
769 	gint expandable_count;
770 	gint usable_width;
771 	gint i;
772 	g_return_if_fail (eth != NULL);
773 	g_return_if_fail (E_IS_TABLE_HEADER (eth));
774 	g_return_if_fail (idx >= 0);
775 	g_return_if_fail (idx < eth->col_count);
776 
777 	/* If this column is not resizable, don't do anything. */
778 	if (!eth->columns[idx]->spec->resizable)
779 		return;
780 
781 	expansion = 0;
782 	min_width = 0;
783 	left_width = 0;
784 	expandable_count = -1;
785 
786 	/* Calculate usable area. */
787 	for (i = 0; i < idx; i++) {
788 		left_width += eth->columns[i]->width;
789 	}
790 	/* - 1 to account for the last pixel border. */
791 	usable_width = eth->width - left_width - 1;
792 
793 	if (eth->sort_info)
794 		usable_width -= e_table_sort_info_grouping_get_count (
795 			eth->sort_info) * GROUP_INDENT;
796 
797 	/* Calculate minimum_width of stuff on the right as well as
798 	 * total usable expansion on the right.
799 	 */
800 	for (; i < eth->col_count; i++) {
801 		min_width += eth->columns[i]->min_width + eth->width_extras;
802 		if (eth->columns[i]->spec->resizable) {
803 			expansion += eth->columns[i]->expansion;
804 			expandable_count++;
805 		}
806 	}
807 	/* If there's no room for anything, don't change. */
808 	if (expansion == 0)
809 		return;
810 
811 	/* (1) If none of the columns to the right are expandable, use
812 	 * all the expansion space in this column.
813 	 */
814 	if (expandable_count == 0) {
815 		eth->columns[idx]->expansion = expansion;
816 		for (i = idx + 1; i < eth->col_count; i++) {
817 			eth->columns[i]->expansion = 0;
818 		}
819 
820 		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
821 		return;
822 	}
823 
824 	total_extra = usable_width - min_width;
825 	/* If there's no extra space, set all expansions to 0. */
826 	if (total_extra <= 0) {
827 		for (i = idx; i < eth->col_count; i++) {
828 			eth->columns[i]->expansion = 0;
829 		}
830 		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
831 		return;
832 	}
833 
834 	/* If you try to resize smaller than the minimum width, it
835 	 * uses the minimum. */
836 	if (size < eth->columns[idx]->min_width + eth->width_extras)
837 		size = eth->columns[idx]->min_width + eth->width_extras;
838 
839 	/* If all the extra space will be used up in this column, use
840 	 * all the expansion and set all others to 0.
841 	 */
842 	if (size >= total_extra + eth->columns[idx]->min_width + eth->width_extras) {
843 		eth->columns[idx]->expansion = expansion;
844 		for (i = idx + 1; i < eth->col_count; i++) {
845 			eth->columns[i]->expansion = 0;
846 		}
847 		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
848 		return;
849 	}
850 
851 	/* The old_expansion used by columns to the right. */
852 	old_expansion = expansion;
853 	old_expansion -= eth->columns[idx]->expansion;
854 	/* Set the new expansion so that it will generate the desired size. */
855 	eth->columns[idx]->expansion =
856 		expansion * (((gdouble)(size - (eth->columns[idx]->min_width +
857 		eth->width_extras))) / ((gdouble) total_extra));
858 	/* The expansion left for the columns on the right. */
859 	expansion -= eth->columns[idx]->expansion;
860 
861 	/* (2) If the old columns to the right didn't have any
862 	 * expansion before, expand them evenly.  old_expansion > 0 by
863 	 * expansion = SUM(i=idx to col_count -1,
864 	 * columns[i]->min_width) - columns[idx]->min_width) =
865 	 * SUM(non-negatives).
866 	 */
867 	if (old_expansion == 0) {
868 		for (i = idx + 1; i < eth->col_count; i++) {
869 			if (eth->columns[idx]->spec->resizable) {
870 				/* expandable_count != 0 by (1) */
871 				eth->columns[i]->expansion = expansion / expandable_count;
872 			}
873 		}
874 		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
875 		return;
876 	}
877 
878 	for (i = idx + 1; i < eth->col_count; i++) {
879 		if (eth->columns[idx]->spec->resizable) {
880 			/* old_expansion != 0 by (2) */
881 			eth->columns[i]->expansion *= expansion / old_expansion;
882 		}
883 	}
884 	g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
885 }
886 
887 /**
888  * e_table_header_col_diff:
889  * @eth: the ETableHeader to query.
890  * @start_col: the starting column
891  * @end_col: the ending column.
892  *
893  * Computes the number of pixels between the columns @start_col and
894  * @end_col.
895  *
896  * Returns: the number of pixels between @start_col and @end_col on the
897  * @eth ETableHeader object
898  */
899 gint
e_table_header_col_diff(ETableHeader * eth,gint start_col,gint end_col)900 e_table_header_col_diff (ETableHeader *eth,
901                          gint start_col,
902                          gint end_col)
903 {
904 	gint total, col;
905 
906 	g_return_val_if_fail (eth != NULL, 0);
907 	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
908 
909 	if (start_col < 0)
910 		start_col = 0;
911 	if (end_col > eth->col_count)
912 		end_col = eth->col_count;
913 
914 	total = 0;
915 	for (col = start_col; col < end_col; col++) {
916 
917 		total += eth->columns[col]->width;
918 	}
919 
920 	return total;
921 }
922 
923 static void
eth_calc_widths(ETableHeader * eth)924 eth_calc_widths (ETableHeader *eth)
925 {
926 	gint i;
927 	gint extra;
928 	gdouble expansion;
929 	gint last_position = 0;
930 	gdouble next_position = 0;
931 	gint last_resizable = -1;
932 	gint *widths;
933 	gboolean changed;
934 
935 	widths = g_new0 (int, eth->col_count + 1);
936 
937 	extra = eth->width;
938 	expansion = 0;
939 	for (i = 0; i < eth->col_count; i++) {
940 		extra -= eth->columns[i]->min_width + eth->width_extras;
941 		if (eth->columns[i]->spec->resizable && eth->columns[i]->expansion > 0)
942 			last_resizable = i;
943 		expansion += eth->columns[i]->spec->resizable ? eth->columns[i]->expansion : 0;
944 		widths[i] = eth->columns[i]->min_width + eth->width_extras;
945 	}
946 	if (eth->sort_info)
947 		extra -= e_table_sort_info_grouping_get_count (eth->sort_info)
948 			* GROUP_INDENT;
949 	if (expansion != 0 && extra > 0) {
950 		for (i = 0; i < last_resizable; i++) {
951 			next_position +=
952 				extra * (eth->columns[i]->spec->resizable ?
953 				eth->columns[i]->expansion : 0) / expansion;
954 			widths[i] += next_position - last_position;
955 			last_position = next_position;
956 		}
957 		widths[i] += extra - last_position;
958 	}
959 
960 	changed = FALSE;
961 
962 	for (i = 0; i < eth->col_count; i++) {
963 		if (eth->columns[i]->width != widths[i]) {
964 			changed = TRUE;
965 			eth->columns[i]->width = widths[i];
966 		}
967 	}
968 	g_free (widths);
969 	if (changed)
970 		g_signal_emit (eth, eth_signals[DIMENSION_CHANGE], 0, eth->width);
971 	eth_update_offsets (eth);
972 }
973 
974 void
e_table_header_update_horizontal(ETableHeader * eth)975 e_table_header_update_horizontal (ETableHeader *eth)
976 {
977 	gint i;
978 	gint cols;
979 
980 	cols = eth->col_count;
981 
982 	for (i = 0; i < cols; i++) {
983 		gint width = 0;
984 
985 		g_signal_emit_by_name (
986 			eth, "request_width", i, &width);
987 		eth->columns[i]->min_width = width + 10;
988 		eth->columns[i]->expansion = 1;
989 	}
990 	enqueue (eth, -1, eth->nominal_width);
991 	g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
992 }
993 
994 gint
e_table_header_prioritized_column(ETableHeader * eth)995 e_table_header_prioritized_column (ETableHeader *eth)
996 {
997 	gint best_model_col = 0;
998 	gint best_priority;
999 	gint i;
1000 	gint count;
1001 
1002 	count = e_table_header_count (eth);
1003 	if (count == 0)
1004 		return -1;
1005 	best_priority = e_table_header_get_column (eth, 0)->spec->priority;
1006 	best_model_col = e_table_header_get_column (eth, 0)->spec->model_col;
1007 	for (i = 1; i < count; i++) {
1008 		gint priority = e_table_header_get_column (eth, i)->spec->priority;
1009 		if (priority > best_priority) {
1010 			best_priority = priority;
1011 			best_model_col = e_table_header_get_column (eth, i)->spec->model_col;
1012 		}
1013 	}
1014 	return best_model_col;
1015 }
1016 
1017 ETableCol *
e_table_header_prioritized_column_selected(ETableHeader * eth,ETableColCheckFunc check_func,gpointer user_data)1018 e_table_header_prioritized_column_selected (ETableHeader *eth,
1019                                             ETableColCheckFunc check_func,
1020                                             gpointer user_data)
1021 {
1022 	ETableCol *best_col = NULL;
1023 	gint best_priority = G_MININT;
1024 	gint i;
1025 	gint count;
1026 
1027 	count = e_table_header_count (eth);
1028 	if (count == 0)
1029 		return NULL;
1030 	for (i = 1; i < count; i++) {
1031 		ETableCol *col = e_table_header_get_column (eth, i);
1032 		if (col) {
1033 			if ((best_col == NULL || col->spec->priority > best_priority)
1034 			   && check_func (col, user_data)) {
1035 				best_priority = col->spec->priority;
1036 				best_col = col;
1037 			}
1038 		}
1039 	}
1040 	return best_col;
1041 }
1042