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 * Damon Chaplin <damon@ximian.com>
17 * Rodrigo Moya <rodrigo@ximian.com>
18 * Nathan Owens <pianocomp81@yahoo.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 /*
25 * EMemoTable - displays the ECalComponent objects in a table (an ETable).
26 */
27
28 #include "evolution-config.h"
29
30 #include "e-memo-table.h"
31
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <glib/gi18n.h>
35 #include <glib/gstdio.h>
36
37 #include "comp-util.h"
38 #include "e-cal-dialogs.h"
39 #include "e-cal-model-memos.h"
40 #include "e-cal-ops.h"
41 #include "e-calendar-view.h"
42 #include "e-cell-date-edit-text.h"
43 #include "itip-utils.h"
44 #include "print.h"
45 #include "misc.h"
46
47 #define E_MEMO_TABLE_GET_PRIVATE(obj) \
48 (G_TYPE_INSTANCE_GET_PRIVATE \
49 ((obj), E_TYPE_MEMO_TABLE, EMemoTablePrivate))
50
51 struct _EMemoTablePrivate {
52 gpointer shell_view; /* weak pointer */
53 ECalModel *model;
54
55 /* Fields used for cut/copy/paste */
56 ICalComponent *tmp_vcal;
57
58 GtkTargetList *copy_target_list;
59 GtkTargetList *paste_target_list;
60 };
61
62 enum {
63 PROP_0,
64 PROP_COPY_TARGET_LIST,
65 PROP_MODEL,
66 PROP_PASTE_TARGET_LIST,
67 PROP_SHELL_VIEW
68 };
69
70 enum {
71 OPEN_COMPONENT,
72 POPUP_EVENT,
73 LAST_SIGNAL
74 };
75
76 static guint signals[LAST_SIGNAL];
77
78 /* The icons to represent the memo. */
79 static const gchar *icon_names[] = {
80 "stock_notes",
81 "stock_insert-note"
82 };
83
84 /* Forward Declarations */
85 static void e_memo_table_selectable_init
86 (ESelectableInterface *iface);
87
G_DEFINE_TYPE_WITH_CODE(EMemoTable,e_memo_table,E_TYPE_TABLE,G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE,e_memo_table_selectable_init))88 G_DEFINE_TYPE_WITH_CODE (
89 EMemoTable,
90 e_memo_table,
91 E_TYPE_TABLE,
92 G_IMPLEMENT_INTERFACE (
93 E_TYPE_SELECTABLE,
94 e_memo_table_selectable_init))
95
96 static void
97 memo_table_emit_open_component (EMemoTable *memo_table,
98 ECalModelComponent *comp_data)
99 {
100 guint signal_id = signals[OPEN_COMPONENT];
101
102 g_signal_emit (memo_table, signal_id, 0, comp_data);
103 }
104
105 static void
memo_table_emit_popup_event(EMemoTable * memo_table,GdkEvent * event)106 memo_table_emit_popup_event (EMemoTable *memo_table,
107 GdkEvent *event)
108 {
109 guint signal_id = signals[POPUP_EVENT];
110
111 g_signal_emit (memo_table, signal_id, 0, event);
112 }
113
114 /* Returns the current time, for the ECellDateEdit items.
115 * FIXME: Should probably use the timezone of the item rather than the
116 * current timezone, though that may be difficult to get from here. */
117 static struct tm
memo_table_get_current_time(ECellDateEdit * ecde,gpointer user_data)118 memo_table_get_current_time (ECellDateEdit *ecde,
119 gpointer user_data)
120 {
121 EMemoTable *memo_table = user_data;
122 ECalModel *model;
123 ICalTimezone *zone;
124 ICalTime *tt;
125 struct tm tmp_tm;
126
127 /* Get the current timezone. */
128 model = e_memo_table_get_model (memo_table);
129 zone = e_cal_model_get_timezone (model);
130
131 tt = i_cal_time_new_from_timet_with_zone (time (NULL), FALSE, zone);
132
133 /* Now copy it to the struct tm and return it. */
134 tmp_tm = e_cal_util_icaltime_to_tm (tt);
135
136 g_clear_object (&tt);
137
138 return tmp_tm;
139 }
140
141 static void
memo_table_date_edit_before_popup_cb(ECellDateEdit * cell_date_edit,gint row,gint view_col,gpointer user_data)142 memo_table_date_edit_before_popup_cb (ECellDateEdit *cell_date_edit,
143 gint row,
144 gint view_col,
145 gpointer user_data)
146 {
147 EMemoTable *memo_table = user_data;
148 ECellPopup *ecp;
149 ETableItem *eti;
150 ETableCol *ecol;
151 gint col;
152 gboolean date_only;
153
154 g_return_if_fail (E_IS_MEMO_TABLE (memo_table));
155
156 ecp = E_CELL_POPUP (cell_date_edit);
157 eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
158 ecol = e_table_header_get_column (eti->header, view_col);
159 col = ecol->spec->model_col;
160
161 date_only = col == E_CAL_MODEL_FIELD_DTSTART;
162
163 if (date_only && e_table_get_cursor_row (E_TABLE (memo_table)) != -1) {
164 ECalModel *model;
165 ECalModelComponent *comp_data;
166 ESelectionModel *esm;
167
168 esm = e_table_get_selection_model (E_TABLE (memo_table));
169 if (esm && esm->sorter && e_sorter_needs_sorting (esm->sorter))
170 row = e_sorter_sorted_to_model (esm->sorter, row);
171
172 model = e_memo_table_get_model (memo_table);
173 comp_data = e_cal_model_get_component_at (model, row);
174 if (comp_data && comp_data->icalcomp) {
175 ICalProperty *prop;
176
177 prop = i_cal_component_get_first_property (comp_data->icalcomp, I_CAL_DTSTART_PROPERTY);
178 if (prop) {
179 ICalTime *dtstart;
180
181 dtstart = i_cal_property_get_dtstart (prop);
182 date_only = !dtstart || i_cal_time_is_null_time (dtstart) || i_cal_time_is_date (dtstart);
183 g_clear_object (&dtstart);
184 }
185
186 g_clear_object (&prop);
187 }
188 }
189
190 g_object_set (G_OBJECT (cell_date_edit), "show-time", !date_only, NULL);
191 }
192
193 /* Deletes all of the selected components in the table */
194 static void
delete_selected_components(EMemoTable * memo_table)195 delete_selected_components (EMemoTable *memo_table)
196 {
197 GSList *objs;
198
199 objs = e_memo_table_get_selected (memo_table);
200 e_cal_ops_delete_ecalmodel_components (memo_table->priv->model, objs);
201 g_slist_free (objs);
202 }
203
204 static void
memo_table_set_model(EMemoTable * memo_table,ECalModel * model)205 memo_table_set_model (EMemoTable *memo_table,
206 ECalModel *model)
207 {
208 g_return_if_fail (memo_table->priv->model == NULL);
209
210 memo_table->priv->model = g_object_ref (model);
211 }
212
213 static void
memo_table_set_shell_view(EMemoTable * memo_table,EShellView * shell_view)214 memo_table_set_shell_view (EMemoTable *memo_table,
215 EShellView *shell_view)
216 {
217 g_return_if_fail (memo_table->priv->shell_view == NULL);
218
219 memo_table->priv->shell_view = shell_view;
220
221 g_object_add_weak_pointer (
222 G_OBJECT (shell_view),
223 &memo_table->priv->shell_view);
224 }
225
226 static void
memo_table_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)227 memo_table_set_property (GObject *object,
228 guint property_id,
229 const GValue *value,
230 GParamSpec *pspec)
231 {
232 switch (property_id) {
233 case PROP_MODEL:
234 memo_table_set_model (
235 E_MEMO_TABLE (object),
236 g_value_get_object (value));
237 return;
238
239 case PROP_SHELL_VIEW:
240 memo_table_set_shell_view (
241 E_MEMO_TABLE (object),
242 g_value_get_object (value));
243 return;
244 }
245
246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
247 }
248
249 static void
memo_table_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)250 memo_table_get_property (GObject *object,
251 guint property_id,
252 GValue *value,
253 GParamSpec *pspec)
254 {
255 switch (property_id) {
256 case PROP_COPY_TARGET_LIST:
257 g_value_set_boxed (
258 value, e_memo_table_get_copy_target_list (
259 E_MEMO_TABLE (object)));
260 return;
261
262 case PROP_MODEL:
263 g_value_set_object (
264 value, e_memo_table_get_model (
265 E_MEMO_TABLE (object)));
266 return;
267
268 case PROP_PASTE_TARGET_LIST:
269 g_value_set_boxed (
270 value, e_memo_table_get_paste_target_list (
271 E_MEMO_TABLE (object)));
272 return;
273
274 case PROP_SHELL_VIEW:
275 g_value_set_object (
276 value, e_memo_table_get_shell_view (
277 E_MEMO_TABLE (object)));
278 return;
279 }
280
281 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
282 }
283
284 static void
memo_table_dispose(GObject * object)285 memo_table_dispose (GObject *object)
286 {
287 EMemoTablePrivate *priv;
288
289 priv = E_MEMO_TABLE_GET_PRIVATE (object);
290
291 if (priv->shell_view != NULL) {
292 g_object_remove_weak_pointer (
293 G_OBJECT (priv->shell_view), &priv->shell_view);
294 priv->shell_view = NULL;
295 }
296
297 g_clear_object (&priv->model);
298 g_clear_pointer (&priv->copy_target_list, gtk_target_list_unref);
299 g_clear_pointer (&priv->paste_target_list, gtk_target_list_unref);
300
301 /* Chain up to parent's dispose() method. */
302 G_OBJECT_CLASS (e_memo_table_parent_class)->dispose (object);
303 }
304
305 static void
memo_table_constructed(GObject * object)306 memo_table_constructed (GObject *object)
307 {
308 EMemoTable *memo_table;
309 ECalModel *model;
310 ECell *cell, *popup_cell;
311 ETableExtras *extras;
312 ETableSpecification *specification;
313 AtkObject *a11y;
314 gchar *etspecfile;
315 GList *strings;
316 GError *local_error = NULL;
317
318 memo_table = E_MEMO_TABLE (object);
319 model = e_memo_table_get_model (memo_table);
320
321 /* Create the header columns */
322
323 extras = e_table_extras_new ();
324
325 /*
326 * Normal string fields.
327 */
328 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
329 g_object_set (cell,
330 "bg_color_column", E_CAL_MODEL_FIELD_COLOR,
331 "strikeout_column", E_CAL_MODEL_FIELD_CANCELLED,
332 NULL);
333 e_table_extras_add_cell (extras, "calstring", cell);
334 g_object_unref (cell);
335
336 /*
337 * Date fields.
338 */
339 cell = e_cell_date_edit_text_new (NULL, GTK_JUSTIFY_LEFT);
340 g_object_set (cell,
341 "bg_color_column", E_CAL_MODEL_FIELD_COLOR,
342 "strikeout_column", E_CAL_MODEL_FIELD_CANCELLED,
343 NULL);
344
345 e_binding_bind_property (
346 model, "timezone",
347 cell, "timezone",
348 G_BINDING_BIDIRECTIONAL |
349 G_BINDING_SYNC_CREATE);
350
351 e_binding_bind_property (
352 model, "use-24-hour-format",
353 cell, "use-24-hour-format",
354 G_BINDING_BIDIRECTIONAL |
355 G_BINDING_SYNC_CREATE);
356
357 popup_cell = e_cell_date_edit_new ();
358 e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell);
359 g_object_unref (cell);
360
361 e_binding_bind_property (
362 model, "use-24-hour-format",
363 popup_cell, "use-24-hour-format",
364 G_BINDING_BIDIRECTIONAL |
365 G_BINDING_SYNC_CREATE);
366
367 g_signal_connect (popup_cell, "before-popup",
368 G_CALLBACK (memo_table_date_edit_before_popup_cb), memo_table);
369
370 e_table_extras_add_cell (extras, "dateedit", popup_cell);
371 g_object_unref (popup_cell);
372
373 e_cell_date_edit_set_get_time_callback (
374 E_CELL_DATE_EDIT (popup_cell),
375 memo_table_get_current_time, memo_table, NULL);
376
377 /* Status field. */
378 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
379 g_object_set (
380 cell,
381 "bg_color_column", E_CAL_MODEL_FIELD_COLOR,
382 "strikeout_column", E_CAL_MODEL_FIELD_CANCELLED,
383 NULL);
384
385 popup_cell = e_cell_combo_new ();
386 e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell);
387 g_object_unref (cell);
388
389 strings = cal_comp_util_get_status_list_for_kind (e_cal_model_get_component_kind (model));
390 e_cell_combo_set_popdown_strings (
391 E_CELL_COMBO (popup_cell),
392 strings);
393 g_list_free (strings);
394
395 e_table_extras_add_cell (extras, "calstatus", popup_cell);
396 g_object_unref (popup_cell);
397
398 /* Sorting */
399 e_table_extras_add_compare (
400 extras, "date-compare", e_cell_date_edit_compare_cb);
401 e_table_extras_add_compare (
402 extras, "status-compare",
403 e_cal_model_util_status_compare_cb);
404
405 /* Create pixmaps */
406
407 cell = e_cell_toggle_new (icon_names, G_N_ELEMENTS (icon_names));
408 g_object_set (cell,
409 "bg-color-column", E_CAL_MODEL_FIELD_COLOR,
410 NULL);
411 e_table_extras_add_cell (extras, "icon", cell);
412 g_object_unref (cell);
413
414 e_table_extras_add_icon_name (extras, "icon", "stock_notes");
415
416 /* set proper format component for a default 'date' cell renderer */
417 cell = e_table_extras_get_cell (extras, "date");
418 e_cell_date_set_format_component (E_CELL_DATE (cell), "calendar");
419
420 /* Construct the table */
421
422 etspecfile = g_build_filename (
423 EVOLUTION_ETSPECDIR, "e-memo-table.etspec", NULL);
424 specification = e_table_specification_new (etspecfile, &local_error);
425
426 /* Failure here is fatal. */
427 if (local_error != NULL) {
428 g_error ("%s: %s", etspecfile, local_error->message);
429 g_return_if_reached ();
430 }
431
432 e_table_construct (
433 E_TABLE (memo_table),
434 E_TABLE_MODEL (model),
435 extras, specification);
436
437 g_object_unref (specification);
438 g_free (etspecfile);
439
440 gtk_widget_set_has_tooltip (GTK_WIDGET (memo_table), TRUE);
441
442 g_object_unref (extras);
443
444 a11y = gtk_widget_get_accessible (GTK_WIDGET (memo_table));
445 if (a11y)
446 atk_object_set_name (a11y, _("Memos"));
447
448 /* Chain up to parent's constructed() method. */
449 G_OBJECT_CLASS (e_memo_table_parent_class)->constructed (object);
450 }
451
452 static gboolean
memo_table_popup_menu(GtkWidget * widget)453 memo_table_popup_menu (GtkWidget *widget)
454 {
455 EMemoTable *memo_table;
456
457 memo_table = E_MEMO_TABLE (widget);
458 memo_table_emit_popup_event (memo_table, NULL);
459
460 return TRUE;
461 }
462
463 static gboolean
memo_table_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip)464 memo_table_query_tooltip (GtkWidget *widget,
465 gint x,
466 gint y,
467 gboolean keyboard_mode,
468 GtkTooltip *tooltip)
469 {
470 EMemoTable *memo_table;
471 ECalModel *model;
472 ECalModelComponent *comp_data;
473 gint row = -1, col = -1, row_y = -1, row_height = -1;
474 GtkWidget *box, *l, *w;
475 GdkRGBA sel_bg, sel_fg, norm_bg, norm_text;
476 gchar *tmp, *summary;
477 GString *tmp2;
478 ECalComponent *new_comp;
479 ECalComponentOrganizer *organizer;
480 ECalComponentDateTime *dtstart, *dtdue;
481 ICalTimezone *zone, *default_zone;
482 GSList *desc, *p;
483 gint len;
484 ESelectionModel *esm;
485 struct tm tmp_tm;
486
487 if (keyboard_mode)
488 return FALSE;
489
490 memo_table = E_MEMO_TABLE (widget);
491
492 e_table_get_mouse_over_cell (E_TABLE (memo_table), &row, &col);
493 if (row == -1)
494 return FALSE;
495
496 /* Respect sorting option; the 'e_table_get_mouse_over_cell'
497 * returns sorted row, not the model one. */
498 esm = e_table_get_selection_model (E_TABLE (memo_table));
499 if (esm && esm->sorter && e_sorter_needs_sorting (esm->sorter))
500 row = e_sorter_sorted_to_model (esm->sorter, row);
501
502 if (row < 0)
503 return FALSE;
504
505 model = e_memo_table_get_model (memo_table);
506 comp_data = e_cal_model_get_component_at (model, row);
507 if (!comp_data || !comp_data->icalcomp)
508 return FALSE;
509
510 new_comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone (comp_data->icalcomp));
511 if (!new_comp)
512 return FALSE;
513
514 e_utils_get_theme_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &sel_bg);
515 e_utils_get_theme_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &sel_fg);
516 e_utils_get_theme_color (widget, "theme_bg_color", E_UTILS_DEFAULT_THEME_BG_COLOR, &norm_bg);
517 e_utils_get_theme_color (widget, "theme_text_color,theme_fg_color", E_UTILS_DEFAULT_THEME_TEXT_COLOR, &norm_text);
518
519 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
520
521 summary = e_calendar_view_dup_component_summary (comp_data->icalcomp);
522 if (!(summary && *summary)) {
523 g_free (summary);
524 summary = g_strdup (_("* No Summary *"));
525 }
526
527 l = gtk_label_new (NULL);
528 tmp = g_markup_printf_escaped ("<b>%s</b>", summary);
529 gtk_label_set_line_wrap (GTK_LABEL (l), TRUE);
530 gtk_label_set_width_chars (GTK_LABEL (l), 20);
531 gtk_label_set_markup (GTK_LABEL (l), tmp);
532 gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5);
533 w = gtk_event_box_new ();
534
535 gtk_widget_override_background_color (w, GTK_STATE_FLAG_NORMAL, &sel_bg);
536 gtk_widget_override_color (l, GTK_STATE_FLAG_NORMAL, &sel_fg);
537 gtk_container_add (GTK_CONTAINER (w), l);
538 gtk_box_pack_start (GTK_BOX (box), w, TRUE, TRUE, 0);
539 g_free (tmp);
540
541 g_free (summary);
542
543 w = gtk_event_box_new ();
544 gtk_widget_override_background_color (w, GTK_STATE_FLAG_NORMAL, &norm_bg);
545
546 l = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
547 gtk_container_add (GTK_CONTAINER (w), l);
548 gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 0);
549 w = l;
550
551 organizer = e_cal_component_get_organizer (new_comp);
552 if (organizer && e_cal_component_organizer_get_cn (organizer)) {
553 const gchar *email;
554
555 email = itip_strip_mailto (e_cal_component_organizer_get_value (organizer));
556
557 if (email) {
558 tmp = g_strdup_printf (
559 /* Translators: It will display
560 * "Organizer: NameOfTheUser <email@ofuser.com>" */
561 _("Organizer: %s <%s>"), e_cal_component_organizer_get_cn (organizer), email);
562 } else {
563 /* With SunOne accounts, there may be no ':' in
564 * organizer.value */
565 tmp = g_strdup_printf (
566 _("Organizer: %s"), e_cal_component_organizer_get_cn (organizer));
567 }
568
569 l = gtk_label_new (tmp);
570 gtk_label_set_line_wrap (GTK_LABEL (l), FALSE);
571 gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5);
572 gtk_box_pack_start (GTK_BOX (w), l, FALSE, FALSE, 0);
573 g_free (tmp);
574
575 gtk_widget_override_color (l, GTK_STATE_FLAG_NORMAL, &norm_text);
576 }
577
578 e_cal_component_organizer_free (organizer);
579
580 dtstart = e_cal_component_get_dtstart (new_comp);
581 dtdue = e_cal_component_get_due (new_comp);
582
583 default_zone = e_cal_model_get_timezone (model);
584
585 if (dtstart && e_cal_component_datetime_get_tzid (dtstart)) {
586 zone = i_cal_component_get_timezone (
587 e_cal_component_get_icalcomponent (new_comp),
588 e_cal_component_datetime_get_tzid (dtstart));
589 if (!zone) {
590 if (!e_cal_client_get_timezone_sync (comp_data->client, e_cal_component_datetime_get_tzid (dtstart), &zone, NULL, NULL))
591 zone = NULL;
592 }
593 if (!zone)
594 zone = default_zone;
595 } else {
596 zone = NULL;
597 }
598
599 tmp2 = g_string_new ("");
600
601 if (dtstart && e_cal_component_datetime_get_value (dtstart)) {
602 gchar *str;
603
604 tmp_tm = e_cal_util_icaltime_to_tm_with_zone (e_cal_component_datetime_get_value (dtstart), zone, default_zone);
605 str = e_datetime_format_format_tm ("calendar", "table",
606 i_cal_time_is_date (e_cal_component_datetime_get_value (dtstart)) ? DTFormatKindDate : DTFormatKindDateTime,
607 &tmp_tm);
608
609 if (str && *str) {
610 /* Translators: This is followed by an event's start date/time */
611 g_string_append (tmp2, _("Start: "));
612 g_string_append (tmp2, str);
613 }
614
615 g_free (str);
616 }
617
618 if (dtdue && e_cal_component_datetime_get_value (dtdue)) {
619 gchar *str;
620
621 tmp_tm = e_cal_util_icaltime_to_tm_with_zone (e_cal_component_datetime_get_value (dtdue), zone, default_zone);
622 str = e_datetime_format_format_tm ("calendar", "table",
623 i_cal_time_is_date (e_cal_component_datetime_get_value (dtdue)) ? DTFormatKindDate : DTFormatKindDateTime,
624 &tmp_tm);
625
626 if (str && *str) {
627 if (tmp2->len)
628 g_string_append (tmp2, "; ");
629
630 /* Translators: This is followed by an event's due date/time */
631 g_string_append (tmp2, _("Due: "));
632 g_string_append (tmp2, str);
633 }
634
635 g_free (str);
636 }
637
638 if (tmp2->len) {
639 l = gtk_label_new (tmp2->str);
640 gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5);
641 gtk_box_pack_start (GTK_BOX (w), l, FALSE, FALSE, 0);
642
643 gtk_widget_override_color (l, GTK_STATE_FLAG_NORMAL, &norm_text);
644 }
645
646 e_cal_component_datetime_free (dtstart);
647 e_cal_component_datetime_free (dtdue);
648
649 g_string_set_size (tmp2, 0);
650 desc = e_cal_component_get_descriptions (new_comp);
651 for (len = 0, p = desc; p != NULL; p = p->next) {
652 ECalComponentText *text = p->data;
653
654 if (text && e_cal_component_text_get_value (text)) {
655 const gchar *value = e_cal_component_text_get_value (text);
656 len += strlen (value);
657 g_string_append (tmp2, value);
658 if (len > 1024) {
659 g_string_set_size (tmp2, 1020);
660 g_string_append (tmp2, _("…"));
661 break;
662 }
663 }
664 }
665 g_slist_free_full (desc, e_cal_component_text_free);
666
667 if (tmp2->len) {
668 l = gtk_label_new (tmp2->str);
669 gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5);
670 gtk_box_pack_start (GTK_BOX (w), l, FALSE, FALSE, 0);
671
672 gtk_widget_override_color (l, GTK_STATE_FLAG_NORMAL, &norm_text);
673 }
674
675 g_string_free (tmp2, TRUE);
676
677 gtk_widget_show_all (box);
678 gtk_tooltip_set_custom (tooltip, box);
679
680 g_object_unref (new_comp);
681
682 if (esm && esm->sorter && e_sorter_needs_sorting (esm->sorter))
683 row = e_sorter_model_to_sorted (esm->sorter, row);
684
685 e_table_get_cell_geometry (E_TABLE (memo_table), row, 0, NULL, &row_y, NULL, &row_height);
686
687 if (row_y != -1 && row_height != -1) {
688 ETable *etable;
689 GdkRectangle rect;
690 GtkAllocation allocation;
691
692 etable = E_TABLE (memo_table);
693
694 if (etable && etable->table_canvas) {
695 gtk_widget_get_allocation (GTK_WIDGET (etable->table_canvas), &allocation);
696 } else {
697 allocation.x = 0;
698 allocation.y = 0;
699 allocation.width = 0;
700 allocation.height = 0;
701 }
702
703 rect.x = allocation.x;
704 rect.y = allocation.y + row_y - BUTTON_PADDING;
705 rect.width = allocation.width;
706 rect.height = row_height + 2 * BUTTON_PADDING;
707
708 if (etable && etable->header_canvas) {
709 gtk_widget_get_allocation (GTK_WIDGET (etable->header_canvas), &allocation);
710
711 rect.y += allocation.height;
712 }
713
714 gtk_tooltip_set_tip_area (tooltip, &rect);
715 }
716
717 return TRUE;
718 }
719
720 static void
memo_table_open_at_row(ETable * table,gint row)721 memo_table_open_at_row (ETable *table,
722 gint row)
723 {
724 EMemoTable *memo_table;
725 ECalModel *model;
726 ECalModelComponent *comp_data;
727
728 memo_table = E_MEMO_TABLE (table);
729 model = e_memo_table_get_model (memo_table);
730 comp_data = e_cal_model_get_component_at (model, row);
731 memo_table_emit_open_component (memo_table, comp_data);
732 }
733
734 static void
memo_table_double_click(ETable * table,gint row,gint col,GdkEvent * event)735 memo_table_double_click (ETable *table,
736 gint row,
737 gint col,
738 GdkEvent *event)
739 {
740 memo_table_open_at_row (table, row);
741 }
742
743 static gint
memo_table_right_click(ETable * table,gint row,gint col,GdkEvent * event)744 memo_table_right_click (ETable *table,
745 gint row,
746 gint col,
747 GdkEvent *event)
748 {
749 EMemoTable *memo_table;
750
751 memo_table = E_MEMO_TABLE (table);
752 memo_table_emit_popup_event (memo_table, event);
753
754 return TRUE;
755 }
756
757 static gboolean
memo_table_key_press(ETable * table,gint row,gint col,GdkEvent * event)758 memo_table_key_press (ETable *table,
759 gint row,
760 gint col,
761 GdkEvent *event)
762 {
763 if (event && event->type == GDK_KEY_PRESS &&
764 (event->key.keyval == GDK_KEY_Return || event->key.keyval == GDK_KEY_KP_Enter) &&
765 (event->key.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK)) == 0 &&
766 !e_table_is_editing (table)) {
767 memo_table_open_at_row (table, row);
768 return TRUE;
769 }
770
771 return FALSE;
772 }
773
774 static gboolean
memo_table_white_space_event(ETable * table,GdkEvent * event)775 memo_table_white_space_event (ETable *table,
776 GdkEvent *event)
777 {
778 guint event_button = 0;
779
780 g_return_val_if_fail (E_IS_MEMO_TABLE (table), FALSE);
781 g_return_val_if_fail (event != NULL, FALSE);
782
783 if (event->type == GDK_BUTTON_PRESS &&
784 gdk_event_get_button (event, &event_button) &&
785 event_button == 3) {
786 GtkWidget *table_canvas;
787
788 table_canvas = GTK_WIDGET (table->table_canvas);
789
790 if (!gtk_widget_has_focus (table_canvas))
791 gtk_widget_grab_focus (table_canvas);
792
793 memo_table_emit_popup_event (E_MEMO_TABLE (table), event);
794
795 return TRUE;
796 }
797
798 return FALSE;
799 }
800
801 static void
memo_table_update_actions(ESelectable * selectable,EFocusTracker * focus_tracker,GdkAtom * clipboard_targets,gint n_clipboard_targets)802 memo_table_update_actions (ESelectable *selectable,
803 EFocusTracker *focus_tracker,
804 GdkAtom *clipboard_targets,
805 gint n_clipboard_targets)
806 {
807 EMemoTable *memo_table;
808 GtkAction *action;
809 GtkTargetList *target_list;
810 GSList *list, *iter;
811 gboolean can_paste = FALSE;
812 gboolean sources_are_editable = TRUE;
813 gboolean is_editing;
814 gboolean sensitive;
815 const gchar *tooltip;
816 gint n_selected;
817 gint ii;
818
819 memo_table = E_MEMO_TABLE (selectable);
820 n_selected = e_table_selected_count (E_TABLE (memo_table));
821 is_editing = e_table_is_editing (E_TABLE (memo_table));
822
823 list = e_memo_table_get_selected (memo_table);
824 for (iter = list; iter != NULL && sources_are_editable; iter = iter->next) {
825 ECalModelComponent *comp_data = iter->data;
826
827 sources_are_editable = sources_are_editable &&
828 !e_client_is_readonly (E_CLIENT (comp_data->client));
829 }
830 g_slist_free (list);
831
832 target_list = e_selectable_get_paste_target_list (selectable);
833 for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
834 can_paste = gtk_target_list_find (
835 target_list, clipboard_targets[ii], NULL);
836
837 action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
838 sensitive = (n_selected > 0) && sources_are_editable && !is_editing;
839 tooltip = _("Cut selected memos to the clipboard");
840 gtk_action_set_sensitive (action, sensitive);
841 gtk_action_set_tooltip (action, tooltip);
842
843 action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
844 sensitive = (n_selected > 0) && !is_editing;
845 tooltip = _("Copy selected memos to the clipboard");
846 gtk_action_set_sensitive (action, sensitive);
847 gtk_action_set_tooltip (action, tooltip);
848
849 action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
850 sensitive = sources_are_editable && can_paste && !is_editing;
851 tooltip = _("Paste memos from the clipboard");
852 gtk_action_set_sensitive (action, sensitive);
853 gtk_action_set_tooltip (action, tooltip);
854
855 action = e_focus_tracker_get_delete_selection_action (focus_tracker);
856 sensitive = (n_selected > 0) && sources_are_editable && !is_editing;
857 tooltip = _("Delete selected memos");
858 gtk_action_set_sensitive (action, sensitive);
859 gtk_action_set_tooltip (action, tooltip);
860
861 action = e_focus_tracker_get_select_all_action (focus_tracker);
862 sensitive = TRUE;
863 tooltip = _("Select all visible memos");
864 gtk_action_set_sensitive (action, sensitive);
865 gtk_action_set_tooltip (action, tooltip);
866 }
867
868 static void
memo_table_cut_clipboard(ESelectable * selectable)869 memo_table_cut_clipboard (ESelectable *selectable)
870 {
871 EMemoTable *memo_table;
872
873 memo_table = E_MEMO_TABLE (selectable);
874
875 e_selectable_copy_clipboard (selectable);
876 delete_selected_components (memo_table);
877 }
878
879 /* Helper for memo_table_copy_clipboard() */
880 static void
copy_row_cb(gint model_row,gpointer data)881 copy_row_cb (gint model_row,
882 gpointer data)
883 {
884 EMemoTable *memo_table;
885 ECalModelComponent *comp_data;
886 ECalModel *model;
887 ICalComponent *child;
888
889 memo_table = E_MEMO_TABLE (data);
890
891 g_return_if_fail (memo_table->priv->tmp_vcal != NULL);
892
893 model = e_memo_table_get_model (memo_table);
894 comp_data = e_cal_model_get_component_at (model, model_row);
895 if (comp_data == NULL)
896 return;
897
898 /* Add timezones to the VCALENDAR component. */
899 e_cal_util_add_timezones_from_component (
900 memo_table->priv->tmp_vcal, comp_data->icalcomp);
901
902 /* Add the new component to the VCALENDAR component. */
903 child = i_cal_component_clone (comp_data->icalcomp);
904 if (child)
905 i_cal_component_take_component (memo_table->priv->tmp_vcal, child);
906 }
907
908 static void
memo_table_copy_clipboard(ESelectable * selectable)909 memo_table_copy_clipboard (ESelectable *selectable)
910 {
911 EMemoTable *memo_table;
912 GtkClipboard *clipboard;
913 gchar *comp_str;
914
915 memo_table = E_MEMO_TABLE (selectable);
916
917 /* Create a temporary VCALENDAR object. */
918 memo_table->priv->tmp_vcal = e_cal_util_new_top_level ();
919
920 e_table_selected_row_foreach (
921 E_TABLE (memo_table), copy_row_cb, memo_table);
922 comp_str = i_cal_component_as_ical_string (memo_table->priv->tmp_vcal);
923
924 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
925 e_clipboard_set_calendar (clipboard, comp_str, -1);
926 gtk_clipboard_store (clipboard);
927
928 g_free (comp_str);
929
930 g_clear_object (&memo_table->priv->tmp_vcal);
931 }
932
933 static void
memo_table_paste_clipboard(ESelectable * selectable)934 memo_table_paste_clipboard (ESelectable *selectable)
935 {
936 EMemoTable *memo_table;
937 GtkClipboard *clipboard;
938 GnomeCanvasItem *item;
939 GnomeCanvas *table_canvas;
940
941 memo_table = E_MEMO_TABLE (selectable);
942
943 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
944
945 table_canvas = E_TABLE (memo_table)->table_canvas;
946 item = table_canvas->focused_item;
947
948 /* XXX Should ECellText implement GtkEditable? */
949
950 /* Paste text into a cell being edited. */
951 if (gtk_clipboard_wait_is_text_available (clipboard) &&
952 gtk_widget_has_focus (GTK_WIDGET (table_canvas)) &&
953 E_IS_TABLE_ITEM (item) &&
954 E_TABLE_ITEM (item)->editing_col >= 0 &&
955 E_TABLE_ITEM (item)->editing_row >= 0) {
956
957 ETableItem *etable_item = E_TABLE_ITEM (item);
958
959 e_cell_text_paste_clipboard (
960 etable_item->cell_views[etable_item->editing_col],
961 etable_item->editing_col,
962 etable_item->editing_row);
963
964 /* Paste iCalendar data into the table. */
965 } else if (e_clipboard_wait_is_calendar_available (clipboard)) {
966 ECalModel *model;
967 gchar *ical_str;
968
969 model = e_memo_table_get_model (memo_table);
970
971 ical_str = e_clipboard_wait_for_calendar (clipboard);
972 e_cal_ops_paste_components (model, ical_str);
973 g_free (ical_str);
974 }
975 }
976
977 /* Used from e_table_selected_row_foreach(); puts the selected row number in an
978 * gint pointed to by the closure data.
979 */
980 static void
get_selected_row_cb(gint model_row,gpointer data)981 get_selected_row_cb (gint model_row,
982 gpointer data)
983 {
984 gint *row;
985
986 row = data;
987 *row = model_row;
988 }
989
990 /*
991 * Returns the component that is selected in the table; only works if there is
992 * one and only one selected row.
993 */
994 static ECalModelComponent *
get_selected_comp(EMemoTable * memo_table)995 get_selected_comp (EMemoTable *memo_table)
996 {
997 ECalModel *model;
998 gint row;
999
1000 model = e_memo_table_get_model (memo_table);
1001 if (e_table_selected_count (E_TABLE (memo_table)) != 1)
1002 return NULL;
1003
1004 row = -1;
1005 e_table_selected_row_foreach (
1006 E_TABLE (memo_table), get_selected_row_cb, &row);
1007 if (row < 0) {
1008 g_warn_if_reached ();
1009 return NULL;
1010 }
1011
1012 return e_cal_model_get_component_at (model, row);
1013 }
1014
1015 static void
memo_table_delete_selection(ESelectable * selectable)1016 memo_table_delete_selection (ESelectable *selectable)
1017 {
1018 ECalModel *model;
1019 EMemoTable *memo_table;
1020 ECalComponent *comp = NULL;
1021 ECalModelComponent *comp_data;
1022 gboolean delete = TRUE;
1023 gint n_selected;
1024
1025 memo_table = E_MEMO_TABLE (selectable);
1026 model = e_memo_table_get_model (memo_table);
1027
1028 n_selected = e_table_selected_count (E_TABLE (memo_table));
1029 if (n_selected <= 0)
1030 return;
1031
1032 if (n_selected == 1)
1033 comp_data = get_selected_comp (memo_table);
1034 else
1035 comp_data = NULL;
1036
1037 /* FIXME: this may be something other than a TODO component */
1038
1039 if (comp_data) {
1040 comp = e_cal_component_new_from_icalcomponent (
1041 i_cal_component_clone (comp_data->icalcomp));
1042 }
1043
1044 if (e_cal_model_get_confirm_delete (model))
1045 delete = e_cal_dialogs_delete_component (
1046 comp, FALSE, n_selected,
1047 E_CAL_COMPONENT_JOURNAL,
1048 GTK_WIDGET (memo_table));
1049
1050 if (delete)
1051 delete_selected_components (memo_table);
1052
1053 g_clear_object (&comp);
1054 }
1055
1056 static void
memo_table_select_all(ESelectable * selectable)1057 memo_table_select_all (ESelectable *selectable)
1058 {
1059 e_table_select_all (E_TABLE (selectable));
1060 }
1061
1062 static void
e_memo_table_class_init(EMemoTableClass * class)1063 e_memo_table_class_init (EMemoTableClass *class)
1064 {
1065 GObjectClass *object_class;
1066 GtkWidgetClass *widget_class;
1067 ETableClass *table_class;
1068
1069 g_type_class_add_private (class, sizeof (EMemoTablePrivate));
1070
1071 object_class = G_OBJECT_CLASS (class);
1072 object_class->set_property = memo_table_set_property;
1073 object_class->get_property = memo_table_get_property;
1074 object_class->dispose = memo_table_dispose;
1075 object_class->constructed = memo_table_constructed;
1076
1077 widget_class = GTK_WIDGET_CLASS (class);
1078 widget_class->popup_menu = memo_table_popup_menu;
1079 widget_class->query_tooltip = memo_table_query_tooltip;
1080
1081 table_class = E_TABLE_CLASS (class);
1082 table_class->double_click = memo_table_double_click;
1083 table_class->right_click = memo_table_right_click;
1084 table_class->key_press = memo_table_key_press;
1085 table_class->white_space_event = memo_table_white_space_event;
1086
1087 /* Inherited from ESelectableInterface */
1088 g_object_class_override_property (
1089 object_class,
1090 PROP_COPY_TARGET_LIST,
1091 "copy-target-list");
1092
1093 g_object_class_install_property (
1094 object_class,
1095 PROP_MODEL,
1096 g_param_spec_object (
1097 "model",
1098 "Model",
1099 NULL,
1100 E_TYPE_CAL_MODEL,
1101 G_PARAM_READWRITE |
1102 G_PARAM_CONSTRUCT_ONLY));
1103
1104 /* Inherited from ESelectableInterface */
1105 g_object_class_override_property (
1106 object_class,
1107 PROP_PASTE_TARGET_LIST,
1108 "paste-target-list");
1109
1110 g_object_class_install_property (
1111 object_class,
1112 PROP_SHELL_VIEW,
1113 g_param_spec_object (
1114 "shell-view",
1115 "Shell View",
1116 NULL,
1117 E_TYPE_SHELL_VIEW,
1118 G_PARAM_READWRITE |
1119 G_PARAM_CONSTRUCT_ONLY));
1120
1121 signals[OPEN_COMPONENT] = g_signal_new (
1122 "open-component",
1123 G_TYPE_FROM_CLASS (class),
1124 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1125 G_STRUCT_OFFSET (EMemoTableClass, open_component),
1126 NULL, NULL,
1127 g_cclosure_marshal_VOID__OBJECT,
1128 G_TYPE_NONE, 1,
1129 E_TYPE_CAL_MODEL_COMPONENT);
1130
1131 signals[POPUP_EVENT] = g_signal_new (
1132 "popup-event",
1133 G_TYPE_FROM_CLASS (class),
1134 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1135 G_STRUCT_OFFSET (EMemoTableClass, popup_event),
1136 NULL, NULL,
1137 g_cclosure_marshal_VOID__BOXED,
1138 G_TYPE_NONE, 1,
1139 GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1140 }
1141
1142 static void
e_memo_table_init(EMemoTable * memo_table)1143 e_memo_table_init (EMemoTable *memo_table)
1144 {
1145 GtkTargetList *target_list;
1146
1147 memo_table->priv = E_MEMO_TABLE_GET_PRIVATE (memo_table);
1148
1149 target_list = gtk_target_list_new (NULL, 0);
1150 e_target_list_add_calendar_targets (target_list, 0);
1151 memo_table->priv->copy_target_list = target_list;
1152
1153 target_list = gtk_target_list_new (NULL, 0);
1154 e_target_list_add_calendar_targets (target_list, 0);
1155 memo_table->priv->paste_target_list = target_list;
1156 }
1157
1158 static void
e_memo_table_selectable_init(ESelectableInterface * iface)1159 e_memo_table_selectable_init (ESelectableInterface *iface)
1160 {
1161 iface->update_actions = memo_table_update_actions;
1162 iface->cut_clipboard = memo_table_cut_clipboard;
1163 iface->copy_clipboard = memo_table_copy_clipboard;
1164 iface->paste_clipboard = memo_table_paste_clipboard;
1165 iface->delete_selection = memo_table_delete_selection;
1166 iface->select_all = memo_table_select_all;
1167 }
1168
1169 /**
1170 * e_memo_table_new:
1171 * @shell_view: an #EShellView
1172 * @model: an #ECalModel for the table
1173 *
1174 * Returns a new #EMemoTable.
1175 *
1176 * Returns: a new #EMemoTable
1177 **/
1178 GtkWidget *
e_memo_table_new(EShellView * shell_view,ECalModel * model)1179 e_memo_table_new (EShellView *shell_view,
1180 ECalModel *model)
1181 {
1182 g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
1183 g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
1184
1185 return g_object_new (
1186 E_TYPE_MEMO_TABLE,
1187 "model", model, "shell-view", shell_view, NULL);
1188 }
1189
1190 /**
1191 * e_memo_table_get_model:
1192 * @memo_table: A calendar table.
1193 *
1194 * Queries the calendar data model that a calendar table is using.
1195 *
1196 * Return value: A memo model.
1197 **/
1198 ECalModel *
e_memo_table_get_model(EMemoTable * memo_table)1199 e_memo_table_get_model (EMemoTable *memo_table)
1200 {
1201 g_return_val_if_fail (memo_table != NULL, NULL);
1202 g_return_val_if_fail (E_IS_MEMO_TABLE (memo_table), NULL);
1203
1204 return memo_table->priv->model;
1205 }
1206
1207 EShellView *
e_memo_table_get_shell_view(EMemoTable * memo_table)1208 e_memo_table_get_shell_view (EMemoTable *memo_table)
1209 {
1210 g_return_val_if_fail (E_IS_MEMO_TABLE (memo_table), NULL);
1211
1212 return memo_table->priv->shell_view;
1213 }
1214
1215 struct get_selected_uids_closure {
1216 EMemoTable *memo_table;
1217 GSList *objects;
1218 };
1219
1220 /* Used from e_table_selected_row_foreach(), builds a list of the selected UIDs */
1221 static void
add_uid_cb(gint model_row,gpointer data)1222 add_uid_cb (gint model_row,
1223 gpointer data)
1224 {
1225 struct get_selected_uids_closure *closure;
1226 ECalModelComponent *comp_data;
1227 ECalModel *model;
1228
1229 closure = data;
1230
1231 model = e_memo_table_get_model (closure->memo_table);
1232 comp_data = e_cal_model_get_component_at (model, model_row);
1233
1234 closure->objects = g_slist_prepend (closure->objects, comp_data);
1235 }
1236
1237 /**
1238 * e_memo_table_get_selected:
1239 * @memo_table:
1240 *
1241 * Get the currently selected ECalModelComponent's on the table.
1242 *
1243 * Return value: A GSList of the components, which should be
1244 * g_slist_free'd when finished with.
1245 **/
1246 GSList *
e_memo_table_get_selected(EMemoTable * memo_table)1247 e_memo_table_get_selected (EMemoTable *memo_table)
1248 {
1249 struct get_selected_uids_closure closure;
1250
1251 closure.memo_table = memo_table;
1252 closure.objects = NULL;
1253
1254 e_table_selected_row_foreach (
1255 E_TABLE (memo_table), add_uid_cb, &closure);
1256
1257 return closure.objects;
1258 }
1259
1260 GtkTargetList *
e_memo_table_get_copy_target_list(EMemoTable * memo_table)1261 e_memo_table_get_copy_target_list (EMemoTable *memo_table)
1262 {
1263 g_return_val_if_fail (E_IS_MEMO_TABLE (memo_table), NULL);
1264
1265 return memo_table->priv->copy_target_list;
1266 }
1267
1268 GtkTargetList *
e_memo_table_get_paste_target_list(EMemoTable * memo_table)1269 e_memo_table_get_paste_target_list (EMemoTable *memo_table)
1270 {
1271 g_return_val_if_fail (E_IS_MEMO_TABLE (memo_table), NULL);
1272
1273 return memo_table->priv->paste_target_list;
1274 }
1275