Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-comp-editor.c
blob8a43cbc43a93bd64bb9e721db518227464b5e143
1 /*
2 * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com)
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by
6 * the Free Software Foundation.
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.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "evolution-config.h"
20 #include <glib/gi18n-lib.h>
21 #include <gtk/gtk.h>
23 #include <libedataserver/libedataserver.h>
24 #include <libecal/libecal.h>
25 #include <e-util/e-util.h>
27 #include "calendar-config.h"
28 #include "comp-util.h"
29 #include "e-cal-dialogs.h"
30 #include "itip-utils.h"
31 #include "print.h"
33 #include "e-comp-editor-page-general.h"
34 #include "e-comp-editor-page-attachments.h"
35 #include "e-comp-editor-event.h"
36 #include "e-comp-editor-memo.h"
37 #include "e-comp-editor-task.h"
39 #include "e-comp-editor.h"
41 struct _ECompEditorPrivate {
42 EAlertBar *alert_bar; /* not referenced */
43 EActivityBar *activity_bar; /* not referenced */
44 GtkNotebook *content; /* not referenced */
46 EAlert *validation_alert;
48 EShell *shell;
49 GSettings *calendar_settings;
50 ESource *origin_source;
51 icalcomponent *component;
52 guint32 flags;
54 EFocusTracker *focus_tracker;
55 GtkUIManager *ui_manager;
57 GSList *pages; /* ECompEditorPage * */
58 gulong show_attendees_handler_id;
60 ECompEditorPageGeneral *page_general; /* special page, can be added only once; not referenced */
62 EActivity *target_client_opening;
64 ECalClient *source_client;
65 ECalClient *target_client;
66 gchar *cal_email_address;
67 gchar *alarm_email_address;
68 gboolean changed;
69 guint updating;
70 gchar *title_suffix;
72 ECompEditorPropertyPart *dtstart_part;
73 ECompEditorPropertyPart *dtend_part;
75 GtkWidget *restore_focus;
78 enum {
79 PROP_0,
80 PROP_ALARM_EMAIL_ADDRESS,
81 PROP_CAL_EMAIL_ADDRESS,
82 PROP_CHANGED,
83 PROP_COMPONENT,
84 PROP_FLAGS,
85 PROP_ORIGIN_SOURCE,
86 PROP_SHELL,
87 PROP_SOURCE_CLIENT,
88 PROP_TARGET_CLIENT,
89 PROP_TITLE_SUFFIX
92 enum {
93 TIMES_CHANGED,
94 OBJECT_CREATED,
95 EDITOR_CLOSED,
96 LAST_SIGNAL
99 static guint signals[LAST_SIGNAL];
101 static GSList *opened_editors = NULL;
103 static void e_comp_editor_alert_sink_iface_init (EAlertSinkInterface *iface);
105 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ECompEditor, e_comp_editor, GTK_TYPE_WINDOW,
106 G_IMPLEMENT_INTERFACE (E_TYPE_ALERT_SINK, e_comp_editor_alert_sink_iface_init)
107 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
109 static void
110 ece_restore_focus (ECompEditor *comp_editor)
112 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
114 if (comp_editor->priv->restore_focus) {
115 gtk_widget_grab_focus (comp_editor->priv->restore_focus);
117 if (GTK_IS_ENTRY (comp_editor->priv->restore_focus))
118 gtk_editable_set_position (GTK_EDITABLE (comp_editor->priv->restore_focus), 0);
120 comp_editor->priv->restore_focus = NULL;
124 static void
125 e_comp_editor_enable (ECompEditor *comp_editor,
126 gboolean enable)
128 GtkActionGroup *group;
129 GtkWidget *current_focus;
131 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
133 current_focus = gtk_window_get_focus (GTK_WINDOW (comp_editor));
135 gtk_widget_set_sensitive (GTK_WIDGET (comp_editor->priv->content), enable);
137 group = e_comp_editor_get_action_group (comp_editor, "individual");
138 gtk_action_group_set_sensitive (group, enable);
140 group = e_comp_editor_get_action_group (comp_editor, "core");
141 gtk_action_group_set_sensitive (group, enable);
143 group = e_comp_editor_get_action_group (comp_editor, "editable");
144 gtk_action_group_set_sensitive (group, enable);
146 if (enable) {
147 e_comp_editor_sensitize_widgets (comp_editor);
148 ece_restore_focus (comp_editor);
149 } else {
150 comp_editor->priv->restore_focus = current_focus;
154 static void
155 ece_set_attendees_for_delegation (ECalComponent *comp,
156 const gchar *address)
158 icalproperty *prop;
159 icalparameter *param;
160 icalcomponent *icalcomp;
161 gboolean again;
163 icalcomp = e_cal_component_get_icalcomponent (comp);
165 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY);
166 prop;
167 prop = again ? icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY) :
168 icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) {
169 const gchar *attendee = icalproperty_get_attendee (prop);
170 const gchar *delfrom = NULL;
172 again = FALSE;
173 param = icalproperty_get_first_parameter (prop, ICAL_DELEGATEDFROM_PARAMETER);
174 if (param)
175 delfrom = icalparameter_get_delegatedfrom (param);
176 if (!(g_str_equal (itip_strip_mailto (attendee), address) ||
177 ((delfrom && *delfrom) && g_str_equal (itip_strip_mailto (delfrom), address)))) {
178 icalcomponent_remove_property (icalcomp, prop);
179 icalproperty_free (prop);
180 again = TRUE;
186 /* Utility function to get the mime-attachment list from the attachment
187 * bar for sending the comp via itip. The list and its contents must
188 * be freed by the caller.
190 static GSList *
191 ece_get_mime_attach_list (ECompEditor *comp_editor)
193 ECompEditorPage *page_attachments;
194 EAttachmentStore *store;
195 GtkTreeModel *model;
196 GtkTreeIter iter;
197 struct CalMimeAttach *cal_mime_attach;
198 GSList *attach_list = NULL;
199 gboolean valid;
201 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
203 page_attachments = e_comp_editor_get_page (comp_editor, E_TYPE_COMP_EDITOR_PAGE_ATTACHMENTS);
204 if (!page_attachments)
205 return NULL;
207 store = e_comp_editor_page_attachments_get_store (E_COMP_EDITOR_PAGE_ATTACHMENTS (page_attachments));
208 if (!store)
209 return NULL;
211 model = GTK_TREE_MODEL (store);
212 valid = gtk_tree_model_get_iter_first (model, &iter);
214 while (valid) {
215 EAttachment *attachment;
216 CamelDataWrapper *wrapper;
217 CamelMimePart *mime_part;
218 CamelStream *stream;
219 GByteArray *byte_array;
220 guchar *buffer = NULL;
221 const gchar *description;
222 const gchar *disposition;
223 gint column_id;
225 column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
226 gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
227 mime_part = e_attachment_ref_mime_part (attachment);
228 g_object_unref (attachment);
230 valid = gtk_tree_model_iter_next (model, &iter);
232 if (mime_part == NULL)
233 continue;
235 cal_mime_attach = g_malloc0 (sizeof (struct CalMimeAttach));
236 wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
238 byte_array = g_byte_array_new ();
239 stream = camel_stream_mem_new_with_byte_array (byte_array);
241 camel_data_wrapper_decode_to_stream_sync (
242 wrapper, stream, NULL, NULL);
243 buffer = g_memdup (byte_array->data, byte_array->len);
245 camel_mime_part_set_content_id (mime_part, NULL);
247 cal_mime_attach->encoded_data = (gchar *) buffer;
248 cal_mime_attach->length = byte_array->len;
249 cal_mime_attach->filename =
250 g_strdup (camel_mime_part_get_filename (mime_part));
251 description = camel_mime_part_get_description (mime_part);
252 if (description == NULL || *description == '\0')
253 description = _("attachment");
254 cal_mime_attach->description = g_strdup (description);
255 cal_mime_attach->content_type = g_strdup (
256 camel_data_wrapper_get_mime_type (wrapper));
257 cal_mime_attach->content_id = g_strdup (
258 camel_mime_part_get_content_id (mime_part));
260 disposition = camel_mime_part_get_disposition (mime_part);
261 cal_mime_attach->disposition =
262 (disposition != NULL) &&
263 (g_ascii_strcasecmp (disposition, "inline") == 0);
265 attach_list = g_slist_append (attach_list, cal_mime_attach);
267 g_object_unref (mime_part);
268 g_object_unref (stream);
272 return attach_list;
275 static void
276 e_comp_editor_set_component (ECompEditor *comp_editor,
277 const icalcomponent *component)
279 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
280 g_return_if_fail (component != NULL);
282 if (comp_editor->priv->component)
283 icalcomponent_free (comp_editor->priv->component);
284 comp_editor->priv->component = icalcomponent_new_clone ((icalcomponent *) component);
286 g_warn_if_fail (comp_editor->priv->component != NULL);
289 typedef struct _SaveData {
290 ECompEditor *comp_editor;
291 ECalClient *source_client;
292 ECalClient *target_client;
293 icalcomponent *component;
294 gboolean with_send;
295 gboolean close_after_save;
296 ECalObjModType recur_mod;
297 gboolean success;
298 GError *error;
299 gchar *alert_ident;
300 gchar *alert_arg_0;
302 gboolean object_created;
303 ECalComponentItipMethod first_send;
304 ECalComponentItipMethod second_send;
305 ECalComponent *send_comp;
306 EActivity *send_activity;
307 gboolean strip_alarms;
308 gboolean only_new_attendees;
309 GSList *mime_attach_list;
310 } SaveData;
312 static void
313 save_data_free (SaveData *sd)
315 if (sd) {
316 e_comp_editor_enable (sd->comp_editor, TRUE);
318 if (sd->success) {
319 if (sd->close_after_save) {
320 g_signal_emit (sd->comp_editor, signals[EDITOR_CLOSED], 0, TRUE, NULL);
321 gtk_widget_destroy (GTK_WIDGET (sd->comp_editor));
322 } else {
323 e_comp_editor_set_component (sd->comp_editor, sd->component);
325 e_comp_editor_fill_widgets (sd->comp_editor, sd->component);
327 g_clear_object (&sd->comp_editor->priv->source_client);
328 sd->comp_editor->priv->source_client = g_object_ref (sd->target_client);
330 sd->comp_editor->priv->flags = sd->comp_editor->priv->flags & (~E_COMP_EDITOR_FLAG_IS_NEW);
332 e_comp_editor_sensitize_widgets (sd->comp_editor);
333 e_comp_editor_set_changed (sd->comp_editor, FALSE);
335 } else if (sd->alert_ident) {
336 e_alert_submit (
337 E_ALERT_SINK (sd->comp_editor), sd->alert_ident, sd->alert_arg_0,
338 sd->error ? sd->error->message : _("Unknown error"), NULL);
341 if (sd->send_activity && e_activity_get_state (sd->send_activity) != E_ACTIVITY_CANCELLED)
342 e_activity_set_state (sd->send_activity, E_ACTIVITY_COMPLETED);
344 g_clear_object (&sd->comp_editor);
345 g_clear_object (&sd->source_client);
346 g_clear_object (&sd->target_client);
347 g_clear_object (&sd->send_comp);
348 g_clear_object (&sd->send_activity);
349 g_clear_error (&sd->error);
350 if (sd->component)
351 icalcomponent_free (sd->component);
352 g_slist_free_full (sd->mime_attach_list, itip_cal_mime_attach_free);
353 g_free (sd->alert_ident);
354 g_free (sd->alert_arg_0);
355 g_free (sd);
359 static gboolean
360 ece_send_process_method (SaveData *sd,
361 ECalComponentItipMethod send_method,
362 ECalComponent *send_comp,
363 ESourceRegistry *registry,
364 GCancellable *cancellable,
365 GAsyncReadyCallback callback,
366 gpointer user_data)
368 GSList *mime_attach_list = NULL;
370 g_return_val_if_fail (sd != NULL, FALSE);
371 g_return_val_if_fail (E_IS_CAL_COMPONENT (send_comp), FALSE);
372 g_return_val_if_fail (send_method != E_CAL_COMPONENT_METHOD_NONE, FALSE);
374 if (e_cal_component_has_attachments (send_comp) &&
375 e_client_check_capability (E_CLIENT (sd->target_client), CAL_STATIC_CAPABILITY_CREATE_MESSAGES)) {
376 /* Clone the component with attachments set to CID:... */
377 GSList *attach_list = NULL;
378 GSList *attach;
380 /* mime_attach_list is freed by itip_send_component() */
381 mime_attach_list = sd->mime_attach_list;
382 sd->mime_attach_list = NULL;
384 for (attach = mime_attach_list; attach; attach = attach->next) {
385 struct CalMimeAttach *cma = (struct CalMimeAttach *) attach->data;
387 attach_list = g_slist_append (
388 attach_list, g_strconcat (
389 "cid:", cma->content_id, NULL));
392 if (attach_list) {
393 e_cal_component_set_attachment_list (send_comp, attach_list);
395 g_slist_free_full (attach_list, g_free);
399 itip_send_component (
400 registry, send_method, send_comp, sd->target_client,
401 NULL, mime_attach_list, NULL, sd->strip_alarms,
402 sd->only_new_attendees, FALSE,
403 cancellable, callback, user_data);
405 return TRUE;
408 static void
409 ecep_second_send_processed_cb (GObject *source_object,
410 GAsyncResult *result,
411 gpointer user_data)
413 SaveData *sd = user_data;
415 g_return_if_fail (sd != NULL);
417 sd->success = itip_send_component_finish (result, &sd->error);
419 save_data_free (sd);
422 static void
423 ecep_first_send_processed_cb (GObject *source_object,
424 GAsyncResult *result,
425 gpointer user_data)
427 SaveData *sd = user_data;
429 g_return_if_fail (sd != NULL);
431 sd->success = itip_send_component_finish (result, &sd->error);
432 if (!sd->success || sd->second_send == E_CAL_COMPONENT_METHOD_NONE) {
433 save_data_free (sd);
434 } else {
435 sd->success = ece_send_process_method (sd, sd->second_send, sd->send_comp,
436 e_shell_get_registry (sd->comp_editor->priv->shell),
437 e_activity_get_cancellable (sd->send_activity),
438 ecep_second_send_processed_cb, sd);
439 if (!sd->success)
440 save_data_free (sd);
444 static void
445 ece_prepare_send_component_done (gpointer ptr)
447 SaveData *sd = ptr;
449 g_return_if_fail (sd != NULL);
450 g_return_if_fail (E_IS_COMP_EDITOR (sd->comp_editor));
451 g_return_if_fail (sd->send_activity != NULL);
453 sd->success = ece_send_process_method (sd, sd->first_send, sd->send_comp,
454 e_shell_get_registry (sd->comp_editor->priv->shell),
455 e_activity_get_cancellable (sd->send_activity),
456 ecep_first_send_processed_cb, sd);
457 if (!sd->success)
458 save_data_free (sd);
461 static void
462 ece_prepare_send_component_thread (EAlertSinkThreadJobData *job_data,
463 gpointer user_data,
464 GCancellable *cancellable,
465 GError **error)
467 SaveData *sd = user_data;
468 const gchar *alert_ident;
469 ECalComponent *send_comp = NULL;
470 guint32 flags;
471 ESourceRegistry *registry;
473 g_return_if_fail (sd != NULL);
474 g_return_if_fail (E_IS_CAL_CLIENT (sd->target_client));
475 g_return_if_fail (sd->component != NULL);
477 while (!sd->send_activity) {
478 /* Give the main thread a chance to set this object
479 and give it a 50 milliseconds delay too */
480 g_thread_yield ();
481 g_usleep (50000);
484 switch (icalcomponent_isa (sd->component)) {
485 case ICAL_VEVENT_COMPONENT:
486 alert_ident = "calendar:failed-send-event";
487 break;
488 case ICAL_VJOURNAL_COMPONENT:
489 alert_ident = "calendar:failed-send-memo";
490 break;
491 case ICAL_VTODO_COMPONENT:
492 alert_ident = "calendar:failed-send-task";
493 break;
494 default:
495 g_warning ("%s: Cannot send component of kind %d", G_STRFUNC, icalcomponent_isa (sd->component));
496 sd->success = FALSE;
497 sd->alert_ident = g_strdup ("calendar:failed-send-event");
498 return;
501 g_free (sd->alert_ident);
502 sd->alert_ident = g_strdup (alert_ident);
504 e_alert_sink_thread_job_set_alert_ident (job_data, alert_ident);
506 flags = e_comp_editor_get_flags (sd->comp_editor);
507 registry = e_shell_get_registry (sd->comp_editor->priv->shell);
509 if (sd->recur_mod == E_CAL_OBJ_MOD_ALL && e_cal_component_is_instance (sd->send_comp)) {
510 /* Ensure we send the master object, not the instance only */
511 icalcomponent *icalcomp = NULL;
512 const gchar *uid = NULL;
514 e_cal_component_get_uid (sd->send_comp, &uid);
515 if (e_cal_client_get_object_sync (sd->target_client, uid, NULL, &icalcomp, cancellable, NULL) &&
516 icalcomp != NULL) {
517 send_comp = e_cal_component_new_from_icalcomponent (icalcomp);
521 if (!send_comp)
522 send_comp = e_cal_component_clone (sd->send_comp);
524 cal_comp_util_copy_new_attendees (send_comp, sd->send_comp);
526 /* The user updates the delegated status to the Organizer,
527 * so remove all other attendees. */
528 if ((flags & E_COMP_EDITOR_FLAG_DELEGATE) != 0) {
529 gchar *address;
531 address = itip_get_comp_attendee (registry, send_comp, sd->target_client);
533 if (address) {
534 ece_set_attendees_for_delegation (send_comp, address);
535 g_free (address);
539 g_clear_object (&sd->send_comp);
540 sd->send_comp = send_comp;
543 static void
544 ece_save_component_done (gpointer ptr)
546 SaveData *sd = ptr;
548 g_return_if_fail (sd != NULL);
549 g_return_if_fail (E_IS_COMP_EDITOR (sd->comp_editor));
551 if (sd->success) {
552 ECalComponent *comp;
553 gboolean delegated, is_new_meeting;
554 gboolean only_new_attendees = FALSE;
555 gboolean strip_alarms = TRUE;
556 guint32 flags;
558 if (sd->object_created)
559 g_signal_emit (sd->comp_editor, signals[OBJECT_CREATED], 0, NULL);
561 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (sd->component));
562 if (sd->comp_editor->priv->page_general) {
563 GSList *added_attendees;
565 added_attendees = e_comp_editor_page_general_get_added_attendees (sd->comp_editor->priv->page_general);
566 cal_comp_util_set_added_attendees_mails (comp, added_attendees);
569 flags = e_comp_editor_get_flags (sd->comp_editor);
570 is_new_meeting = (flags & E_COMP_EDITOR_FLAG_WITH_ATTENDEES) == 0 ||
571 (flags & E_COMP_EDITOR_FLAG_IS_NEW) != 0;
572 delegated = (flags & E_COMP_EDITOR_FLAG_DELEGATE) != 0 &&
573 e_cal_client_check_save_schedules (sd->target_client);
575 if (delegated || (sd->with_send && e_cal_dialogs_send_component (
576 GTK_WINDOW (sd->comp_editor), sd->target_client, comp,
577 is_new_meeting, &strip_alarms, &only_new_attendees))) {
578 ESourceRegistry *registry;
579 EActivity *activity;
581 registry = e_shell_get_registry (sd->comp_editor->priv->shell);
583 if (delegated)
584 only_new_attendees = FALSE;
586 if ((itip_organizer_is_user (registry, comp, sd->target_client) ||
587 itip_sentby_is_user (registry, comp, sd->target_client))) {
588 if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_JOURNAL)
589 sd->first_send = E_CAL_COMPONENT_METHOD_PUBLISH;
590 else
591 sd->first_send = E_CAL_COMPONENT_METHOD_REQUEST;
592 } else {
593 sd->first_send = E_CAL_COMPONENT_METHOD_REQUEST;
595 if ((flags & E_COMP_EDITOR_FLAG_DELEGATE) != 0)
596 sd->second_send = E_CAL_COMPONENT_METHOD_REPLY;
599 sd->mime_attach_list = ece_get_mime_attach_list (sd->comp_editor);
600 sd->strip_alarms = strip_alarms;
601 sd->only_new_attendees = only_new_attendees;
602 sd->send_comp = comp;
603 sd->success = FALSE;
604 sd->alert_ident = g_strdup ("calendar:failed-send-event");
605 sd->alert_arg_0 = e_util_get_source_full_name (registry, e_client_get_source (E_CLIENT (sd->target_client)));
607 activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (sd->comp_editor),
608 _("Sending notifications to attendees..."), sd->alert_ident, sd->alert_arg_0,
609 ece_prepare_send_component_thread, sd, ece_prepare_send_component_done);
611 if (activity)
612 e_activity_bar_set_activity (sd->comp_editor->priv->activity_bar, activity);
614 /* The thread is waiting for this to be set first */
615 sd->send_activity = activity;
617 return;
620 g_clear_object (&comp);
623 save_data_free (sd);
626 static gboolean
627 ece_save_component_attachments_sync (ECalClient *cal_client,
628 icalcomponent *component,
629 GCancellable *cancellable,
630 GError **error)
632 icalproperty *prop;
633 const gchar *local_store;
634 gchar *target_filename_prefix, *filename_prefix, *tmp;
635 gboolean success = TRUE;
637 g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), FALSE);
638 g_return_val_if_fail (component != NULL, FALSE);
640 tmp = g_strdup (icalcomponent_get_uid (component));
641 e_filename_make_safe (tmp);
642 filename_prefix = g_strconcat (tmp, "-", NULL);
643 g_free (tmp);
645 local_store = e_cal_client_get_local_attachment_store (cal_client);
646 if (local_store && *local_store &&
647 g_mkdir_with_parents (local_store, 0700) < 0) {
648 g_debug ("%s: Failed to create local store directory '%s'", G_STRFUNC, local_store);
651 target_filename_prefix = g_build_filename (local_store, filename_prefix, NULL);
653 g_free (filename_prefix);
655 for (prop = icalcomponent_get_first_property (component, ICAL_ATTACH_PROPERTY);
656 prop && success;
657 prop = icalcomponent_get_next_property (component, ICAL_ATTACH_PROPERTY)) {
658 icalattach *attach;
659 gchar *uri = NULL;
661 attach = icalproperty_get_attach (prop);
662 if (!attach)
663 continue;
665 if (icalattach_get_is_url (attach)) {
666 const gchar *data;
667 gsize buf_size;
669 data = icalattach_get_url (attach);
670 buf_size = strlen (data);
671 uri = g_malloc0 (buf_size + 1);
673 icalvalue_decode_ical_string (data, uri, buf_size);
676 if (uri) {
677 if (g_ascii_strncasecmp (uri, "file://", 7) == 0 &&
678 !g_str_has_prefix (uri + 7, target_filename_prefix)) {
679 GFile *source, *destination;
680 gchar *decoded_filename;
681 gchar *target_filename;
683 decoded_filename = g_uri_unescape_string (strrchr (uri, '/') + 1, NULL);
684 target_filename = g_strconcat (target_filename_prefix, decoded_filename, NULL);
685 g_free (decoded_filename);
687 source = g_file_new_for_uri (uri);
688 destination = g_file_new_for_path (target_filename);
690 if (source && destination) {
691 success = g_file_copy (source, destination, G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error);
692 if (success) {
693 g_free (uri);
694 uri = g_file_get_uri (destination);
696 if (uri) {
697 icalattach *new_attach;
698 gsize buf_size;
699 gchar *buf;
701 buf_size = 2 * strlen (uri) + 1;
702 buf = g_malloc0 (buf_size);
704 icalvalue_encode_ical_string (uri, buf, buf_size);
705 new_attach = icalattach_new_from_url (buf);
707 icalproperty_set_attach (prop, new_attach);
709 icalattach_unref (new_attach);
710 g_free (buf);
715 g_clear_object (&source);
716 g_clear_object (&destination);
717 g_free (target_filename);
720 g_free (uri);
723 success = success & !g_cancellable_set_error_if_cancelled (cancellable, error);
726 g_free (target_filename_prefix);
728 return success;
731 static void
732 ece_gather_tzids_cb (icalparameter *param,
733 gpointer user_data)
735 GHashTable *tzids = user_data;
736 const gchar *tzid;
738 g_return_if_fail (param != NULL);
739 g_return_if_fail (tzids != NULL);
741 tzid = icalparameter_get_tzid (param);
742 if (tzid && *tzid && g_ascii_strcasecmp (tzid, "UTC") != 0)
743 g_hash_table_insert (tzids, g_strdup (tzid), NULL);
746 static gboolean
747 ece_save_component_add_timezones_sync (SaveData *sd,
748 GCancellable *cancellable,
749 GError **error)
751 GHashTable *tzids;
752 GHashTableIter iter;
753 gpointer key, value;
754 gboolean source_is_target;
756 g_return_val_if_fail (sd != NULL, FALSE);
757 g_return_val_if_fail (sd->component != NULL, FALSE);
758 g_return_val_if_fail (sd->target_client != NULL, FALSE);
760 sd->success = TRUE;
762 tzids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
763 source_is_target = !sd->source_client ||
764 e_source_equal (e_client_get_source (E_CLIENT (sd->target_client)),
765 e_client_get_source (E_CLIENT (sd->source_client)));
767 icalcomponent_foreach_tzid (sd->component, ece_gather_tzids_cb, tzids);
769 g_hash_table_iter_init (&iter, tzids);
770 while (sd->success && g_hash_table_iter_next (&iter, &key, &value)) {
771 const gchar *tzid = key;
772 icaltimezone *zone = NULL;
773 GError *local_error = NULL;
775 if (!e_cal_client_get_timezone_sync (source_is_target ? sd->target_client : sd->source_client,
776 tzid, &zone, cancellable, &local_error)) {
777 zone = icaltimezone_get_builtin_timezone_from_tzid (tzid);
778 if (!zone)
779 zone = icaltimezone_get_builtin_timezone (tzid);
780 if (!zone) {
781 g_propagate_error (error, local_error);
782 local_error = NULL;
783 sd->success = FALSE;
784 break;
788 sd->success = e_cal_client_add_timezone_sync (sd->target_client, zone, cancellable, error);
790 g_clear_error (&local_error);
793 g_hash_table_destroy (tzids);
795 return sd->success;
798 static void
799 ece_save_component_thread (EAlertSinkThreadJobData *job_data,
800 gpointer user_data,
801 GCancellable *cancellable,
802 GError **error)
804 SaveData *sd = user_data;
805 const gchar *create_alert_ident, *modify_alert_ident, *remove_alert_ident, *get_alert_ident;
806 gchar *orig_uid, *new_uid = NULL;
808 g_return_if_fail (sd != NULL);
809 g_return_if_fail (E_IS_CAL_CLIENT (sd->target_client));
810 g_return_if_fail (sd->component != NULL);
812 switch (icalcomponent_isa (sd->component)) {
813 case ICAL_VEVENT_COMPONENT:
814 create_alert_ident = "calendar:failed-create-event";
815 modify_alert_ident = "calendar:failed-modify-event";
816 remove_alert_ident = "calendar:failed-remove-event";
817 get_alert_ident = "calendar:failed-get-event";
818 break;
819 case ICAL_VJOURNAL_COMPONENT:
820 create_alert_ident = "calendar:failed-create-memo";
821 modify_alert_ident = "calendar:failed-modify-memo";
822 remove_alert_ident = "calendar:failed-remove-memo";
823 get_alert_ident = "calendar:failed-get-memo";
824 break;
825 case ICAL_VTODO_COMPONENT:
826 create_alert_ident = "calendar:failed-create-task";
827 modify_alert_ident = "calendar:failed-modify-task";
828 remove_alert_ident = "calendar:failed-remove-task";
829 get_alert_ident = "calendar:failed-get-task";
830 break;
831 default:
832 g_warning ("%s: Cannot save component of kind %d", G_STRFUNC, icalcomponent_isa (sd->component));
833 return;
836 sd->success = ece_save_component_add_timezones_sync (sd, cancellable, error);
837 if (!sd->success) {
838 e_alert_sink_thread_job_set_alert_ident (job_data, "calendar:failed-add-timezone");
839 return;
842 sd->success = ece_save_component_attachments_sync (sd->target_client, sd->component, cancellable, error);
843 if (!sd->success) {
844 e_alert_sink_thread_job_set_alert_ident (job_data, "calendar:failed-save-attachments");
845 return;
848 orig_uid = g_strdup (icalcomponent_get_uid (sd->component));
850 if (cal_comp_is_icalcomp_on_server_sync (sd->component, sd->target_client, cancellable, error)) {
851 ECalComponent *comp;
852 gboolean has_recurrences;
854 e_alert_sink_thread_job_set_alert_ident (job_data, modify_alert_ident);
856 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (sd->component));
857 g_return_if_fail (comp != NULL);
859 has_recurrences = e_cal_util_component_has_recurrences (sd->component);
861 if (has_recurrences && sd->recur_mod == E_CAL_OBJ_MOD_ALL)
862 sd->success = comp_util_sanitize_recurrence_master_sync (comp, sd->target_client, cancellable, error);
863 else
864 sd->success = TRUE;
866 if (sd->recur_mod == E_CAL_OBJ_MOD_THIS) {
867 e_cal_component_set_rdate_list (comp, NULL);
868 e_cal_component_set_rrule_list (comp, NULL);
869 e_cal_component_set_exdate_list (comp, NULL);
870 e_cal_component_set_exrule_list (comp, NULL);
873 sd->success = sd->success && e_cal_client_modify_object_sync (
874 sd->target_client, e_cal_component_get_icalcomponent (comp), sd->recur_mod, cancellable, error);
876 g_clear_object (&comp);
877 } else {
878 e_alert_sink_thread_job_set_alert_ident (job_data, create_alert_ident);
880 sd->success = e_cal_client_create_object_sync (sd->target_client, sd->component, &new_uid, cancellable, error);
882 if (sd->success)
883 sd->object_created = TRUE;
886 if (sd->success && sd->source_client &&
887 !e_source_equal (e_client_get_source (E_CLIENT (sd->target_client)),
888 e_client_get_source (E_CLIENT (sd->source_client))) &&
889 cal_comp_is_icalcomp_on_server_sync (sd->component, sd->source_client, cancellable, NULL)) {
890 ECalObjModType recur_mod = E_CAL_OBJ_MOD_THIS;
892 /* Comp found a new home. Remove it from old one. */
893 if (e_cal_util_component_is_instance (sd->component) ||
894 e_cal_util_component_has_recurrences (sd->component))
895 recur_mod = E_CAL_OBJ_MOD_ALL;
897 sd->success = e_cal_client_remove_object_sync (
898 sd->source_client, orig_uid,
899 NULL, recur_mod, cancellable, error);
901 if (!sd->success) {
902 gchar *source_display_name;
904 source_display_name = e_util_get_source_full_name (e_shell_get_registry (sd->comp_editor->priv->shell),
905 e_client_get_source (E_CLIENT (sd->source_client)));
907 e_alert_sink_thread_job_set_alert_ident (job_data, remove_alert_ident);
908 e_alert_sink_thread_job_set_alert_arg_0 (job_data, source_display_name);
910 g_free (source_display_name);
914 if (new_uid) {
915 icalcomponent_set_uid (sd->component, new_uid);
916 g_free (new_uid);
919 g_free (orig_uid);
921 if (sd->success && !sd->close_after_save) {
922 icalcomponent *comp = NULL;
923 gchar *uid, *rid = NULL;
925 uid = g_strdup (icalcomponent_get_uid (sd->component));
926 if (icalcomponent_get_first_property (sd->component, ICAL_RECURRENCEID_PROPERTY)) {
927 struct icaltimetype ridtt;
929 ridtt = icalcomponent_get_recurrenceid (sd->component);
930 if (icaltime_is_valid_time (ridtt) && !icaltime_is_null_time (ridtt)) {
931 rid = icaltime_as_ical_string_r (ridtt);
935 sd->success = e_cal_client_get_object_sync (sd->target_client, uid, rid, &comp, cancellable, error);
936 if (sd->success && comp) {
937 icalcomponent_free (sd->component);
938 sd->component = comp;
939 } else {
940 e_alert_sink_thread_job_set_alert_ident (job_data, get_alert_ident);
943 g_free (uid);
944 g_free (rid);
948 static void
949 ece_save_component (ECompEditor *comp_editor,
950 icalcomponent *component,
951 gboolean with_send,
952 gboolean close_after_save)
954 EActivity *activity;
955 const gchar *summary;
956 ECalObjModType recur_mod = E_CAL_OBJ_MOD_THIS;
957 SaveData *sd;
958 gchar *source_display_name;
960 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
961 g_return_if_fail (component != NULL);
963 summary = icalcomponent_get_summary (component);
964 if (!summary || !*summary) {
965 if (!e_cal_dialogs_send_component_prompt_subject (GTK_WINDOW (comp_editor), component)) {
966 return;
970 if (e_cal_util_component_is_instance (component)) {
971 if (!e_cal_dialogs_recur_icalcomp (comp_editor->priv->target_client,
972 component, &recur_mod, GTK_WINDOW (comp_editor), FALSE)) {
973 return;
975 } else if (e_cal_util_component_has_recurrences (component)) {
976 recur_mod = E_CAL_OBJ_MOD_ALL;
979 e_comp_editor_enable (comp_editor, FALSE);
981 sd = g_new0 (SaveData, 1);
982 sd->comp_editor = g_object_ref (comp_editor);
983 sd->source_client = comp_editor->priv->source_client ? g_object_ref (comp_editor->priv->source_client) : NULL;
984 sd->target_client = g_object_ref (comp_editor->priv->target_client);
985 sd->component = icalcomponent_new_clone (component);
986 sd->with_send = with_send;
987 sd->close_after_save = close_after_save;
988 sd->recur_mod = recur_mod;
989 sd->first_send = E_CAL_COMPONENT_METHOD_NONE;
990 sd->second_send = E_CAL_COMPONENT_METHOD_NONE;
991 sd->success = FALSE;
993 source_display_name = e_util_get_source_full_name (e_shell_get_registry (comp_editor->priv->shell),
994 e_client_get_source (E_CLIENT (sd->target_client)));
996 activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (comp_editor),
997 _("Saving changes..."), "calendar:failed-create-event", source_display_name,
998 ece_save_component_thread, sd, ece_save_component_done);
1000 if (activity)
1001 e_activity_bar_set_activity (comp_editor->priv->activity_bar, activity);
1003 g_clear_object (&activity);
1004 g_free (source_display_name);
1007 typedef struct _OpenTargetClientData {
1008 ECompEditor *comp_editor;
1009 ESource *source;
1010 gchar *extension_name;
1011 EClient *client;
1012 gchar *cal_email_address;
1013 gchar *alarm_email_address;
1014 gboolean is_target_client_change;
1015 EActivity *activity;
1016 } OpenTargetClientData;
1018 static void
1019 open_target_client_data_free (gpointer ptr)
1021 OpenTargetClientData *otc = ptr;
1023 if (otc) {
1024 if (otc->comp_editor) {
1025 if (otc->client) {
1026 gboolean previous_changed = e_comp_editor_get_changed (otc->comp_editor);
1028 e_comp_editor_set_alarm_email_address (otc->comp_editor, otc->alarm_email_address);
1029 e_comp_editor_set_cal_email_address (otc->comp_editor, otc->cal_email_address);
1030 e_comp_editor_set_target_client (otc->comp_editor, E_CAL_CLIENT (otc->client));
1032 if (otc->is_target_client_change)
1033 e_comp_editor_set_changed (otc->comp_editor, TRUE);
1034 else
1035 e_comp_editor_set_changed (otc->comp_editor, previous_changed);
1038 if (otc->comp_editor->priv->activity_bar && otc->activity) {
1039 if (otc->activity == e_activity_bar_get_activity (otc->comp_editor->priv->activity_bar))
1040 e_activity_bar_set_activity (otc->comp_editor->priv->activity_bar, NULL);
1042 if (otc->activity == otc->comp_editor->priv->target_client_opening)
1043 g_clear_object (&otc->comp_editor->priv->target_client_opening);
1046 if (otc->source) {
1047 EShell *shell;
1048 ECredentialsPrompter *credentials_prompter;
1050 shell = e_comp_editor_get_shell (otc->comp_editor);
1051 credentials_prompter = e_shell_get_credentials_prompter (shell);
1053 e_credentials_prompter_set_auto_prompt_disabled_for (credentials_prompter, otc->source, TRUE);
1056 e_comp_editor_sensitize_widgets (otc->comp_editor);
1059 g_clear_object (&otc->comp_editor);
1060 g_clear_object (&otc->source);
1061 g_clear_object (&otc->client);
1062 g_clear_object (&otc->activity);
1063 g_free (otc->extension_name);
1064 g_free (otc->cal_email_address);
1065 g_free (otc->alarm_email_address);
1066 g_free (otc);
1070 static void
1071 comp_editor_open_target_client_thread (EAlertSinkThreadJobData *job_data,
1072 gpointer user_data,
1073 GCancellable *cancellable,
1074 GError **error)
1076 OpenTargetClientData *otc = user_data;
1077 EClientCache *client_cache;
1079 g_return_if_fail (otc != NULL);
1081 if (g_cancellable_set_error_if_cancelled (cancellable, error))
1082 return;
1084 g_return_if_fail (E_IS_COMP_EDITOR (otc->comp_editor));
1085 g_return_if_fail (E_IS_SOURCE (otc->source));
1086 g_return_if_fail (otc->extension_name != NULL);
1088 client_cache = e_shell_get_client_cache (e_comp_editor_get_shell (otc->comp_editor));
1090 otc->client = e_client_cache_get_client_sync (client_cache, otc->source, otc->extension_name,
1091 30, cancellable, error);
1093 if (otc->client) {
1094 /* Cache some properties which require remote calls */
1096 if (!g_cancellable_is_cancelled (cancellable)) {
1097 e_client_get_capabilities (otc->client);
1100 if (!g_cancellable_is_cancelled (cancellable)) {
1101 e_client_get_backend_property_sync (otc->client,
1102 CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS,
1103 &otc->cal_email_address,
1104 cancellable, error);
1107 if (!g_cancellable_is_cancelled (cancellable)) {
1108 e_client_get_backend_property_sync (otc->client,
1109 CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS,
1110 &otc->alarm_email_address,
1111 cancellable, error);
1114 if (g_cancellable_is_cancelled (cancellable))
1115 g_clear_object (&otc->client);
1119 typedef struct _UpdateActivityBarData {
1120 ECompEditor *comp_editor;
1121 EActivity *activity;
1122 } UpdateActivityBarData;
1124 static void
1125 update_activity_bar_data_free (gpointer ptr)
1127 UpdateActivityBarData *uab = ptr;
1129 if (uab) {
1130 g_clear_object (&uab->comp_editor);
1131 g_clear_object (&uab->activity);
1132 g_free (uab);
1136 static gboolean
1137 update_activity_bar_cb (gpointer user_data)
1139 UpdateActivityBarData *uab = user_data;
1141 g_return_val_if_fail (uab != NULL, FALSE);
1142 g_return_val_if_fail (E_IS_COMP_EDITOR (uab->comp_editor), FALSE);
1143 g_return_val_if_fail (E_IS_ACTIVITY (uab->activity), FALSE);
1145 if (uab->comp_editor->priv->target_client_opening == uab->activity &&
1146 e_activity_get_state (uab->activity) != E_ACTIVITY_CANCELLED &&
1147 e_activity_get_state (uab->activity) != E_ACTIVITY_COMPLETED) {
1148 e_activity_bar_set_activity (uab->comp_editor->priv->activity_bar, uab->activity);
1151 return FALSE;
1154 static void
1155 e_comp_editor_open_target_client (ECompEditor *comp_editor)
1157 OpenTargetClientData *otc;
1158 ESource *source;
1159 EActivity *activity;
1160 ECredentialsPrompter *credentials_prompter;
1161 gchar *source_display_name, *description = NULL, *alert_ident = NULL, *alert_arg_0 = NULL;
1162 gboolean is_target_client_change;
1163 const gchar *extension_name;
1165 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1166 g_return_if_fail (comp_editor->priv->page_general != NULL);
1168 source = e_comp_editor_page_general_ref_selected_source (comp_editor->priv->page_general);
1169 if (!source)
1170 return;
1172 if (comp_editor->priv->target_client &&
1173 e_client_get_source (E_CLIENT (comp_editor->priv->target_client)) == source) {
1174 g_clear_object (&source);
1175 return;
1178 if (comp_editor->priv->target_client_opening) {
1179 e_activity_cancel (comp_editor->priv->target_client_opening);
1180 g_clear_object (&comp_editor->priv->target_client_opening);
1183 is_target_client_change = comp_editor->priv->target_client != NULL;
1184 g_clear_object (&comp_editor->priv->target_client);
1186 extension_name = e_comp_editor_page_general_get_source_extension_name (comp_editor->priv->page_general);
1187 source_display_name = e_util_get_source_full_name (
1188 e_shell_get_registry (e_comp_editor_get_shell (comp_editor)),
1189 source);
1191 g_return_if_fail (e_util_get_open_source_job_info (extension_name, source_display_name,
1192 &description, &alert_ident, &alert_arg_0));
1194 credentials_prompter = e_shell_get_credentials_prompter (e_comp_editor_get_shell (comp_editor));
1195 e_credentials_prompter_set_auto_prompt_disabled_for (credentials_prompter, source, FALSE);
1197 otc = g_new0 (OpenTargetClientData, 1);
1198 otc->extension_name = g_strdup (extension_name);
1199 otc->comp_editor = g_object_ref (comp_editor);
1200 otc->source = g_object_ref (source);
1201 otc->is_target_client_change = is_target_client_change;
1203 activity = e_alert_sink_submit_thread_job (
1204 E_ALERT_SINK (comp_editor), description, alert_ident, alert_arg_0,
1205 comp_editor_open_target_client_thread, otc,
1206 open_target_client_data_free);
1208 otc->activity = g_object_ref (activity);
1209 comp_editor->priv->target_client_opening = g_object_ref (activity);
1211 /* Close all alerts */
1212 while (e_alert_bar_close_alert (comp_editor->priv->alert_bar)) {
1216 if (comp_editor->priv->activity_bar) {
1217 UpdateActivityBarData *uab;
1219 uab = g_new0 (UpdateActivityBarData, 1);
1220 uab->comp_editor = g_object_ref (comp_editor);
1221 uab->activity = g_object_ref (activity);
1223 /* To avoid UI flickering when the source can be opened quickly */
1224 g_timeout_add_seconds_full (G_PRIORITY_LOW, 1,
1225 update_activity_bar_cb, uab, update_activity_bar_data_free);
1228 g_free (description);
1229 g_free (alert_ident);
1230 g_free (alert_arg_0);
1231 g_free (source_display_name);
1232 g_clear_object (&source);
1233 g_clear_object (&activity);
1236 static void
1237 e_comp_editor_update_window_title (ECompEditor *comp_editor)
1239 ECompEditorClass *comp_editor_class;
1240 gboolean with_attendees = FALSE;
1241 const gchar *format, *title_suffix;
1242 gchar *title;
1244 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1246 if (comp_editor->priv->page_general)
1247 with_attendees = e_comp_editor_page_general_get_show_attendees (comp_editor->priv->page_general);
1249 comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor);
1250 if (with_attendees)
1251 format = comp_editor_class->title_format_with_attendees;
1252 else
1253 format = comp_editor_class->title_format_without_attendees;
1255 title_suffix = e_comp_editor_get_title_suffix (comp_editor);
1257 title = g_strdup_printf (format, title_suffix && *title_suffix ? title_suffix : _("No Summary"));
1259 gtk_window_set_icon_name (GTK_WINDOW (comp_editor), comp_editor_class->icon_name);
1260 gtk_window_set_title (GTK_WINDOW (comp_editor), title);
1262 g_free (title);
1265 static void
1266 e_comp_editor_close (ECompEditor *comp_editor)
1268 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1270 g_signal_emit (comp_editor, signals[EDITOR_CLOSED], 0, FALSE, NULL);
1272 gtk_widget_destroy (GTK_WIDGET (comp_editor));
1275 static void
1276 e_comp_editor_save_and_close (ECompEditor *comp_editor,
1277 gboolean can_close)
1279 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1281 if (comp_editor->priv->component) {
1282 icalcomponent *component = icalcomponent_new_clone (comp_editor->priv->component);
1283 if (component && e_comp_editor_fill_component (comp_editor, component)) {
1284 ece_save_component (comp_editor, component, TRUE, can_close);
1285 icalcomponent_free (component);
1290 static GtkResponseType
1291 ece_save_component_dialog (ECompEditor *comp_editor)
1293 const icalcomponent *component;
1294 GtkWindow *parent;
1296 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), GTK_RESPONSE_NO);
1297 g_return_val_if_fail (e_comp_editor_get_component (comp_editor) != NULL, GTK_RESPONSE_NO);
1299 parent = GTK_WINDOW (comp_editor);
1300 component = e_comp_editor_get_component (comp_editor);
1301 switch (icalcomponent_isa (component)) {
1302 case ICAL_VEVENT_COMPONENT:
1303 if (e_comp_editor_page_general_get_show_attendees (comp_editor->priv->page_general))
1304 return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-meeting", NULL);
1305 else
1306 return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-appointment", NULL);
1307 case ICAL_VTODO_COMPONENT:
1308 return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-task", NULL);
1309 case ICAL_VJOURNAL_COMPONENT:
1310 return e_alert_run_dialog_for_args (parent, "calendar:prompt-save-memo", NULL);
1311 default:
1312 return GTK_RESPONSE_NO;
1316 static gboolean
1317 e_comp_editor_prompt_and_save_changes (ECompEditor *comp_editor,
1318 gboolean with_send)
1320 icalcomponent *component;
1322 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
1324 if (!e_comp_editor_get_changed (comp_editor))
1325 return TRUE;
1327 switch (ece_save_component_dialog (comp_editor)) {
1328 case GTK_RESPONSE_YES: /* Save */
1329 if (e_client_is_readonly (E_CLIENT (comp_editor->priv->target_client))) {
1330 e_alert_submit (
1331 E_ALERT_SINK (comp_editor),
1332 "calendar:prompt-read-only-cal-editor",
1333 e_source_get_display_name (
1334 e_client_get_source (E_CLIENT (comp_editor->priv->target_client))),
1335 NULL);
1336 /* don't discard changes when selected readonly calendar */
1337 return FALSE;
1340 if (comp_editor->priv->component &&
1341 e_comp_editor_page_general_get_show_attendees (comp_editor->priv->page_general) &&
1342 icalcomponent_isa (comp_editor->priv->component) == ICAL_VTODO_COMPONENT
1343 && e_client_check_capability (E_CLIENT (comp_editor->priv->target_client), CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT)) {
1344 e_alert_submit (
1345 E_ALERT_SINK (comp_editor),
1346 "calendar:prompt-no-task-assignment-editor",
1347 e_source_get_display_name (
1348 e_client_get_source (E_CLIENT (comp_editor->priv->target_client))),
1349 NULL);
1350 return FALSE;
1353 component = icalcomponent_new_clone (comp_editor->priv->component);
1354 if (!e_comp_editor_fill_component (comp_editor, component)) {
1355 icalcomponent_free (component);
1356 return FALSE;
1359 ece_save_component (comp_editor, component, with_send, TRUE);
1361 return FALSE;
1362 case GTK_RESPONSE_NO: /* Discard */
1363 return TRUE;
1364 case GTK_RESPONSE_CANCEL: /* Cancel */
1365 default:
1366 return FALSE;
1369 return FALSE;
1372 static void
1373 action_close_cb (GtkAction *action,
1374 ECompEditor *comp_editor)
1376 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1378 if (e_comp_editor_prompt_and_save_changes (comp_editor, TRUE))
1379 e_comp_editor_close (comp_editor);
1382 static void
1383 action_help_cb (GtkAction *action,
1384 ECompEditor *comp_editor)
1386 ECompEditorClass *klass;
1388 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1390 klass = E_COMP_EDITOR_GET_CLASS (comp_editor);
1391 g_return_if_fail (klass->help_section != NULL);
1393 e_display_help (GTK_WINDOW (comp_editor), klass->help_section);
1396 static void
1397 ece_print_or_preview (ECompEditor *comp_editor,
1398 GtkPrintOperationAction print_action)
1400 icalcomponent *component;
1401 ECalComponent *comp;
1403 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1404 g_return_if_fail (e_comp_editor_get_component (comp_editor) != NULL);
1406 component = icalcomponent_new_clone (e_comp_editor_get_component (comp_editor));
1407 if (!e_comp_editor_fill_component (comp_editor, component)) {
1408 icalcomponent_free (component);
1409 return;
1412 comp = e_cal_component_new_from_icalcomponent (component);
1413 g_return_if_fail (comp != NULL);
1415 print_comp (comp,
1416 e_comp_editor_get_target_client (comp_editor),
1417 calendar_config_get_icaltimezone (),
1418 calendar_config_get_24_hour_format (),
1419 print_action);
1421 g_object_unref (comp);
1424 static void
1425 action_print_cb (GtkAction *action,
1426 ECompEditor *comp_editor)
1428 ece_print_or_preview (comp_editor, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
1431 static void
1432 action_print_preview_cb (GtkAction *action,
1433 ECompEditor *comp_editor)
1435 ece_print_or_preview (comp_editor, GTK_PRINT_OPERATION_ACTION_PREVIEW);
1438 static void
1439 action_save_cb (GtkAction *action,
1440 ECompEditor *comp_editor)
1442 e_comp_editor_save_and_close (comp_editor, FALSE);
1445 static void
1446 action_save_and_close_cb (GtkAction *action,
1447 ECompEditor *comp_editor)
1449 e_comp_editor_save_and_close (comp_editor, TRUE);
1452 static gboolean
1453 ece_organizer_email_address_is_user (ECompEditor *comp_editor,
1454 EClient *client,
1455 const gchar *email_address,
1456 gboolean is_organizer)
1458 ESourceRegistry *registry;
1459 const gchar *cal_email_address;
1461 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
1462 g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1464 email_address = itip_strip_mailto (email_address);
1466 if (!email_address || !*email_address)
1467 return FALSE;
1469 cal_email_address = e_comp_editor_get_cal_email_address (comp_editor);
1470 if (cal_email_address && *cal_email_address &&
1471 g_ascii_strcasecmp (cal_email_address, email_address) == 0) {
1472 return TRUE;
1475 if (is_organizer && e_client_check_capability (client, CAL_STATIC_CAPABILITY_ORGANIZER_NOT_EMAIL_ADDRESS))
1476 return FALSE;
1478 registry = e_shell_get_registry (e_comp_editor_get_shell (comp_editor));
1480 return itip_address_is_user (registry, email_address);
1483 static gboolean
1484 ece_organizer_is_user (ECompEditor *comp_editor,
1485 icalcomponent *component,
1486 EClient *client)
1488 icalproperty *prop;
1489 const gchar *organizer;
1491 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
1492 g_return_val_if_fail (component != NULL, FALSE);
1493 g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1495 prop = icalcomponent_get_first_property (component, ICAL_ORGANIZER_PROPERTY);
1496 if (!prop || e_client_check_capability (client, CAL_STATIC_CAPABILITY_NO_ORGANIZER))
1497 return FALSE;
1499 organizer = itip_strip_mailto (icalproperty_get_organizer (prop));
1500 if (!organizer || !*organizer)
1501 return FALSE;
1503 return ece_organizer_email_address_is_user (comp_editor, client, organizer, TRUE);
1506 static gboolean
1507 ece_sentby_is_user (ECompEditor *comp_editor,
1508 icalcomponent *component,
1509 EClient *client)
1511 icalproperty *prop;
1512 icalparameter *param;
1513 const gchar *sentby;
1515 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
1516 g_return_val_if_fail (component != NULL, FALSE);
1517 g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
1519 prop = icalcomponent_get_first_property (component, ICAL_ORGANIZER_PROPERTY);
1520 if (!prop || e_client_check_capability (client, CAL_STATIC_CAPABILITY_NO_ORGANIZER))
1521 return FALSE;
1523 param = icalproperty_get_first_parameter (prop, ICAL_SENTBY_PARAMETER);
1524 if (!param)
1525 return FALSE;
1527 sentby = icalparameter_get_sentby (param);
1529 return ece_organizer_email_address_is_user (comp_editor, client, sentby, FALSE);
1532 static void
1533 ece_emit_times_changed_cb (ECompEditor *comp_editor)
1535 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1537 g_signal_emit (comp_editor, signals[TIMES_CHANGED], 0, NULL);
1540 static void
1541 ece_connect_time_parts (ECompEditor *comp_editor,
1542 ECompEditorPropertyPart *dtstart_part,
1543 ECompEditorPropertyPart *dtend_part)
1545 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1547 #define update_part(x) G_STMT_START { \
1548 if (x) \
1549 g_object_ref (x); \
1550 if (comp_editor->priv->x) { \
1551 g_signal_handlers_disconnect_by_func (comp_editor->priv->x, G_CALLBACK (ece_emit_times_changed_cb), comp_editor); \
1552 g_clear_object (&comp_editor->priv->x); \
1554 if (x) { \
1555 comp_editor->priv->x = x; \
1556 g_signal_connect_swapped (comp_editor->priv->x, "changed", \
1557 G_CALLBACK (ece_emit_times_changed_cb), comp_editor); \
1559 } G_STMT_END
1561 update_part (dtstart_part);
1562 update_part (dtend_part);
1564 #undef update_part
1567 static void
1568 ece_sensitize_widgets (ECompEditor *comp_editor,
1569 gboolean force_insensitive)
1571 GtkActionGroup *group;
1572 GSList *link;
1574 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1576 for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) {
1577 ECompEditorPage *page = link->data;
1579 g_warn_if_fail (E_IS_COMP_EDITOR_PAGE (page));
1580 if (!E_IS_COMP_EDITOR_PAGE (page))
1581 continue;
1583 e_comp_editor_page_sensitize_widgets (page, force_insensitive);
1586 group = e_comp_editor_get_action_group (comp_editor, "individual");
1587 gtk_action_group_set_sensitive (group, !force_insensitive);
1589 group = e_comp_editor_get_action_group (comp_editor, "editable");
1590 gtk_action_group_set_sensitive (group, !force_insensitive);
1593 static void
1594 ece_fill_widgets (ECompEditor *comp_editor,
1595 icalcomponent *component)
1597 GSList *link;
1599 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1600 g_return_if_fail (component != NULL);
1602 for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) {
1603 ECompEditorPage *page = link->data;
1605 g_warn_if_fail (E_IS_COMP_EDITOR_PAGE (page));
1606 if (!E_IS_COMP_EDITOR_PAGE (page))
1607 continue;
1609 e_comp_editor_page_fill_widgets (page, component);
1613 static gboolean
1614 ece_fill_component (ECompEditor *comp_editor,
1615 icalcomponent *component)
1617 GSList *link;
1619 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
1620 g_return_val_if_fail (component != NULL, FALSE);
1622 for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) {
1623 ECompEditorPage *page = link->data;
1625 g_warn_if_fail (E_IS_COMP_EDITOR_PAGE (page));
1626 if (!E_IS_COMP_EDITOR_PAGE (page))
1627 continue;
1629 if (!e_comp_editor_page_fill_component (page, component))
1630 return FALSE;
1633 return TRUE;
1636 static void
1637 comp_editor_realize_cb (ECompEditor *comp_editor)
1639 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1641 if (comp_editor->priv->component) {
1642 e_comp_editor_fill_widgets (comp_editor, comp_editor->priv->component);
1643 e_comp_editor_set_changed (comp_editor, FALSE);
1646 e_comp_editor_update_window_title (comp_editor);
1647 e_comp_editor_sensitize_widgets (comp_editor);
1649 if (comp_editor->priv->page_general && comp_editor->priv->origin_source) {
1650 e_comp_editor_page_general_set_selected_source (
1651 comp_editor->priv->page_general,
1652 comp_editor->priv->origin_source);
1653 e_comp_editor_set_changed (comp_editor, FALSE);
1656 if (comp_editor->priv->page_general) {
1657 e_comp_editor_page_general_update_view (comp_editor->priv->page_general);
1659 if (!comp_editor->priv->show_attendees_handler_id) {
1660 comp_editor->priv->show_attendees_handler_id =
1661 e_signal_connect_notify_swapped (comp_editor->priv->page_general,
1662 "notify::show-attendees",
1663 G_CALLBACK (e_comp_editor_update_window_title), comp_editor);
1667 if (!comp_editor->priv->target_client)
1668 e_comp_editor_open_target_client (comp_editor);
1671 static void
1672 comp_editor_unrealize_cb (ECompEditor *comp_editor)
1674 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1676 if (comp_editor->priv->page_general) {
1677 e_signal_disconnect_notify_handler (comp_editor->priv->page_general,
1678 &comp_editor->priv->show_attendees_handler_id);
1682 static gboolean
1683 comp_editor_delete_event (GtkWidget *widget,
1684 GdkEventAny *event)
1686 ECompEditor *comp_editor;
1688 g_return_val_if_fail (E_IS_COMP_EDITOR (widget), FALSE);
1690 comp_editor = E_COMP_EDITOR (widget);
1692 /* It's disabled when the component is being saved */
1693 if (gtk_widget_get_sensitive (GTK_WIDGET (comp_editor->priv->content)))
1694 action_close_cb (NULL, comp_editor);
1696 return TRUE;
1699 static gboolean
1700 comp_editor_key_press_event (GtkWidget *widget,
1701 GdkEventKey *event)
1703 ECompEditor *comp_editor;
1705 g_return_val_if_fail (E_IS_COMP_EDITOR (widget), FALSE);
1707 comp_editor = E_COMP_EDITOR (widget);
1709 if (event->keyval == GDK_KEY_Escape &&
1710 !e_alert_bar_close_alert (comp_editor->priv->alert_bar)) {
1711 GtkAction *action;
1713 action = e_comp_editor_get_action (comp_editor, "close");
1714 gtk_action_activate (action);
1716 return TRUE;
1719 /* Chain up to parent's method. */
1720 return GTK_WIDGET_CLASS (e_comp_editor_parent_class)->key_press_event (widget, event);
1723 static void
1724 comp_editor_selected_source_notify_cb (ECompEditorPageGeneral *page_general,
1725 GParamSpec *param,
1726 ECompEditor *comp_editor)
1728 g_return_if_fail (E_IS_COMP_EDITOR_PAGE_GENERAL (page_general));
1729 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1730 g_return_if_fail (comp_editor->priv->page_general == page_general);
1732 e_comp_editor_open_target_client (comp_editor);
1735 static void
1736 e_comp_editor_submit_alert (EAlertSink *alert_sink,
1737 EAlert *alert)
1739 ECompEditor *comp_editor;
1741 g_return_if_fail (E_IS_COMP_EDITOR (alert_sink));
1742 g_return_if_fail (E_IS_ALERT (alert));
1744 comp_editor = E_COMP_EDITOR (alert_sink);
1746 e_alert_bar_submit_alert (comp_editor->priv->alert_bar, alert);
1749 static void
1750 e_comp_editor_set_origin_source (ECompEditor *comp_editor,
1751 ESource *origin_source)
1753 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1754 if (origin_source)
1755 g_return_if_fail (E_IS_SOURCE (origin_source));
1757 g_clear_object (&comp_editor->priv->origin_source);
1758 if (origin_source)
1759 comp_editor->priv->origin_source = g_object_ref (origin_source);
1762 static void
1763 e_comp_editor_set_shell (ECompEditor *comp_editor,
1764 EShell *shell)
1766 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
1767 g_return_if_fail (E_IS_SHELL (shell));
1769 g_clear_object (&comp_editor->priv->shell);
1770 comp_editor->priv->shell = g_object_ref (shell);
1773 static void
1774 e_comp_editor_set_property (GObject *object,
1775 guint property_id,
1776 const GValue *value,
1777 GParamSpec *pspec)
1779 switch (property_id) {
1780 case PROP_ALARM_EMAIL_ADDRESS:
1781 e_comp_editor_set_alarm_email_address (
1782 E_COMP_EDITOR (object),
1783 g_value_get_string (value));
1784 return;
1786 case PROP_CAL_EMAIL_ADDRESS:
1787 e_comp_editor_set_cal_email_address (
1788 E_COMP_EDITOR (object),
1789 g_value_get_string (value));
1790 return;
1792 case PROP_CHANGED:
1793 e_comp_editor_set_changed (
1794 E_COMP_EDITOR (object),
1795 g_value_get_boolean (value));
1796 return;
1798 case PROP_COMPONENT:
1799 e_comp_editor_set_component (
1800 E_COMP_EDITOR (object),
1801 g_value_get_pointer (value));
1802 return;
1804 case PROP_FLAGS:
1805 e_comp_editor_set_flags (
1806 E_COMP_EDITOR (object),
1807 g_value_get_uint (value));
1808 return;
1810 case PROP_ORIGIN_SOURCE:
1811 e_comp_editor_set_origin_source (
1812 E_COMP_EDITOR (object),
1813 g_value_get_object (value));
1814 return;
1816 case PROP_SHELL:
1817 e_comp_editor_set_shell (
1818 E_COMP_EDITOR (object),
1819 g_value_get_object (value));
1820 return;
1822 case PROP_SOURCE_CLIENT:
1823 e_comp_editor_set_source_client (
1824 E_COMP_EDITOR (object),
1825 g_value_get_object (value));
1826 return;
1828 case PROP_TARGET_CLIENT:
1829 e_comp_editor_set_target_client (
1830 E_COMP_EDITOR (object),
1831 g_value_get_object (value));
1832 return;
1834 case PROP_TITLE_SUFFIX:
1835 e_comp_editor_set_title_suffix (
1836 E_COMP_EDITOR (object),
1837 g_value_get_string (value));
1838 return;
1841 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1844 static void
1845 e_comp_editor_get_property (GObject *object,
1846 guint property_id,
1847 GValue *value,
1848 GParamSpec *pspec)
1850 switch (property_id) {
1851 case PROP_ALARM_EMAIL_ADDRESS:
1852 g_value_set_string (
1853 value,
1854 e_comp_editor_get_alarm_email_address (
1855 E_COMP_EDITOR (object)));
1856 return;
1858 case PROP_CAL_EMAIL_ADDRESS:
1859 g_value_set_string (
1860 value,
1861 e_comp_editor_get_cal_email_address (
1862 E_COMP_EDITOR (object)));
1863 return;
1865 case PROP_CHANGED:
1866 g_value_set_boolean (
1867 value,
1868 e_comp_editor_get_changed (
1869 E_COMP_EDITOR (object)));
1870 return;
1872 case PROP_COMPONENT:
1873 g_value_set_pointer (
1874 value,
1875 e_comp_editor_get_component (
1876 E_COMP_EDITOR (object)));
1877 return;
1879 case PROP_FLAGS:
1880 g_value_set_uint (
1881 value,
1882 e_comp_editor_get_flags (
1883 E_COMP_EDITOR (object)));
1884 return;
1886 case PROP_ORIGIN_SOURCE:
1887 g_value_set_object (
1888 value,
1889 e_comp_editor_get_origin_source (
1890 E_COMP_EDITOR (object)));
1891 return;
1893 case PROP_SHELL:
1894 g_value_set_object (
1895 value,
1896 e_comp_editor_get_shell (
1897 E_COMP_EDITOR (object)));
1898 return;
1900 case PROP_SOURCE_CLIENT:
1901 g_value_set_object (
1902 value,
1903 e_comp_editor_get_source_client (
1904 E_COMP_EDITOR (object)));
1905 return;
1907 case PROP_TARGET_CLIENT:
1908 g_value_set_object (
1909 value,
1910 e_comp_editor_get_target_client (
1911 E_COMP_EDITOR (object)));
1912 return;
1914 case PROP_TITLE_SUFFIX:
1915 g_value_set_string (
1916 value,
1917 e_comp_editor_get_title_suffix (
1918 E_COMP_EDITOR (object)));
1919 return;
1922 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1925 static void
1926 e_comp_editor_constructed (GObject *object)
1928 const gchar *ui =
1929 "<ui>"
1930 " <menubar action='main-menu'>"
1931 " <menu action='file-menu'>"
1932 " <menuitem action='save'/>"
1933 " <menuitem action='save-and-close'/>"
1934 " <separator/>"
1935 " <placeholder name='custom-actions-placeholder'/>"
1936 " <separator/>"
1937 " <menuitem action='print-preview'/>"
1938 " <menuitem action='print'/>"
1939 " <separator/>"
1940 " <menuitem action='close'/>"
1941 " </menu>"
1942 " <menu action='edit-menu'>"
1943 " <menuitem action='undo'/>"
1944 " <menuitem action='redo'/>"
1945 " <separator/>"
1946 " <menuitem action='cut-clipboard'/>"
1947 " <menuitem action='copy-clipboard'/>"
1948 " <menuitem action='paste-clipboard'/>"
1949 " <menuitem action='delete-selection'/>"
1950 " <separator/>"
1951 " <menuitem action='select-all'/>"
1952 " </menu>"
1953 " <menu action='view-menu'>"
1954 " <placeholder name='parts'/>"
1955 " <separator />"
1956 " <placeholder name='columns'/>"
1957 " </menu>"
1958 " <menu action='insert-menu'/>"
1959 " <menu action='options-menu'>"
1960 " <placeholder name='tabs'/>"
1961 " <placeholder name='toggles'/>"
1962 " </menu>"
1963 " <menu action='help-menu'>"
1964 " <menuitem action='help'/>"
1965 " </menu>"
1966 " </menubar>"
1967 " <toolbar name='main-toolbar'>"
1968 " <toolitem action='save-and-close'/>\n"
1969 " <toolitem action='save'/>"
1970 " <toolitem action='print'/>"
1971 " <separator/>"
1972 " <toolitem action='undo'/>"
1973 " <toolitem action='redo'/>"
1974 " <separator/>"
1975 " <placeholder name='content'/>"
1976 " <placeholder name='after-content'/>"
1977 " </toolbar>"
1978 "</ui>";
1980 GtkActionEntry core_entries[] = {
1982 { "close",
1983 "window-close",
1984 N_("_Close"),
1985 "<Control>w",
1986 N_("Close the current window"),
1987 G_CALLBACK (action_close_cb) },
1989 { "copy-clipboard",
1990 "edit-copy",
1991 N_("_Copy"),
1992 "<Control>c",
1993 N_("Copy the selection"),
1994 NULL }, /* Handled by EFocusTracker */
1996 { "cut-clipboard",
1997 "edit-cut",
1998 N_("Cu_t"),
1999 "<Control>x",
2000 N_("Cut the selection"),
2001 NULL }, /* Handled by EFocusTracker */
2003 { "delete-selection",
2004 "edit-delete",
2005 N_("_Delete"),
2006 NULL,
2007 N_("Delete the selection"),
2008 NULL }, /* Handled by EFocusTracker */
2010 { "help",
2011 "help-browser",
2012 N_("_Help"),
2013 "F1",
2014 N_("View help"),
2015 G_CALLBACK (action_help_cb) },
2017 { "paste-clipboard",
2018 "edit-paste",
2019 N_("_Paste"),
2020 "<Control>v",
2021 N_("Paste the clipboard"),
2022 NULL }, /* Handled by EFocusTracker */
2024 { "print",
2025 "document-print",
2026 N_("_Print..."),
2027 "<Control>p",
2028 NULL,
2029 G_CALLBACK (action_print_cb) },
2031 { "print-preview",
2032 "document-print-preview",
2033 N_("Pre_view..."),
2034 NULL,
2035 NULL,
2036 G_CALLBACK (action_print_preview_cb) },
2038 { "select-all",
2039 "edit-select-all",
2040 N_("Select _All"),
2041 "<Control>a",
2042 N_("Select all text"),
2043 NULL }, /* Handled by EFocusTracker */
2045 { "undo",
2046 "edit-undo",
2047 N_("_Undo"),
2048 "<Control>z",
2049 N_("Undo"),
2050 NULL }, /* Handled by EFocusTracker */
2052 { "redo",
2053 "edit-redo",
2054 N_("_Redo"),
2055 "<Control>y",
2056 N_("Redo"),
2057 NULL }, /* Handled by EFocusTracker */
2059 /* Menus */
2061 { "classification-menu",
2062 NULL,
2063 N_("_Classification"),
2064 NULL,
2065 NULL,
2066 NULL },
2068 { "edit-menu",
2069 NULL,
2070 N_("_Edit"),
2071 NULL,
2072 NULL,
2073 NULL },
2075 { "file-menu",
2076 NULL,
2077 N_("_File"),
2078 NULL,
2079 NULL,
2080 NULL },
2082 { "help-menu",
2083 NULL,
2084 N_("_Help"),
2085 NULL,
2086 NULL,
2087 NULL },
2089 { "insert-menu",
2090 NULL,
2091 N_("_Insert"),
2092 NULL,
2093 NULL,
2094 NULL },
2096 { "options-menu",
2097 NULL,
2098 N_("_Options"),
2099 NULL,
2100 NULL,
2101 NULL },
2103 { "view-menu",
2104 NULL,
2105 N_("_View"),
2106 NULL,
2107 NULL,
2108 NULL }
2111 GtkActionEntry editable_entries[] = {
2113 { "save",
2114 "document-save",
2115 N_("_Save"),
2116 "<Control>s",
2117 N_("Save current changes"),
2118 G_CALLBACK (action_save_cb) },
2120 { "save-and-close",
2121 NULL,
2122 N_("Save and Close"),
2123 NULL,
2124 N_("Save current changes and close editor"),
2125 G_CALLBACK (action_save_and_close_cb) }
2128 ECompEditor *comp_editor = E_COMP_EDITOR (object);
2129 GtkWidget *widget;
2130 GtkBox *vbox;
2131 GtkAction *action;
2132 GtkActionGroup *action_group;
2133 EFocusTracker *focus_tracker;
2134 GError *error = NULL;
2136 G_OBJECT_CLASS (e_comp_editor_parent_class)->constructed (object);
2138 g_signal_connect (comp_editor, "key-press-event",
2139 G_CALLBACK (e_util_check_gtk_bindings_in_key_press_event_cb), NULL);
2141 comp_editor->priv->calendar_settings = e_util_ref_settings ("org.gnome.evolution.calendar");
2142 comp_editor->priv->ui_manager = gtk_ui_manager_new ();
2144 gtk_window_add_accel_group (
2145 GTK_WINDOW (comp_editor),
2146 gtk_ui_manager_get_accel_group (comp_editor->priv->ui_manager));
2148 /* Setup Action Groups */
2150 action_group = gtk_action_group_new ("individual");
2151 gtk_action_group_set_translation_domain (
2152 action_group, GETTEXT_PACKAGE);
2153 gtk_ui_manager_insert_action_group (
2154 comp_editor->priv->ui_manager, action_group, 0);
2155 g_object_unref (action_group);
2157 action_group = gtk_action_group_new ("core");
2158 gtk_action_group_set_translation_domain (
2159 action_group, GETTEXT_PACKAGE);
2160 gtk_action_group_add_actions (
2161 action_group, core_entries,
2162 G_N_ELEMENTS (core_entries), comp_editor);
2163 gtk_ui_manager_insert_action_group (
2164 comp_editor->priv->ui_manager, action_group, 0);
2165 g_object_unref (action_group);
2167 action_group = gtk_action_group_new ("editable");
2168 gtk_action_group_set_translation_domain (
2169 action_group, GETTEXT_PACKAGE);
2170 gtk_action_group_add_actions (
2171 action_group, editable_entries,
2172 G_N_ELEMENTS (editable_entries), comp_editor);
2173 gtk_ui_manager_insert_action_group (
2174 comp_editor->priv->ui_manager, action_group, 0);
2175 g_object_unref (action_group);
2177 action = gtk_action_group_get_action (action_group, "save-and-close");
2178 if (action) {
2179 GtkAction *save_action;
2180 GIcon *icon;
2181 GIcon *emblemed_icon;
2182 GEmblem *emblem;
2184 icon = g_themed_icon_new ("window-close");
2185 emblemed_icon = g_themed_icon_new ("document-save");
2186 emblem = g_emblem_new (emblemed_icon);
2187 g_object_unref (emblemed_icon);
2189 emblemed_icon = g_emblemed_icon_new (icon, emblem);
2190 g_object_unref (emblem);
2191 g_object_unref (icon);
2193 gtk_action_set_gicon (action, emblemed_icon);
2195 g_object_unref (emblemed_icon);
2197 save_action = gtk_action_group_get_action (action_group, "save");
2198 e_binding_bind_property (
2199 save_action, "sensitive",
2200 action, "sensitive",
2201 G_BINDING_SYNC_CREATE);
2204 gtk_ui_manager_add_ui_from_string (comp_editor->priv->ui_manager, ui, -1, &error);
2205 if (error != NULL) {
2206 g_warning ("%s: %s", G_STRFUNC, error->message);
2207 g_error_free (error);
2210 widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
2211 g_object_set (G_OBJECT (widget),
2212 "hexpand", TRUE,
2213 "halign", GTK_ALIGN_FILL,
2214 "vexpand", TRUE,
2215 "valign", GTK_ALIGN_FILL,
2216 NULL);
2217 gtk_widget_show (widget);
2219 vbox = GTK_BOX (widget);
2221 gtk_container_add (GTK_CONTAINER (comp_editor), widget);
2223 widget = e_comp_editor_get_managed_widget (comp_editor, "/main-menu");
2224 gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
2225 gtk_widget_set_visible (widget, TRUE);
2227 widget = e_comp_editor_get_managed_widget (comp_editor, "/main-toolbar");
2228 gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
2229 gtk_widget_show (widget);
2231 gtk_style_context_add_class (
2232 gtk_widget_get_style_context (widget),
2233 GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
2235 widget = e_alert_bar_new ();
2236 g_object_set (G_OBJECT (widget),
2237 "hexpand", FALSE,
2238 "halign", GTK_ALIGN_FILL,
2239 "vexpand", FALSE,
2240 "valign", GTK_ALIGN_START,
2241 NULL);
2243 comp_editor->priv->alert_bar = E_ALERT_BAR (widget);
2245 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
2247 widget = e_activity_bar_new ();
2248 g_object_set (G_OBJECT (widget),
2249 "hexpand", FALSE,
2250 "halign", GTK_ALIGN_FILL,
2251 "vexpand", FALSE,
2252 "valign", GTK_ALIGN_START,
2253 NULL);
2255 comp_editor->priv->activity_bar = E_ACTIVITY_BAR (widget);
2257 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
2259 widget = gtk_notebook_new ();
2260 g_object_set (G_OBJECT (widget),
2261 "hexpand", TRUE,
2262 "halign", GTK_ALIGN_FILL,
2263 "vexpand", TRUE,
2264 "valign", GTK_ALIGN_FILL,
2265 "show-tabs", TRUE,
2266 "show-border", FALSE,
2267 NULL);
2268 gtk_widget_show (widget);
2270 comp_editor->priv->content = GTK_NOTEBOOK (widget);
2272 gtk_box_pack_start (vbox, widget, TRUE, TRUE, 0);
2274 /* Configure an EFocusTracker to manage selection actions. */
2276 focus_tracker = e_focus_tracker_new (GTK_WINDOW (comp_editor));
2278 action = e_comp_editor_get_action (comp_editor, "cut-clipboard");
2279 e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
2281 action = e_comp_editor_get_action (comp_editor, "copy-clipboard");
2282 e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
2284 action = e_comp_editor_get_action (comp_editor, "paste-clipboard");
2285 e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
2287 action = e_comp_editor_get_action (comp_editor, "delete-selection");
2288 e_focus_tracker_set_delete_selection_action (focus_tracker, action);
2290 action = e_comp_editor_get_action (comp_editor, "select-all");
2291 e_focus_tracker_set_select_all_action (focus_tracker, action);
2293 action = e_comp_editor_get_action (comp_editor, "undo");
2294 e_focus_tracker_set_undo_action (focus_tracker, action);
2296 action = e_comp_editor_get_action (comp_editor, "redo");
2297 e_focus_tracker_set_redo_action (focus_tracker, action);
2299 comp_editor->priv->focus_tracker = focus_tracker;
2301 /* Desensitize the "save" action. */
2302 action = e_comp_editor_get_action (comp_editor, "save");
2303 gtk_action_set_sensitive (action, FALSE);
2305 e_binding_bind_property (comp_editor, "changed", action, "sensitive", 0);
2307 g_signal_connect (comp_editor, "realize", G_CALLBACK (comp_editor_realize_cb), NULL);
2308 g_signal_connect (comp_editor, "unrealize", G_CALLBACK (comp_editor_unrealize_cb), NULL);
2310 gtk_application_add_window (GTK_APPLICATION (comp_editor->priv->shell), GTK_WINDOW (comp_editor));
2312 e_extensible_load_extensions (E_EXTENSIBLE (comp_editor));
2315 static void
2316 e_comp_editor_dispose (GObject *object)
2318 ECompEditor *comp_editor = E_COMP_EDITOR (object);
2320 if (comp_editor->priv->page_general) {
2321 g_signal_handlers_disconnect_by_func (comp_editor->priv->page_general,
2322 G_CALLBACK (comp_editor_selected_source_notify_cb), comp_editor);
2323 comp_editor->priv->page_general = NULL;
2326 if (comp_editor->priv->target_client_opening) {
2327 e_activity_cancel (comp_editor->priv->target_client_opening);
2328 g_clear_object (&comp_editor->priv->target_client_opening);
2331 g_slist_free_full (comp_editor->priv->pages, g_object_unref);
2332 comp_editor->priv->pages = NULL;
2334 g_free (comp_editor->priv->alarm_email_address);
2335 comp_editor->priv->alarm_email_address = NULL;
2337 g_free (comp_editor->priv->cal_email_address);
2338 comp_editor->priv->cal_email_address = NULL;
2340 g_free (comp_editor->priv->title_suffix);
2341 comp_editor->priv->title_suffix = NULL;
2343 if (comp_editor->priv->component) {
2344 icalcomponent_free (comp_editor->priv->component);
2345 comp_editor->priv->component = NULL;
2348 ece_connect_time_parts (comp_editor, NULL, NULL);
2350 g_clear_object (&comp_editor->priv->origin_source);
2351 g_clear_object (&comp_editor->priv->shell);
2352 g_clear_object (&comp_editor->priv->focus_tracker);
2353 g_clear_object (&comp_editor->priv->ui_manager);
2354 g_clear_object (&comp_editor->priv->source_client);
2355 g_clear_object (&comp_editor->priv->target_client);
2356 g_clear_object (&comp_editor->priv->calendar_settings);
2357 g_clear_object (&comp_editor->priv->validation_alert);
2359 comp_editor->priv->activity_bar = NULL;
2361 opened_editors = g_slist_remove (opened_editors, comp_editor);
2363 G_OBJECT_CLASS (e_comp_editor_parent_class)->dispose (object);
2366 static void
2367 e_comp_editor_init (ECompEditor *comp_editor)
2369 comp_editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (comp_editor, E_TYPE_COMP_EDITOR, ECompEditorPrivate);
2372 static void
2373 e_comp_editor_alert_sink_iface_init (EAlertSinkInterface *iface)
2375 iface->submit_alert = e_comp_editor_submit_alert;
2378 static void
2379 e_comp_editor_class_init (ECompEditorClass *klass)
2381 GtkWidgetClass *widget_class;
2382 GObjectClass *object_class;
2384 g_type_class_add_private (klass, sizeof (ECompEditorPrivate));
2386 klass->sensitize_widgets = ece_sensitize_widgets;
2387 klass->fill_widgets = ece_fill_widgets;
2388 klass->fill_component = ece_fill_component;
2390 widget_class = GTK_WIDGET_CLASS (klass);
2391 widget_class->delete_event = comp_editor_delete_event;
2392 widget_class->key_press_event = comp_editor_key_press_event;
2394 object_class = G_OBJECT_CLASS (klass);
2395 object_class->set_property = e_comp_editor_set_property;
2396 object_class->get_property = e_comp_editor_get_property;
2397 object_class->constructed = e_comp_editor_constructed;
2398 object_class->dispose = e_comp_editor_dispose;
2400 g_object_class_install_property (
2401 object_class,
2402 PROP_ALARM_EMAIL_ADDRESS,
2403 g_param_spec_string (
2404 "alarm-email-address",
2405 "Alarm Email Address",
2406 "Target client's alarm email address",
2407 NULL,
2408 G_PARAM_READWRITE |
2409 G_PARAM_STATIC_STRINGS));
2411 g_object_class_install_property (
2412 object_class,
2413 PROP_CAL_EMAIL_ADDRESS,
2414 g_param_spec_string (
2415 "cal-email-address",
2416 "Calendar Email Address",
2417 "Target client's calendar email address",
2418 NULL,
2419 G_PARAM_READWRITE |
2420 G_PARAM_STATIC_STRINGS));
2422 g_object_class_install_property (
2423 object_class,
2424 PROP_CHANGED,
2425 g_param_spec_boolean (
2426 "changed",
2427 "Changed",
2428 "Whether the editor content changed",
2429 FALSE,
2430 G_PARAM_READWRITE |
2431 G_PARAM_STATIC_STRINGS));
2433 g_object_class_install_property (
2434 object_class,
2435 PROP_COMPONENT,
2436 g_param_spec_pointer (
2437 "component",
2438 "Component",
2439 "icalcomponent currently edited",
2440 G_PARAM_READWRITE |
2441 G_PARAM_CONSTRUCT_ONLY |
2442 G_PARAM_STATIC_STRINGS));
2444 g_object_class_install_property (
2445 object_class,
2446 PROP_FLAGS,
2447 g_param_spec_uint (
2448 "flags",
2449 "Flags",
2450 "Editor flags",
2451 0, G_MAXUINT, 0,
2452 G_PARAM_READWRITE |
2453 G_PARAM_CONSTRUCT_ONLY |
2454 G_PARAM_STATIC_STRINGS));
2456 g_object_class_install_property (
2457 object_class,
2458 PROP_ORIGIN_SOURCE,
2459 g_param_spec_object (
2460 "origin-source",
2461 "Origin Source",
2462 "ESource of an ECalClient the component is stored in",
2463 E_TYPE_SOURCE,
2464 G_PARAM_READWRITE |
2465 G_PARAM_CONSTRUCT_ONLY |
2466 G_PARAM_STATIC_STRINGS));
2468 g_object_class_install_property (
2469 object_class,
2470 PROP_SHELL,
2471 g_param_spec_object (
2472 "shell",
2473 "Shell",
2474 "EShell",
2475 E_TYPE_SHELL,
2476 G_PARAM_READWRITE |
2477 G_PARAM_CONSTRUCT_ONLY |
2478 G_PARAM_STATIC_STRINGS));
2480 g_object_class_install_property (
2481 object_class,
2482 PROP_SOURCE_CLIENT,
2483 g_param_spec_object (
2484 "source-client",
2485 "Source Client",
2486 "ECalClient, the source calendar for the component",
2487 E_TYPE_CAL_CLIENT,
2488 G_PARAM_READWRITE |
2489 G_PARAM_STATIC_STRINGS));
2491 g_object_class_install_property (
2492 object_class,
2493 PROP_TARGET_CLIENT,
2494 g_param_spec_object (
2495 "target-client",
2496 "Target Client",
2497 "ECalClient currently set as the target calendar for the component",
2498 E_TYPE_CAL_CLIENT,
2499 G_PARAM_READWRITE |
2500 G_PARAM_STATIC_STRINGS));
2502 g_object_class_install_property (
2503 object_class,
2504 PROP_TITLE_SUFFIX,
2505 g_param_spec_string (
2506 "title-suffix",
2507 "Title Suffix",
2508 "Window title suffix, usually summary of the component",
2509 NULL,
2510 G_PARAM_READWRITE |
2511 G_PARAM_STATIC_STRINGS));
2513 signals[TIMES_CHANGED] = g_signal_new (
2514 "times-changed",
2515 G_TYPE_FROM_CLASS (klass),
2516 G_SIGNAL_RUN_FIRST,
2517 G_STRUCT_OFFSET (ECompEditorClass, times_changed),
2518 NULL, NULL, NULL,
2519 G_TYPE_NONE, 0,
2520 G_TYPE_NONE);
2522 signals[OBJECT_CREATED] = g_signal_new (
2523 "object-created",
2524 G_TYPE_FROM_CLASS (klass),
2525 G_SIGNAL_RUN_FIRST,
2526 G_STRUCT_OFFSET (ECompEditorClass, object_created),
2527 NULL, NULL, NULL,
2528 G_TYPE_NONE, 0,
2529 G_TYPE_NONE);
2531 signals[EDITOR_CLOSED] = g_signal_new (
2532 "editor-closed",
2533 G_TYPE_FROM_CLASS (klass),
2534 G_SIGNAL_RUN_LAST,
2535 G_STRUCT_OFFSET (ECompEditorClass, editor_closed),
2536 NULL, NULL,
2537 g_cclosure_marshal_VOID__BOOLEAN,
2538 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
2541 void
2542 e_comp_editor_sensitize_widgets (ECompEditor *comp_editor)
2544 ECompEditorClass *comp_editor_class;
2545 gboolean force_insensitive;
2546 GtkWidget *current_focus;
2548 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2550 comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor);
2551 g_return_if_fail (comp_editor_class != NULL);
2552 g_return_if_fail (comp_editor_class->sensitize_widgets != NULL);
2554 current_focus = gtk_window_get_focus (GTK_WINDOW (comp_editor));
2556 force_insensitive = !comp_editor->priv->component;
2558 if (!force_insensitive) {
2559 ECalClient *target_client;
2561 target_client = e_comp_editor_get_target_client (comp_editor);
2562 if (target_client) {
2563 EClient *client = E_CLIENT (target_client);
2565 if (e_client_is_readonly (client)) {
2566 force_insensitive = TRUE;
2567 } else {
2568 if (!e_cal_util_component_has_organizer (comp_editor->priv->component) ||
2569 ece_organizer_is_user (comp_editor, comp_editor->priv->component, client) ||
2570 ece_sentby_is_user (comp_editor, comp_editor->priv->component, client)) {
2571 comp_editor->priv->flags = comp_editor->priv->flags | E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER;
2572 } else {
2573 comp_editor->priv->flags = comp_editor->priv->flags & (~E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER);
2576 } else {
2577 force_insensitive = TRUE;
2581 comp_editor_class->sensitize_widgets (comp_editor, force_insensitive);
2583 if (force_insensitive)
2584 comp_editor->priv->restore_focus = current_focus;
2585 else
2586 ece_restore_focus (comp_editor);
2589 void
2590 e_comp_editor_fill_widgets (ECompEditor *comp_editor,
2591 icalcomponent *component)
2593 ECompEditorClass *comp_editor_class;
2595 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2596 g_return_if_fail (component != NULL);
2598 comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor);
2599 g_return_if_fail (comp_editor_class != NULL);
2600 g_return_if_fail (comp_editor_class->fill_widgets != NULL);
2602 e_comp_editor_set_updating (comp_editor, TRUE);
2604 comp_editor_class->fill_widgets (comp_editor, component);
2606 e_comp_editor_set_updating (comp_editor, FALSE);
2609 gboolean
2610 e_comp_editor_fill_component (ECompEditor *comp_editor,
2611 icalcomponent *component)
2613 ECompEditorClass *comp_editor_class;
2614 gboolean is_valid;
2616 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
2617 g_return_val_if_fail (component != NULL, FALSE);
2619 comp_editor_class = E_COMP_EDITOR_GET_CLASS (comp_editor);
2620 g_return_val_if_fail (comp_editor_class != NULL, FALSE);
2621 g_return_val_if_fail (comp_editor_class->fill_component != NULL, FALSE);
2623 is_valid = comp_editor_class->fill_component (comp_editor, component);
2625 if (is_valid && comp_editor->priv->validation_alert) {
2626 e_alert_response (comp_editor->priv->validation_alert, GTK_RESPONSE_CLOSE);
2627 g_clear_object (&comp_editor->priv->validation_alert);
2630 if (is_valid) {
2631 ECalClient *target_client;
2632 EClient *client = NULL;
2634 target_client = e_comp_editor_get_target_client (comp_editor);
2635 if (target_client)
2636 client = E_CLIENT (target_client);
2638 if (!e_cal_util_component_has_organizer (component) || (client && (
2639 ece_organizer_is_user (comp_editor, component, client) ||
2640 ece_sentby_is_user (comp_editor, component, client)))) {
2641 gint sequence;
2643 sequence = icalcomponent_get_sequence (component);
2644 icalcomponent_set_sequence (component, sequence + 1);
2648 return is_valid;
2651 void
2652 e_comp_editor_set_validation_error (ECompEditor *comp_editor,
2653 ECompEditorPage *error_page,
2654 GtkWidget *error_widget,
2655 const gchar *error_message)
2657 EAlert *alert, *previous_alert;
2659 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2660 g_return_if_fail (error_message != NULL);
2662 /* Ignore validation errors when the inner editor is currently updating. */
2663 if (e_comp_editor_get_updating (comp_editor))
2664 return;
2666 alert = e_alert_new ("calendar:comp-editor-failed-validate", error_message, NULL);
2668 e_alert_bar_add_alert (comp_editor->priv->alert_bar, alert);
2670 previous_alert = comp_editor->priv->validation_alert;
2671 comp_editor->priv->validation_alert = alert;
2673 if (previous_alert) {
2674 e_alert_response (previous_alert, GTK_RESPONSE_CLOSE);
2675 g_clear_object (&previous_alert);
2678 if (error_page)
2679 e_comp_editor_select_page (comp_editor, error_page);
2681 if (error_widget)
2682 gtk_widget_grab_focus (error_widget);
2685 EShell *
2686 e_comp_editor_get_shell (ECompEditor *comp_editor)
2688 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2690 return comp_editor->priv->shell;
2693 GSettings *
2694 e_comp_editor_get_settings (ECompEditor *comp_editor)
2696 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2698 return comp_editor->priv->calendar_settings;
2701 ESource *
2702 e_comp_editor_get_origin_source (ECompEditor *comp_editor)
2704 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2706 return comp_editor->priv->origin_source;
2709 icalcomponent *
2710 e_comp_editor_get_component (ECompEditor *comp_editor)
2712 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2714 return comp_editor->priv->component;
2717 guint32
2718 e_comp_editor_get_flags (ECompEditor *comp_editor)
2720 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), 0);
2722 return comp_editor->priv->flags;
2725 void
2726 e_comp_editor_set_flags (ECompEditor *comp_editor,
2727 guint32 flags)
2729 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2731 if (comp_editor->priv->flags == flags)
2732 return;
2734 comp_editor->priv->flags = flags;
2736 g_object_notify (G_OBJECT (comp_editor), "flags");
2739 EFocusTracker *
2740 e_comp_editor_get_focus_tracker (ECompEditor *comp_editor)
2742 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2744 return comp_editor->priv->focus_tracker;
2747 GtkUIManager *
2748 e_comp_editor_get_ui_manager (ECompEditor *comp_editor)
2750 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2752 return comp_editor->priv->ui_manager;
2755 GtkAction *
2756 e_comp_editor_get_action (ECompEditor *comp_editor,
2757 const gchar *action_name)
2759 GtkUIManager *ui_manager;
2761 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2762 g_return_val_if_fail (action_name != NULL, NULL);
2764 ui_manager = e_comp_editor_get_ui_manager (comp_editor);
2766 return e_lookup_action (ui_manager, action_name);
2769 GtkActionGroup *
2770 e_comp_editor_get_action_group (ECompEditor *comp_editor,
2771 const gchar *group_name)
2773 GtkUIManager *ui_manager;
2775 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2776 g_return_val_if_fail (group_name != NULL, NULL);
2778 ui_manager = e_comp_editor_get_ui_manager (comp_editor);
2780 return e_lookup_action_group (ui_manager, group_name);
2783 GtkWidget *
2784 e_comp_editor_get_managed_widget (ECompEditor *comp_editor,
2785 const gchar *widget_path)
2787 GtkUIManager *ui_manager;
2788 GtkWidget *widget;
2790 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2791 g_return_val_if_fail (widget_path != NULL, NULL);
2793 ui_manager = e_comp_editor_get_ui_manager (comp_editor);
2794 widget = gtk_ui_manager_get_widget (ui_manager, widget_path);
2795 g_return_val_if_fail (widget != NULL, NULL);
2797 return widget;
2800 const gchar *
2801 e_comp_editor_get_alarm_email_address (ECompEditor *comp_editor)
2803 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2805 return comp_editor->priv->alarm_email_address;
2808 void
2809 e_comp_editor_set_alarm_email_address (ECompEditor *comp_editor,
2810 const gchar *alarm_email_address)
2812 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2814 if (g_strcmp0 (alarm_email_address, comp_editor->priv->alarm_email_address) == 0)
2815 return;
2817 g_free (comp_editor->priv->alarm_email_address);
2818 comp_editor->priv->alarm_email_address = g_strdup (alarm_email_address);
2820 g_object_notify (G_OBJECT (comp_editor), "alarm-email-address");
2823 const gchar *
2824 e_comp_editor_get_cal_email_address (ECompEditor *comp_editor)
2826 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2828 return comp_editor->priv->cal_email_address;
2831 void
2832 e_comp_editor_set_cal_email_address (ECompEditor *comp_editor,
2833 const gchar *cal_email_address)
2835 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2837 if (g_strcmp0 (cal_email_address, comp_editor->priv->cal_email_address) == 0)
2838 return;
2840 g_free (comp_editor->priv->cal_email_address);
2841 comp_editor->priv->cal_email_address = g_strdup (cal_email_address);
2843 g_object_notify (G_OBJECT (comp_editor), "cal-email-address");
2846 gboolean
2847 e_comp_editor_get_changed (ECompEditor *comp_editor)
2849 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
2851 return comp_editor->priv->changed;
2854 void
2855 e_comp_editor_set_changed (ECompEditor *comp_editor,
2856 gboolean changed)
2858 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2860 if ((changed ? 1 : 0) == (comp_editor->priv->changed ? 1 : 0))
2861 return;
2863 comp_editor->priv->changed = changed;
2865 g_object_notify (G_OBJECT (comp_editor), "changed");
2868 void
2869 e_comp_editor_ensure_changed (ECompEditor *comp_editor)
2871 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2873 e_comp_editor_set_changed (comp_editor, TRUE);
2876 gboolean
2877 e_comp_editor_get_updating (ECompEditor *comp_editor)
2879 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
2881 return comp_editor->priv->updating > 0;
2884 void
2885 e_comp_editor_set_updating (ECompEditor *comp_editor,
2886 gboolean updating)
2888 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2890 if (updating) {
2891 comp_editor->priv->updating++;
2892 } else if (comp_editor->priv->updating > 0) {
2893 comp_editor->priv->updating--;
2894 } else {
2895 g_warn_if_reached ();
2899 ECalClient *
2900 e_comp_editor_get_source_client (ECompEditor *comp_editor)
2902 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2904 return comp_editor->priv->source_client;
2907 void
2908 e_comp_editor_set_source_client (ECompEditor *comp_editor,
2909 ECalClient *client)
2911 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2913 if (client == comp_editor->priv->source_client)
2914 return;
2916 if (client)
2917 g_object_ref (client);
2918 g_clear_object (&comp_editor->priv->source_client);
2919 comp_editor->priv->source_client = client;
2921 g_object_notify (G_OBJECT (comp_editor), "source-client");
2924 ECalClient *
2925 e_comp_editor_get_target_client (ECompEditor *comp_editor)
2927 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2929 return comp_editor->priv->target_client;
2932 void
2933 e_comp_editor_set_target_client (ECompEditor *comp_editor,
2934 ECalClient *client)
2936 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2938 if (client == comp_editor->priv->target_client)
2939 return;
2941 if (client)
2942 g_object_ref (client);
2943 g_clear_object (&comp_editor->priv->target_client);
2944 comp_editor->priv->target_client = client;
2946 g_object_notify (G_OBJECT (comp_editor), "target-client");
2948 if (client && !comp_editor->priv->source_client && comp_editor->priv->origin_source &&
2949 e_source_equal (e_client_get_source (E_CLIENT (client)), comp_editor->priv->origin_source))
2950 e_comp_editor_set_source_client (comp_editor, client);
2952 e_comp_editor_sensitize_widgets (comp_editor);
2955 const gchar *
2956 e_comp_editor_get_title_suffix (ECompEditor *comp_editor)
2958 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
2960 return comp_editor->priv->title_suffix;
2963 void
2964 e_comp_editor_set_title_suffix (ECompEditor *comp_editor,
2965 const gchar *title_suffix)
2967 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2969 if (g_strcmp0 (title_suffix, comp_editor->priv->title_suffix) == 0)
2970 return;
2972 g_free (comp_editor->priv->title_suffix);
2973 comp_editor->priv->title_suffix = g_strdup (title_suffix);
2975 g_object_notify (G_OBJECT (comp_editor), "title-suffix");
2977 e_comp_editor_update_window_title (comp_editor);
2980 void
2981 e_comp_editor_set_time_parts (ECompEditor *comp_editor,
2982 ECompEditorPropertyPart *dtstart_part,
2983 ECompEditorPropertyPart *dtend_part)
2985 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
2987 if (dtstart_part)
2988 g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (dtstart_part));
2989 if (dtend_part)
2990 g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (dtend_part));
2992 ece_connect_time_parts (comp_editor, dtstart_part, dtend_part);
2995 void
2996 e_comp_editor_get_time_parts (ECompEditor *comp_editor,
2997 ECompEditorPropertyPart **out_dtstart_part,
2998 ECompEditorPropertyPart **out_dtend_part)
3000 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
3002 if (out_dtstart_part)
3003 *out_dtstart_part = comp_editor->priv->dtstart_part;
3004 if (out_dtend_part)
3005 *out_dtend_part = comp_editor->priv->dtend_part;
3008 /* This consumes the @page. */
3009 void
3010 e_comp_editor_add_page (ECompEditor *comp_editor,
3011 const gchar *label,
3012 ECompEditorPage *page)
3014 ECompEditor *pages_comp_editor;
3016 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
3017 g_return_if_fail (label != NULL);
3018 g_return_if_fail (E_IS_COMP_EDITOR_PAGE (page));
3020 pages_comp_editor = e_comp_editor_page_ref_editor (page);
3021 if (pages_comp_editor != comp_editor) {
3022 g_warn_if_fail (pages_comp_editor == comp_editor);
3023 g_clear_object (&pages_comp_editor);
3024 return;
3027 g_clear_object (&pages_comp_editor);
3029 /* One reference uses the GtkNotebook, the other the pages GSList */
3030 gtk_notebook_append_page (comp_editor->priv->content,
3031 GTK_WIDGET (page),
3032 gtk_label_new_with_mnemonic (label));
3034 comp_editor->priv->pages = g_slist_append (comp_editor->priv->pages, g_object_ref (page));
3036 g_signal_connect_swapped (page, "changed", G_CALLBACK (e_comp_editor_ensure_changed), comp_editor);
3038 if (E_IS_COMP_EDITOR_PAGE_GENERAL (page)) {
3039 ECompEditorPageGeneral *page_general;
3041 g_return_if_fail (comp_editor->priv->page_general == NULL);
3043 page_general = E_COMP_EDITOR_PAGE_GENERAL (page);
3045 g_signal_connect (page_general, "notify::selected-source",
3046 G_CALLBACK (comp_editor_selected_source_notify_cb), comp_editor);
3048 comp_editor->priv->page_general = page_general;
3050 if ((comp_editor->priv->flags & E_COMP_EDITOR_FLAG_WITH_ATTENDEES) != 0) {
3051 e_comp_editor_page_general_set_show_attendees (page_general, TRUE);
3056 /* The returned pointer is owned by the @comp_editor; returns the first instance,
3057 in order of the addition. */
3058 ECompEditorPage *
3059 e_comp_editor_get_page (ECompEditor *comp_editor,
3060 GType page_type)
3062 GSList *link;
3064 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
3065 g_return_val_if_fail (g_type_is_a (page_type, E_TYPE_COMP_EDITOR_PAGE), NULL);
3066 g_return_val_if_fail (page_type != E_TYPE_COMP_EDITOR_PAGE, NULL);
3068 for (link = comp_editor->priv->pages; link; link = g_slist_next (link)) {
3069 ECompEditorPage *page = link->data;
3071 if (G_TYPE_CHECK_INSTANCE_TYPE (page, page_type))
3072 return page;
3075 return NULL;
3078 /* Free the returned GSList with g_slist_free(), the memebers are owned by the comp_editor */
3079 GSList *
3080 e_comp_editor_get_pages (ECompEditor *comp_editor)
3082 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
3084 return g_slist_copy (comp_editor->priv->pages);
3087 void
3088 e_comp_editor_select_page (ECompEditor *comp_editor,
3089 ECompEditorPage *page)
3091 gint page_num;
3093 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
3094 g_return_if_fail (E_IS_COMP_EDITOR_PAGE (page));
3096 page_num = gtk_notebook_page_num (comp_editor->priv->content, GTK_WIDGET (page));
3097 g_return_if_fail (page_num != -1);
3099 gtk_notebook_set_current_page (comp_editor->priv->content, page_num);
3102 /* Unref returned pointer when done with it. */
3103 static EAlert *
3104 e_comp_editor_add_alert (ECompEditor *comp_editor,
3105 const gchar *alert_id,
3106 const gchar *primary_text,
3107 const gchar *secondary_text)
3109 EAlert *alert;
3111 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
3112 g_return_val_if_fail (alert_id != NULL, NULL);
3113 g_return_val_if_fail (primary_text != NULL || secondary_text != NULL, NULL);
3115 alert = e_alert_new (alert_id,
3116 primary_text ? primary_text : "",
3117 secondary_text ? secondary_text : "",
3118 NULL);
3120 e_alert_bar_add_alert (comp_editor->priv->alert_bar, alert);
3122 return alert;
3125 /* Unref returned pointer when done with it. */
3126 EAlert *
3127 e_comp_editor_add_information (ECompEditor *comp_editor,
3128 const gchar *primary_text,
3129 const gchar *secondary_text)
3131 return e_comp_editor_add_alert (comp_editor, "calendar:comp-editor-information", primary_text, secondary_text);
3134 /* Unref returned pointer when done with it. */
3135 EAlert *
3136 e_comp_editor_add_warning (ECompEditor *comp_editor,
3137 const gchar *primary_text,
3138 const gchar *secondary_text)
3140 return e_comp_editor_add_alert (comp_editor, "calendar:comp-editor-warning", primary_text, secondary_text);
3143 /* Unref returned pointer when done with it. */
3144 EAlert *
3145 e_comp_editor_add_error (ECompEditor *comp_editor,
3146 const gchar *primary_text,
3147 const gchar *secondary_text)
3149 return e_comp_editor_add_alert (comp_editor, "calendar:comp-editor-error", primary_text, secondary_text);
3153 static gboolean
3154 ece_check_start_before_end (ECompEditor *comp_editor,
3155 struct icaltimetype *start_tt,
3156 struct icaltimetype *end_tt,
3157 gboolean adjust_end_time)
3159 struct icaltimetype end_tt_copy;
3160 icaltimezone *start_zone, *end_zone;
3161 gint duration = -1;
3162 gint cmp;
3164 if ((e_comp_editor_get_flags (comp_editor) & E_COMP_EDITOR_FLAG_IS_NEW) == 0) {
3165 icalcomponent *icomp;
3167 icomp = e_comp_editor_get_component (comp_editor);
3168 if (icomp &&
3169 icalcomponent_get_first_property (icomp, ICAL_DTSTART_PROPERTY) &&
3170 (icalcomponent_get_first_property (icomp, ICAL_DTEND_PROPERTY) ||
3171 icalcomponent_get_first_property (icomp, ICAL_DUE_PROPERTY))) {
3172 struct icaltimetype orig_start, orig_end;
3174 orig_start = icalcomponent_get_dtstart (icomp);
3175 orig_end = icalcomponent_get_dtend (icomp);
3177 if (icaltime_is_valid_time (orig_start) &&
3178 icaltime_is_valid_time (orig_end)) {
3179 duration = icaltime_as_timet (orig_end) - icaltime_as_timet (orig_start);
3184 start_zone = (icaltimezone *) start_tt->zone;
3185 end_zone = (icaltimezone *) end_tt->zone;
3187 /* Convert the end time to the same timezone as the start time. */
3188 end_tt_copy = *end_tt;
3190 if (start_zone && end_zone && start_zone != end_zone)
3191 icaltimezone_convert_time (&end_tt_copy, end_zone, start_zone);
3193 /* Now check if the start time is after the end time. If it is,
3194 * we need to modify one of the times. */
3195 cmp = icaltime_compare (*start_tt, end_tt_copy);
3196 if (cmp > 0) {
3197 if (adjust_end_time) {
3198 /* Try to switch only the date */
3199 end_tt->year = start_tt->year;
3200 end_tt->month = start_tt->month;
3201 end_tt->day = start_tt->day;
3203 end_tt_copy = *end_tt;
3204 if (start_zone && end_zone && start_zone != end_zone)
3205 icaltimezone_convert_time (&end_tt_copy, end_zone, start_zone);
3207 if (duration > 0)
3208 icaltime_adjust (&end_tt_copy, 0, 0, 0, -duration);
3210 if (icaltime_compare (*start_tt, end_tt_copy) >= 0) {
3211 *end_tt = *start_tt;
3213 if (duration >= 0) {
3214 icaltime_adjust (end_tt, 0, 0, 0, duration);
3215 } else {
3216 /* Modify the end time, to be the start + 1 hour/day. */
3217 icaltime_adjust (end_tt, 0, start_tt->is_date ? 24 : 1, 0, 0);
3220 if (start_zone && end_zone && start_zone != end_zone)
3221 icaltimezone_convert_time (end_tt, start_zone, end_zone);
3223 } else {
3224 /* Try to switch only the date */
3225 start_tt->year = end_tt->year;
3226 start_tt->month = end_tt->month;
3227 start_tt->day = end_tt->day;
3229 if (icaltime_compare (*start_tt, end_tt_copy) >= 0) {
3230 *start_tt = *end_tt;
3232 if (duration >= 0) {
3233 icaltime_adjust (start_tt, 0, 0, 0, -duration);
3234 } else {
3235 /* Modify the start time, to be the end - 1 hour/day. */
3236 icaltime_adjust (start_tt, 0, start_tt->is_date ? -24 : -1, 0, 0);
3239 if (start_zone && end_zone && start_zone != end_zone)
3240 icaltimezone_convert_time (start_tt, end_zone, start_zone);
3244 return TRUE;
3247 return FALSE;
3250 void
3251 e_comp_editor_ensure_start_before_end (ECompEditor *comp_editor,
3252 ECompEditorPropertyPart *start_datetime,
3253 ECompEditorPropertyPart *end_datetime,
3254 gboolean change_end_datetime)
3256 ECompEditorPropertyPartDatetime *start_dtm, *end_dtm;
3257 struct icaltimetype start_tt, end_tt;
3258 gboolean set_dtstart = FALSE, set_dtend = FALSE;
3260 g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
3261 g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (start_datetime));
3262 g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_DATETIME (end_datetime));
3264 start_dtm = E_COMP_EDITOR_PROPERTY_PART_DATETIME (start_datetime);
3265 end_dtm = E_COMP_EDITOR_PROPERTY_PART_DATETIME (end_datetime);
3267 start_tt = e_comp_editor_property_part_datetime_get_value (start_dtm);
3268 end_tt = e_comp_editor_property_part_datetime_get_value (end_dtm);
3270 if (icaltime_is_null_time (start_tt) ||
3271 icaltime_is_null_time (end_tt) ||
3272 !icaltime_is_valid_time (start_tt) ||
3273 !icaltime_is_valid_time (end_tt))
3274 return;
3276 if (start_tt.is_date || end_tt.is_date) {
3277 /* All Day Events are simple. We just compare the dates and if
3278 * start > end we copy one of them to the other. */
3279 gint cmp;
3281 start_tt.is_date = TRUE;
3282 end_tt.is_date = TRUE;
3284 cmp = icaltime_compare_date_only (start_tt, end_tt);
3286 if (cmp > 0) {
3287 if (change_end_datetime) {
3288 end_tt = start_tt;
3289 set_dtend = TRUE;
3290 } else {
3291 start_tt = end_tt;
3292 set_dtstart = TRUE;
3295 } else {
3296 if (ece_check_start_before_end (comp_editor, &start_tt, &end_tt, change_end_datetime)) {
3297 if (change_end_datetime)
3298 set_dtend = TRUE;
3299 else
3300 set_dtstart = TRUE;
3304 if (set_dtstart || set_dtend) {
3305 e_comp_editor_set_updating (comp_editor, TRUE);
3307 if (set_dtstart)
3308 e_comp_editor_property_part_datetime_set_value (start_dtm, start_tt);
3310 if (set_dtend)
3311 e_comp_editor_property_part_datetime_set_value (end_dtm, end_tt);
3313 e_comp_editor_set_updating (comp_editor, FALSE);
3317 static gboolean
3318 e_comp_editor_holds_component (ECompEditor *comp_editor,
3319 ESource *origin_source,
3320 const icalcomponent *component)
3322 const gchar *component_uid, *editor_uid;
3323 gboolean equal;
3325 g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
3326 g_return_val_if_fail (component != NULL, FALSE);
3328 if (!origin_source || !comp_editor->priv->origin_source ||
3329 !e_source_equal (origin_source, comp_editor->priv->origin_source))
3330 return FALSE;
3332 component_uid = icalcomponent_get_uid ((icalcomponent *) component);
3333 editor_uid = icalcomponent_get_uid (comp_editor->priv->component);
3335 if (!component_uid || !editor_uid)
3336 return FALSE;
3338 equal = g_strcmp0 (component_uid, editor_uid) == 0;
3339 if (equal) {
3340 struct icaltimetype component_rid, editor_rid;
3342 component_rid = icalcomponent_get_recurrenceid ((icalcomponent *) component);
3343 editor_rid = icalcomponent_get_recurrenceid (comp_editor->priv->component);
3345 if (icaltime_is_null_time (component_rid)) {
3346 equal = icaltime_is_null_time (editor_rid);
3347 } else if (!icaltime_is_null_time (editor_rid)) {
3348 equal = icaltime_compare (component_rid, editor_rid) == 0;
3352 return equal;
3355 ECompEditor *
3356 e_comp_editor_open_for_component (GtkWindow *parent,
3357 EShell *shell,
3358 ESource *origin_source,
3359 const icalcomponent *component,
3360 guint32 flags /* bit-or of ECompEditorFlags */)
3362 ECompEditor *comp_editor;
3363 GType comp_editor_type;
3365 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
3366 if (origin_source)
3367 g_return_val_if_fail (E_IS_SOURCE (origin_source), NULL);
3368 g_return_val_if_fail (component != NULL, NULL);
3370 comp_editor = e_comp_editor_find_existing_for (origin_source, component);
3371 if (comp_editor) {
3372 gtk_window_present (GTK_WINDOW (comp_editor));
3373 return comp_editor;
3376 switch (icalcomponent_isa (component)) {
3377 case ICAL_VEVENT_COMPONENT:
3378 comp_editor_type = E_TYPE_COMP_EDITOR_EVENT;
3379 break;
3380 case ICAL_VTODO_COMPONENT:
3381 comp_editor_type = E_TYPE_COMP_EDITOR_TASK;
3382 break;
3383 case ICAL_VJOURNAL_COMPONENT:
3384 comp_editor_type = E_TYPE_COMP_EDITOR_MEMO;
3385 break;
3386 default:
3387 g_warn_if_reached ();
3388 return NULL;
3391 comp_editor = g_object_new (comp_editor_type,
3392 "shell", shell,
3393 "origin-source", origin_source,
3394 "component", component,
3395 "flags", flags,
3396 NULL);
3398 opened_editors = g_slist_prepend (opened_editors, comp_editor);
3400 gtk_widget_show (GTK_WIDGET (comp_editor));
3402 return comp_editor;
3405 ECompEditor *
3406 e_comp_editor_find_existing_for (ESource *origin_source,
3407 const icalcomponent *component)
3409 ECompEditor *comp_editor;
3410 GSList *link;
3412 if (origin_source)
3413 g_return_val_if_fail (E_IS_SOURCE (origin_source), NULL);
3414 g_return_val_if_fail (component != NULL, NULL);
3416 for (link = opened_editors; link; link = g_slist_next (link)) {
3417 comp_editor = link->data;
3419 if (!comp_editor)
3420 continue;
3422 if (e_comp_editor_holds_component (comp_editor, origin_source, component)) {
3423 gtk_window_present (GTK_WINDOW (comp_editor));
3424 return comp_editor;
3428 return NULL;