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 *
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19 *
20 */
21
22 #include "evolution-config.h"
23
24 #include "e-table-click-to-add.h"
25
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gdk-pixbuf/gdk-pixbuf.h>
30 #include <libgnomecanvas/libgnomecanvas.h>
31
32 #include "e-canvas-utils.h"
33 #include "e-canvas.h"
34 #include "e-marshal.h"
35 #include "e-table-defines.h"
36 #include "e-table-header.h"
37 #include "e-table-one.h"
38 #include "e-text.h"
39 #include "gal-a11y-e-table-click-to-add.h"
40
41 enum {
42 CURSOR_CHANGE,
43 STYLE_UPDATED,
44 LAST_SIGNAL
45 };
46
47 static guint etcta_signals[LAST_SIGNAL] = { 0 };
48
49 G_DEFINE_TYPE (
50 ETableClickToAdd,
51 e_table_click_to_add,
52 GNOME_TYPE_CANVAS_GROUP)
53
54 enum {
55 PROP_0,
56 PROP_HEADER,
57 PROP_MODEL,
58 PROP_MESSAGE,
59 PROP_WIDTH,
60 PROP_HEIGHT,
61 PROP_IS_EDITING
62 };
63
64 static void
etcta_cursor_change(GObject * object,gint row,gint col,ETableClickToAdd * etcta)65 etcta_cursor_change (GObject *object,
66 gint row,
67 gint col,
68 ETableClickToAdd *etcta)
69 {
70 g_signal_emit (
71 etcta,
72 etcta_signals[CURSOR_CHANGE], 0,
73 row, col);
74 }
75
76 static void
etcta_style_updated(ETableClickToAdd * etcta)77 etcta_style_updated (ETableClickToAdd *etcta)
78 {
79 GtkWidget *widget;
80 GdkColor fg, bg;
81
82 widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etcta)->canvas);
83
84 e_utils_get_theme_color_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &fg);
85 e_utils_get_theme_color_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &bg);
86
87 if (etcta->rect)
88 gnome_canvas_item_set (
89 etcta->rect,
90 "fill_color_gdk", &bg,
91 NULL);
92
93 if (etcta->text)
94 gnome_canvas_item_set (
95 etcta->text,
96 "fill_color_gdk", &fg,
97 NULL);
98 }
99
100 static void
etcta_add_table_header(ETableClickToAdd * etcta,ETableHeader * header)101 etcta_add_table_header (ETableClickToAdd *etcta,
102 ETableHeader *header)
103 {
104 etcta->eth = header;
105 if (etcta->eth)
106 g_object_ref (etcta->eth);
107 if (etcta->row)
108 gnome_canvas_item_set (
109 GNOME_CANVAS_ITEM (etcta->row),
110 "ETableHeader", header,
111 NULL);
112 }
113
114 static void
etcta_drop_table_header(ETableClickToAdd * etcta)115 etcta_drop_table_header (ETableClickToAdd *etcta)
116 {
117 if (!etcta->eth)
118 return;
119
120 g_object_unref (etcta->eth);
121 etcta->eth = NULL;
122 }
123
124 static void
etcta_add_one(ETableClickToAdd * etcta,ETableModel * one)125 etcta_add_one (ETableClickToAdd *etcta,
126 ETableModel *one)
127 {
128 etcta->one = one;
129 if (etcta->one)
130 g_object_ref (etcta->one);
131 if (etcta->row)
132 gnome_canvas_item_set (
133 GNOME_CANVAS_ITEM (etcta->row),
134 "ETableModel", one,
135 NULL);
136 g_object_set (
137 etcta->selection,
138 "model", one,
139 NULL);
140 }
141
142 static void
etcta_drop_one(ETableClickToAdd * etcta)143 etcta_drop_one (ETableClickToAdd *etcta)
144 {
145 if (!etcta->one)
146 return;
147 g_object_unref (etcta->one);
148 etcta->one = NULL;
149 g_object_set (
150 etcta->selection,
151 "model", NULL,
152 NULL);
153 }
154
155 static void
etcta_add_model(ETableClickToAdd * etcta,ETableModel * model)156 etcta_add_model (ETableClickToAdd *etcta,
157 ETableModel *model)
158 {
159 etcta->model = model;
160 if (etcta->model)
161 g_object_ref (etcta->model);
162 }
163
164 static void
etcta_drop_model(ETableClickToAdd * etcta)165 etcta_drop_model (ETableClickToAdd *etcta)
166 {
167 etcta_drop_one (etcta);
168 if (!etcta->model)
169 return;
170 g_object_unref (etcta->model);
171 etcta->model = NULL;
172 }
173
174 static void
etcta_add_message(ETableClickToAdd * etcta,const gchar * message)175 etcta_add_message (ETableClickToAdd *etcta,
176 const gchar *message)
177 {
178 etcta->message = g_strdup (message);
179 }
180
181 static void
etcta_drop_message(ETableClickToAdd * etcta)182 etcta_drop_message (ETableClickToAdd *etcta)
183 {
184 g_free (etcta->message);
185 etcta->message = NULL;
186 }
187
188 static void
etcta_dispose(GObject * object)189 etcta_dispose (GObject *object)
190 {
191 ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (object);
192
193 etcta_drop_table_header (etcta);
194 etcta_drop_model (etcta);
195 etcta_drop_message (etcta);
196 g_clear_object (&etcta->selection);
197
198 /* Chain up to parent's dispose() method. */
199 G_OBJECT_CLASS (e_table_click_to_add_parent_class)->dispose (object);
200 }
201
202 static void
etcta_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)203 etcta_set_property (GObject *object,
204 guint property_id,
205 const GValue *value,
206 GParamSpec *pspec)
207 {
208 GnomeCanvasItem *item;
209 ETableClickToAdd *etcta;
210
211 item = GNOME_CANVAS_ITEM (object);
212 etcta = E_TABLE_CLICK_TO_ADD (object);
213
214 switch (property_id) {
215 case PROP_HEADER:
216 etcta_drop_table_header (etcta);
217 etcta_add_table_header (etcta, E_TABLE_HEADER (g_value_get_object (value)));
218 break;
219 case PROP_MODEL:
220 etcta_drop_model (etcta);
221 etcta_add_model (etcta, E_TABLE_MODEL (g_value_get_object (value)));
222 break;
223 case PROP_MESSAGE:
224 etcta_drop_message (etcta);
225 etcta_add_message (etcta, g_value_get_string (value));
226 break;
227 case PROP_WIDTH:
228 etcta->width = g_value_get_double (value);
229 if (etcta->row)
230 gnome_canvas_item_set (
231 etcta->row,
232 "minimum_width", etcta->width,
233 NULL);
234 if (etcta->text)
235 gnome_canvas_item_set (
236 etcta->text,
237 "width", (etcta->width < 4 ? 4 : etcta->width) - 4,
238 NULL);
239 if (etcta->rect)
240 gnome_canvas_item_set (
241 etcta->rect,
242 "x2", etcta->width,
243 NULL);
244 break;
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
247 return;
248
249 }
250 gnome_canvas_item_request_update (item);
251 }
252
253 static void
create_rect_and_text(ETableClickToAdd * etcta)254 create_rect_and_text (ETableClickToAdd *etcta)
255 {
256 GtkWidget *widget;
257 GdkColor fg, bg;
258
259 widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etcta)->canvas);
260
261 e_utils_get_theme_color_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &fg);
262 e_utils_get_theme_color_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &bg);
263
264 if (!etcta->rect)
265 etcta->rect = gnome_canvas_item_new (
266 GNOME_CANVAS_GROUP (etcta),
267 gnome_canvas_rect_get_type (),
268 "x1", (gdouble) 0,
269 "y1", (gdouble) 1,
270 "x2", (gdouble) etcta->width,
271 "y2", (gdouble) etcta->height,
272 "fill_color_gdk", &bg,
273 NULL);
274
275 if (!etcta->text)
276 etcta->text = gnome_canvas_item_new (
277 GNOME_CANVAS_GROUP (etcta),
278 e_text_get_type (),
279 "text", etcta->message ? etcta->message : "",
280 "width", etcta->width - 4,
281 "fill_color_gdk", &fg,
282 NULL);
283 }
284
285 static void
etcta_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)286 etcta_get_property (GObject *object,
287 guint property_id,
288 GValue *value,
289 GParamSpec *pspec)
290 {
291 ETableClickToAdd *etcta;
292
293 etcta = E_TABLE_CLICK_TO_ADD (object);
294
295 switch (property_id) {
296 case PROP_HEADER:
297 g_value_set_object (value, etcta->eth);
298 break;
299 case PROP_MODEL:
300 g_value_set_object (value, etcta->model);
301 break;
302 case PROP_MESSAGE:
303 g_value_set_string (value, etcta->message);
304 break;
305 case PROP_WIDTH:
306 g_value_set_double (value, etcta->width);
307 break;
308 case PROP_HEIGHT:
309 g_value_set_double (value, etcta->height);
310 break;
311 case PROP_IS_EDITING:
312 g_value_set_boolean (value, e_table_click_to_add_is_editing (etcta));
313 break;
314 default:
315 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
316 break;
317 }
318 }
319
320 static void
etcta_realize(GnomeCanvasItem * item)321 etcta_realize (GnomeCanvasItem *item)
322 {
323 ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
324
325 create_rect_and_text (etcta);
326 e_canvas_item_move_absolute (etcta->text, 2, 2);
327
328 if (GNOME_CANVAS_ITEM_CLASS (e_table_click_to_add_parent_class)->realize)
329 (*GNOME_CANVAS_ITEM_CLASS (e_table_click_to_add_parent_class)->realize)(item);
330
331 e_canvas_item_request_reflow (item);
332 }
333
334 static void
etcta_unrealize(GnomeCanvasItem * item)335 etcta_unrealize (GnomeCanvasItem *item)
336 {
337 if (GNOME_CANVAS_ITEM_CLASS (e_table_click_to_add_parent_class)->unrealize)
338 (*GNOME_CANVAS_ITEM_CLASS (e_table_click_to_add_parent_class)->unrealize)(item);
339 }
340
341 static void finish_editing (ETableClickToAdd *etcta);
342
343 static gint
item_key_press(ETableItem * item,gint row,gint col,GdkEvent * event,ETableClickToAdd * etcta)344 item_key_press (ETableItem *item,
345 gint row,
346 gint col,
347 GdkEvent *event,
348 ETableClickToAdd *etcta)
349 {
350 switch (event->key.keyval) {
351 case GDK_KEY_Return:
352 case GDK_KEY_KP_Enter:
353 case GDK_KEY_ISO_Enter:
354 case GDK_KEY_3270_Enter:
355 finish_editing (etcta);
356 return TRUE;
357 }
358 return FALSE;
359 }
360
361 static void
set_initial_selection(ETableClickToAdd * etcta)362 set_initial_selection (ETableClickToAdd *etcta)
363 {
364 e_selection_model_do_something (
365 E_SELECTION_MODEL (etcta->selection),
366 0, e_table_header_prioritized_column (etcta->eth),
367 0);
368 }
369
370 static void
table_click_to_add_row_is_editing_changed_cb(ETableItem * item,GParamSpec * param,ETableClickToAdd * etcta)371 table_click_to_add_row_is_editing_changed_cb (ETableItem *item,
372 GParamSpec *param,
373 ETableClickToAdd *etcta)
374 {
375 g_return_if_fail (E_IS_TABLE_CLICK_TO_ADD (etcta));
376
377 g_object_notify (G_OBJECT (etcta), "is-editing");
378 }
379
380 static void
finish_editing(ETableClickToAdd * etcta)381 finish_editing (ETableClickToAdd *etcta)
382 {
383 if (etcta->row) {
384 ETableModel *one;
385
386 e_table_item_leave_edit (E_TABLE_ITEM (etcta->row));
387 e_table_one_commit (E_TABLE_ONE (etcta->one));
388 etcta_drop_one (etcta);
389 g_object_run_dispose (G_OBJECT (etcta->row));
390 etcta->row = NULL;
391
392 if (etcta->text) {
393 g_object_run_dispose (G_OBJECT (etcta->text));
394 etcta->text = NULL;
395 }
396 if (etcta->rect) {
397 g_object_run_dispose (G_OBJECT (etcta->rect));
398 etcta->rect = NULL;
399 }
400
401 one = e_table_one_new (etcta->model);
402 etcta_add_one (etcta, one);
403 g_object_unref (one);
404
405 e_selection_model_clear (E_SELECTION_MODEL (etcta->selection));
406
407 etcta->row = gnome_canvas_item_new (
408 GNOME_CANVAS_GROUP (etcta),
409 e_table_item_get_type (),
410 "ETableHeader", etcta->eth,
411 "ETableModel", etcta->one,
412 "minimum_width", etcta->width,
413 "horizontal_draw_grid", TRUE,
414 "vertical_draw_grid", TRUE,
415 "selection_model", etcta->selection,
416 "cursor_mode", E_CURSOR_SPREADSHEET,
417 NULL);
418
419 g_signal_connect (
420 etcta->row, "key_press",
421 G_CALLBACK (item_key_press), etcta);
422
423 e_signal_connect_notify (
424 etcta->row, "notify::is-editing",
425 G_CALLBACK (table_click_to_add_row_is_editing_changed_cb), etcta);
426
427 set_initial_selection (etcta);
428
429 g_object_notify (G_OBJECT (etcta), "is-editing");
430 }
431 }
432
433 /* Handles the events on the ETableClickToAdd, particularly
434 * it creates the ETableItem and passes in some events. */
435 static gint
etcta_event(GnomeCanvasItem * item,GdkEvent * e)436 etcta_event (GnomeCanvasItem *item,
437 GdkEvent *e)
438 {
439 ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
440
441 switch (e->type) {
442 case GDK_FOCUS_CHANGE:
443 if (!e->focus_change.in)
444 return TRUE;
445 /* coverity[fallthrough] */
446 /* falls through */
447
448 case GDK_BUTTON_PRESS:
449 if (etcta->text) {
450 g_object_run_dispose (G_OBJECT (etcta->text));
451 etcta->text = NULL;
452 }
453 if (etcta->rect) {
454 g_object_run_dispose (G_OBJECT (etcta->rect));
455 etcta->rect = NULL;
456 }
457 if (!etcta->row) {
458 ETableModel *one;
459
460 one = e_table_one_new (etcta->model);
461 etcta_add_one (etcta, one);
462 g_object_unref (one);
463
464 e_selection_model_clear (E_SELECTION_MODEL (etcta->selection));
465
466 etcta->row = gnome_canvas_item_new (
467 GNOME_CANVAS_GROUP (item),
468 e_table_item_get_type (),
469 "ETableHeader", etcta->eth,
470 "ETableModel", etcta->one,
471 "minimum_width", etcta->width,
472 "horizontal_draw_grid", TRUE,
473 "vertical_draw_grid", TRUE,
474 "selection_model", etcta->selection,
475 "cursor_mode", E_CURSOR_SPREADSHEET,
476 NULL);
477
478 g_signal_connect (
479 etcta->row, "key_press",
480 G_CALLBACK (item_key_press), etcta);
481
482 e_signal_connect_notify (
483 etcta->row, "notify::is-editing",
484 G_CALLBACK (table_click_to_add_row_is_editing_changed_cb), etcta);
485
486 e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (etcta->row), TRUE);
487
488 set_initial_selection (etcta);
489
490 g_object_notify (G_OBJECT (etcta), "is-editing");
491 }
492 break;
493
494 case GDK_KEY_PRESS:
495 switch (e->key.keyval) {
496 case GDK_KEY_Tab:
497 case GDK_KEY_KP_Tab:
498 case GDK_KEY_ISO_Left_Tab:
499 finish_editing (etcta);
500 break;
501 default:
502 return FALSE;
503 case GDK_KEY_Escape:
504 if (etcta->row) {
505 e_table_item_leave_edit (E_TABLE_ITEM (etcta->row));
506 etcta_drop_one (etcta);
507 g_object_run_dispose (G_OBJECT (etcta->row));
508 etcta->row = NULL;
509 create_rect_and_text (etcta);
510 e_canvas_item_move_absolute (etcta->text, 3, 3);
511 }
512 break;
513 }
514 break;
515
516 default:
517 return FALSE;
518 }
519 return TRUE;
520 }
521
522 static void
etcta_reflow(GnomeCanvasItem * item,gint flags)523 etcta_reflow (GnomeCanvasItem *item,
524 gint flags)
525 {
526 ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
527
528 gdouble old_height = etcta->height;
529
530 if (etcta->text) {
531 g_object_get (
532 etcta->text,
533 "height", &etcta->height,
534 NULL);
535 etcta->height += 6;
536 }
537 if (etcta->row) {
538 g_object_get (
539 etcta->row,
540 "height", &etcta->height,
541 NULL);
542 }
543
544 if (etcta->rect) {
545 g_object_set (
546 etcta->rect,
547 "y2", etcta->height - 1,
548 NULL);
549 }
550
551 if (old_height != etcta->height)
552 e_canvas_item_request_parent_reflow (item);
553 }
554
555 static void
e_table_click_to_add_class_init(ETableClickToAddClass * class)556 e_table_click_to_add_class_init (ETableClickToAddClass *class)
557 {
558 GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
559 GObjectClass *object_class = G_OBJECT_CLASS (class);
560
561 class->cursor_change = NULL;
562 class->style_updated = etcta_style_updated;
563
564 object_class->dispose = etcta_dispose;
565 object_class->set_property = etcta_set_property;
566 object_class->get_property = etcta_get_property;
567
568 item_class->realize = etcta_realize;
569 item_class->unrealize = etcta_unrealize;
570 item_class->event = etcta_event;
571
572 g_object_class_install_property (
573 object_class,
574 PROP_HEADER,
575 g_param_spec_object (
576 "header",
577 "Header",
578 NULL,
579 E_TYPE_TABLE_HEADER,
580 G_PARAM_READWRITE));
581
582 g_object_class_install_property (
583 object_class,
584 PROP_MODEL,
585 g_param_spec_object (
586 "model",
587 "Model",
588 NULL,
589 E_TYPE_TABLE_MODEL,
590 G_PARAM_READWRITE));
591
592 g_object_class_install_property (
593 object_class,
594 PROP_MESSAGE,
595 g_param_spec_string (
596 "message",
597 "Message",
598 NULL,
599 NULL,
600 G_PARAM_READWRITE));
601
602 g_object_class_install_property (
603 object_class,
604 PROP_WIDTH,
605 g_param_spec_double (
606 "width",
607 "Width",
608 NULL,
609 0.0, G_MAXDOUBLE, 0.0,
610 G_PARAM_READWRITE |
611 G_PARAM_LAX_VALIDATION));
612
613 g_object_class_install_property (
614 object_class,
615 PROP_HEIGHT,
616 g_param_spec_double (
617 "height",
618 "Height",
619 NULL,
620 0.0, G_MAXDOUBLE, 0.0,
621 G_PARAM_READABLE |
622 G_PARAM_LAX_VALIDATION));
623
624 g_object_class_install_property (
625 object_class,
626 PROP_IS_EDITING,
627 g_param_spec_boolean (
628 "is-editing",
629 "Whether is in an editing mode",
630 "Whether is in an editing mode",
631 FALSE,
632 G_PARAM_READABLE));
633
634 etcta_signals[CURSOR_CHANGE] = g_signal_new (
635 "cursor_change",
636 G_OBJECT_CLASS_TYPE (object_class),
637 G_SIGNAL_RUN_LAST,
638 G_STRUCT_OFFSET (ETableClickToAddClass, cursor_change),
639 NULL, NULL,
640 e_marshal_VOID__INT_INT,
641 G_TYPE_NONE, 2,
642 G_TYPE_INT,
643 G_TYPE_INT);
644
645 etcta_signals[STYLE_UPDATED] = g_signal_new (
646 "style_updated",
647 G_OBJECT_CLASS_TYPE (object_class),
648 G_SIGNAL_RUN_LAST,
649 G_STRUCT_OFFSET (ETableClickToAddClass, style_updated),
650 NULL, NULL,
651 g_cclosure_marshal_VOID__VOID,
652 G_TYPE_NONE, 0);
653
654 gal_a11y_e_table_click_to_add_init ();
655 }
656
657 static void
e_table_click_to_add_init(ETableClickToAdd * etcta)658 e_table_click_to_add_init (ETableClickToAdd *etcta)
659 {
660 AtkObject *a11y;
661
662 etcta->one = NULL;
663 etcta->model = NULL;
664 etcta->eth = NULL;
665
666 etcta->message = NULL;
667
668 etcta->row = NULL;
669 etcta->text = NULL;
670 etcta->rect = NULL;
671
672 /* Pick some arbitrary defaults. */
673 etcta->width = 12;
674 etcta->height = 6;
675
676 etcta->selection = e_table_selection_model_new ();
677 g_signal_connect (
678 etcta->selection, "cursor_changed",
679 G_CALLBACK (etcta_cursor_change), etcta);
680
681 e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (etcta), etcta_reflow);
682
683 /* create its a11y object at this time if accessibility is enabled*/
684 if (atk_get_root () != NULL) {
685 a11y = atk_gobject_accessible_for_object (G_OBJECT (etcta));
686 atk_object_set_name (a11y, _("click to add"));
687 }
688 }
689
690 /* The colors in this need to be themefied. */
691 /**
692 * e_table_click_to_add_commit:
693 * @etcta: The %ETableClickToAdd to commit.
694 *
695 * This routine commits the current thing being edited and returns to
696 * just displaying the click to add message.
697 **/
698 void
e_table_click_to_add_commit(ETableClickToAdd * etcta)699 e_table_click_to_add_commit (ETableClickToAdd *etcta)
700 {
701 if (etcta->row) {
702 e_table_one_commit (E_TABLE_ONE (etcta->one));
703 etcta_drop_one (etcta);
704 g_object_run_dispose (G_OBJECT (etcta->row));
705 etcta->row = NULL;
706 }
707 create_rect_and_text (etcta);
708 e_canvas_item_move_absolute (etcta->text, 3, 3);
709 }
710
711 gboolean
e_table_click_to_add_is_editing(ETableClickToAdd * etcta)712 e_table_click_to_add_is_editing (ETableClickToAdd *etcta)
713 {
714 g_return_val_if_fail (E_IS_TABLE_CLICK_TO_ADD (etcta), FALSE);
715
716 return etcta->row && e_table_item_is_editing (E_TABLE_ITEM (etcta->row));
717 }
718