evolution-3.6.4/modules/calendar/e-cal-shell-sidebar.c

No issues found

   1 /*
   2  * e-cal-shell-sidebar.c
   3  *
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  *
  18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  19  *
  20  */
  21 
  22 #ifdef HAVE_CONFIG_H
  23 #include <config.h>
  24 #endif
  25 
  26 #include "e-cal-shell-sidebar.h"
  27 
  28 #include <string.h>
  29 #include <glib/gi18n.h>
  30 #include <libedataserverui/libedataserverui.h>
  31 
  32 #include "libevolution-utils/e-alert-dialog.h"
  33 #include "widgets/misc/e-paned.h"
  34 
  35 #include "calendar/gui/e-calendar-selector.h"
  36 #include "calendar/gui/misc.h"
  37 
  38 #include "e-cal-shell-view.h"
  39 #include "e-cal-shell-backend.h"
  40 #include "e-cal-shell-content.h"
  41 
  42 #define E_CAL_SHELL_SIDEBAR_GET_PRIVATE(obj) \
  43 	(G_TYPE_INSTANCE_GET_PRIVATE \
  44 	((obj), E_TYPE_CAL_SHELL_SIDEBAR, ECalShellSidebarPrivate))
  45 
  46 struct _ECalShellSidebarPrivate {
  47 	GtkWidget *paned;
  48 	GtkWidget *selector;
  49 	GtkWidget *date_navigator;
  50 	GtkWidget *new_calendar_button;
  51 
  52 	/* UID -> Client */
  53 	GHashTable *client_table;
  54 
  55 	/* The default client is for ECalModel.  It follows the
  56 	 * sidebar's primary selection, even if the highlighted
  57 	 * source is not selected.  The tricky part is we don't
  58 	 * update the property until the client is successfully
  59 	 * opened.  So the user first highlights a source, then
  60 	 * sometime later we update our default-client property
  61 	 * which is bound by an EBinding to ECalModel. */
  62 	ECalClient *default_client;
  63 
  64 	ESource *loading_default_source_instance; /* not-reffed, only for comparison */
  65 
  66 	GCancellable *loading_default_client;
  67 	GCancellable *loading_clients;
  68 };
  69 
  70 enum {
  71 	PROP_0,
  72 	PROP_DATE_NAVIGATOR,
  73 	PROP_DEFAULT_CLIENT,
  74 	PROP_SELECTOR
  75 };
  76 
  77 enum {
  78 	CLIENT_ADDED,
  79 	CLIENT_REMOVED,
  80 	STATUS_MESSAGE,
  81 	LAST_SIGNAL
  82 };
  83 
  84 static guint signals[LAST_SIGNAL];
  85 
  86 G_DEFINE_DYNAMIC_TYPE (
  87 	ECalShellSidebar,
  88 	e_cal_shell_sidebar,
  89 	E_TYPE_SHELL_SIDEBAR)
  90 
  91 static void
  92 cal_shell_sidebar_emit_client_added (ECalShellSidebar *cal_shell_sidebar,
  93                                      ECalClient *client)
  94 {
  95 	guint signal_id = signals[CLIENT_ADDED];
  96 
  97 	g_signal_emit (cal_shell_sidebar, signal_id, 0, client);
  98 }
  99 
 100 static void
 101 cal_shell_sidebar_emit_client_removed (ECalShellSidebar *cal_shell_sidebar,
 102                                        ECalClient *client)
 103 {
 104 	guint signal_id = signals[CLIENT_REMOVED];
 105 
 106 	g_signal_emit (cal_shell_sidebar, signal_id, 0, client);
 107 }
 108 
 109 static void
 110 cal_shell_sidebar_emit_status_message (ECalShellSidebar *cal_shell_sidebar,
 111                                        const gchar *status_message)
 112 {
 113 	guint signal_id = signals[STATUS_MESSAGE];
 114 
 115 	g_signal_emit (cal_shell_sidebar, signal_id, 0, status_message);
 116 }
 117 
 118 static void
 119 cal_shell_sidebar_backend_died_cb (ECalShellSidebar *cal_shell_sidebar,
 120                                    ECalClient *client)
 121 {
 122 	EShellView *shell_view;
 123 	EShellContent *shell_content;
 124 	EShellSidebar *shell_sidebar;
 125 	GHashTable *client_table;
 126 	ESource *source;
 127 	const gchar *uid;
 128 
 129 	client_table = cal_shell_sidebar->priv->client_table;
 130 
 131 	shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
 132 	shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
 133 	shell_content = e_shell_view_get_shell_content (shell_view);
 134 
 135 	source = e_client_get_source (E_CLIENT (client));
 136 	uid = e_source_get_uid (source);
 137 
 138 	g_object_ref (source);
 139 
 140 	g_hash_table_remove (client_table, uid);
 141 	cal_shell_sidebar_emit_status_message (cal_shell_sidebar, NULL);
 142 
 143 	e_alert_submit (
 144 		E_ALERT_SINK (shell_content),
 145 		"calendar:calendar-crashed", NULL);
 146 
 147 	g_object_unref (source);
 148 }
 149 
 150 static void
 151 cal_shell_sidebar_backend_error_cb (ECalShellSidebar *cal_shell_sidebar,
 152                                     const gchar *message,
 153                                     ECalClient *client)
 154 {
 155 	EShell *shell;
 156 	EShellView *shell_view;
 157 	EShellBackend *shell_backend;
 158 	EShellContent *shell_content;
 159 	EShellSidebar *shell_sidebar;
 160 	ESourceRegistry *registry;
 161 	ESource *parent;
 162 	ESource *source;
 163 	const gchar *parent_uid;
 164 	const gchar *parent_display_name;
 165 	const gchar *source_display_name;
 166 
 167 	shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
 168 	shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
 169 	shell_backend = e_shell_view_get_shell_backend (shell_view);
 170 	shell_content = e_shell_view_get_shell_content (shell_view);
 171 
 172 	shell = e_shell_backend_get_shell (shell_backend);
 173 	registry = e_shell_get_registry (shell);
 174 
 175 	source = e_client_get_source (E_CLIENT (client));
 176 
 177 	parent_uid = e_source_get_parent (source);
 178 	parent = e_source_registry_ref_source (registry, parent_uid);
 179 	g_return_if_fail (parent != NULL);
 180 
 181 	parent_display_name = e_source_get_display_name (parent);
 182 	source_display_name = e_source_get_display_name (source);
 183 
 184 	e_alert_submit (
 185 		E_ALERT_SINK (shell_content),
 186 		"calendar:backend-error",
 187 		parent_display_name,
 188 		source_display_name,
 189 		message, NULL);
 190 
 191 	g_object_unref (parent);
 192 }
 193 
 194 static void
 195 cal_shell_sidebar_retrieve_capabilies_cb (GObject *source_object,
 196                                           GAsyncResult *result,
 197                                           gpointer user_data)
 198 {
 199 	ECalClient *client = E_CAL_CLIENT (source_object);
 200 	ECalShellSidebar *cal_shell_sidebar = user_data;
 201 	gchar *capabilities = NULL;
 202 
 203 	g_return_if_fail (client != NULL);
 204 	g_return_if_fail (cal_shell_sidebar != NULL);
 205 
 206 	e_client_retrieve_capabilities_finish (
 207 		E_CLIENT (client), result, &capabilities, NULL);
 208 	g_free (capabilities);
 209 
 210 	cal_shell_sidebar_emit_status_message (
 211 		cal_shell_sidebar, _("Loading calendars"));
 212 	cal_shell_sidebar_emit_client_added (cal_shell_sidebar, client);
 213 	cal_shell_sidebar_emit_status_message (cal_shell_sidebar, NULL);
 214 }
 215 
 216 static gboolean cal_shell_sidebar_retry_open_timeout_cb (gpointer user_data);
 217 
 218 struct RetryOpenData
 219 {
 220 	EClient *client;
 221 	ECalShellSidebar *cal_shell_sidebar;
 222 	GCancellable *cancellable;
 223 };
 224 
 225 static void
 226 free_retry_open_data (gpointer data)
 227 {
 228 	struct RetryOpenData *rod = data;
 229 
 230 	if (!rod)
 231 		return;
 232 
 233 	g_object_unref (rod->client);
 234 	g_object_unref (rod->cancellable);
 235 	g_free (rod);
 236 }
 237 
 238 static void
 239 cal_shell_sidebar_client_opened_cb (GObject *source_object,
 240                                     GAsyncResult *result,
 241                                     gpointer user_data)
 242 {
 243 	ECalClient *client = E_CAL_CLIENT (source_object);
 244 	ECalShellSidebar *cal_shell_sidebar = user_data;
 245 	ESource *source;
 246 	EShellView *shell_view;
 247 	EShellContent *shell_content;
 248 	EShellSidebar *shell_sidebar;
 249 	GError *error = NULL;
 250 
 251 	source = e_client_get_source (E_CLIENT (client));
 252 
 253 	e_client_open_finish (E_CLIENT (client), result, &error);
 254 
 255 	if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
 256 	    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
 257 		g_clear_error (&error);
 258 		return;
 259 	}
 260 
 261 	if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) {
 262 		struct RetryOpenData *rod;
 263 
 264 		rod = g_new0 (struct RetryOpenData, 1);
 265 		rod->client = g_object_ref (client);
 266 		rod->cal_shell_sidebar = cal_shell_sidebar;
 267 		rod->cancellable = g_object_ref (cal_shell_sidebar->priv->loading_clients);
 268 
 269 		/* postpone for 1/2 of a second, backend is busy now */
 270 		g_timeout_add_full (
 271 			G_PRIORITY_DEFAULT, 500,
 272 			cal_shell_sidebar_retry_open_timeout_cb,
 273 			rod, free_retry_open_data);
 274 
 275 		g_clear_error (&error);
 276 		return;
 277 	}
 278 
 279 	shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
 280 	shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
 281 	shell_content = e_shell_view_get_shell_content (shell_view);
 282 
 283 	/* Handle errors. */
 284 	switch ((error && error->domain == E_CLIENT_ERROR) ? error->code : -1) {
 285 		case -1:
 286 			break;
 287 
 288 		case E_CLIENT_ERROR_BUSY:
 289 			g_warning (
 290 				"%s: Cannot open '%s', it's busy (%s)",
 291 				G_STRFUNC, e_source_get_display_name (source),
 292 				error->message);
 293 			g_clear_error (&error);
 294 			return;
 295 
 296 		case E_CLIENT_ERROR_REPOSITORY_OFFLINE:
 297 			e_alert_submit (
 298 				E_ALERT_SINK (shell_content),
 299 				"calendar:prompt-no-contents-offline-calendar",
 300 				NULL);
 301 			/* fall through */
 302 
 303 		default:
 304 			if (error->code != E_CLIENT_ERROR_REPOSITORY_OFFLINE) {
 305 				e_alert_submit (
 306 					E_ALERT_SINK (shell_content),
 307 					"calendar:failed-open-calendar",
 308 					error->message, NULL);
 309 			}
 310 
 311 			e_cal_shell_sidebar_remove_source (
 312 				cal_shell_sidebar,
 313 				e_client_get_source (E_CLIENT (client)));
 314 			g_clear_error (&error);
 315 			return;
 316 	}
 317 
 318 	g_clear_error (&error);
 319 
 320 	/* to have them ready for later use */
 321 	e_client_retrieve_capabilities (
 322 		E_CLIENT (client), NULL,
 323 		cal_shell_sidebar_retrieve_capabilies_cb,
 324 		cal_shell_sidebar);
 325 }
 326 
 327 static gboolean
 328 cal_shell_sidebar_retry_open_timeout_cb (gpointer user_data)
 329 {
 330 	struct RetryOpenData *rod = user_data;
 331 
 332 	g_return_val_if_fail (rod != NULL, FALSE);
 333 	g_return_val_if_fail (rod->client != NULL, FALSE);
 334 	g_return_val_if_fail (rod->cal_shell_sidebar != NULL, FALSE);
 335 	g_return_val_if_fail (rod->cancellable != NULL, FALSE);
 336 
 337 	if (g_cancellable_is_cancelled (rod->cancellable))
 338 		return FALSE;
 339 
 340 	e_client_open (
 341 		rod->client, FALSE,
 342 		rod->cal_shell_sidebar->priv->loading_clients,
 343 		cal_shell_sidebar_client_opened_cb,
 344 		rod->cal_shell_sidebar);
 345 
 346 	return FALSE;
 347 }
 348 
 349 static void
 350 cal_shell_sidebar_default_loaded_cb (GObject *source_object,
 351                                      GAsyncResult *result,
 352                                      gpointer user_data)
 353 {
 354 	ESource *source = E_SOURCE (source_object);
 355 	EShellSidebar *shell_sidebar = user_data;
 356 	ECalShellSidebarPrivate *priv;
 357 	EShellContent *shell_content;
 358 	EShellView *shell_view;
 359 	ECalShellContent *cal_shell_content;
 360 	ECalModel *model;
 361 	EClient *client = NULL;
 362 	GError *error = NULL;
 363 
 364 	priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (shell_sidebar);
 365 
 366 	shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
 367 	shell_content = e_shell_view_get_shell_content (shell_view);
 368 	cal_shell_content = E_CAL_SHELL_CONTENT (shell_content);
 369 	model = e_cal_shell_content_get_model (cal_shell_content);
 370 
 371 	e_client_utils_open_new_finish (source, result, &client, &error);
 372 
 373 	if (priv->loading_default_client) {
 374 		g_object_unref (priv->loading_default_client);
 375 		priv->loading_default_client = NULL;
 376 	}
 377 
 378 	if (source == priv->loading_default_source_instance)
 379 		priv->loading_default_source_instance = NULL;
 380 
 381 	/* Ignore cancellations. */
 382 	if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED) ||
 383 	    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
 384 		g_warn_if_fail (client == NULL);
 385 		g_error_free (error);
 386 		goto exit;
 387 
 388 	} else if (error != NULL) {
 389 		g_warn_if_fail (client == NULL);
 390 		e_alert_submit (
 391 			E_ALERT_SINK (shell_content),
 392 			"calendar:failed-open-calendar",
 393 			error->message, NULL);
 394 		g_error_free (error);
 395 		goto exit;
 396 	}
 397 
 398 	g_return_if_fail (E_IS_CAL_CLIENT (client));
 399 
 400 	if (priv->default_client != NULL)
 401 		g_object_unref (priv->default_client);
 402 
 403 	priv->default_client = E_CAL_CLIENT (client);
 404 
 405 	e_cal_client_set_default_timezone (
 406 		priv->default_client, e_cal_model_get_timezone (model));
 407 
 408 	g_object_notify (G_OBJECT (shell_sidebar), "default-client");
 409 
 410  exit:
 411 	g_object_unref (shell_sidebar);
 412 }
 413 
 414 static void
 415 cal_shell_sidebar_set_default (ECalShellSidebar *cal_shell_sidebar,
 416                                ESource *source)
 417 {
 418 	ECalShellSidebarPrivate *priv;
 419 	EShellSidebar *shell_sidebar;
 420 	ECalClient *client;
 421 	const gchar *uid;
 422 
 423 	priv = cal_shell_sidebar->priv;
 424 
 425 	/* already loading that source as default source */
 426 	if (source == priv->loading_default_source_instance)
 427 		return;
 428 
 429 	/* FIXME Sidebar should not be accessing the EShellContent.
 430 	 *       This probably needs to be moved to ECalShellView. */
 431 	shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
 432 
 433 	/* Cancel any unfinished previous request. */
 434 	if (priv->loading_default_client != NULL) {
 435 		g_cancellable_cancel (priv->loading_default_client);
 436 		g_object_unref (priv->loading_default_client);
 437 		priv->loading_default_client = NULL;
 438 	}
 439 
 440 	uid = e_source_get_uid (source);
 441 	client = g_hash_table_lookup (priv->client_table, uid);
 442 
 443 	/* If we already have an open connection for
 444 	 * this UID, we can finish immediately. */
 445 	if (client != NULL) {
 446 		if (priv->default_client != NULL)
 447 			g_object_unref (priv->default_client);
 448 		priv->default_client = g_object_ref (client);
 449 		g_object_notify (G_OBJECT (shell_sidebar), "default-client");
 450 		return;
 451 	}
 452 
 453 	/* it's only for pointer comparison, no need to ref it */
 454 	priv->loading_default_source_instance = source;
 455 	priv->loading_default_client = g_cancellable_new ();
 456 
 457 	e_client_utils_open_new (
 458 		source, E_CLIENT_SOURCE_TYPE_EVENTS,
 459 		FALSE, priv->loading_default_client,
 460 		cal_shell_sidebar_default_loaded_cb,
 461 		g_object_ref (shell_sidebar));
 462 }
 463 
 464 static void
 465 cal_shell_sidebar_row_changed_cb (ECalShellSidebar *cal_shell_sidebar,
 466                                   GtkTreePath *tree_path,
 467                                   GtkTreeIter *tree_iter,
 468                                   GtkTreeModel *tree_model)
 469 {
 470 	ESourceSelector *selector;
 471 	ESource *source;
 472 
 473 	selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
 474 	source = e_source_selector_ref_source_by_path (selector, tree_path);
 475 
 476 	/* XXX This signal gets emitted a lot while the model is being
 477 	 *     rebuilt, during which time we won't get a valid ESource.
 478 	 *     ESourceSelector should probably block this signal while
 479 	 *     rebuilding the model, but we'll be forgiving and not
 480 	 *     emit a warning. */
 481 	if (source == NULL)
 482 		return;
 483 
 484 	if (e_source_selector_source_is_selected (selector, source))
 485 		e_cal_shell_sidebar_add_source (cal_shell_sidebar, source);
 486 	else
 487 		e_cal_shell_sidebar_remove_source (cal_shell_sidebar, source);
 488 
 489 	g_object_unref (source);
 490 }
 491 
 492 static void
 493 cal_shell_sidebar_primary_selection_changed_cb (ECalShellSidebar *cal_shell_sidebar,
 494                                                 ESourceSelector *selector)
 495 {
 496 	ESource *source;
 497 
 498 	source = e_source_selector_ref_primary_selection (selector);
 499 	if (source == NULL)
 500 		return;
 501 
 502 	cal_shell_sidebar_set_default (cal_shell_sidebar, source);
 503 
 504 	g_object_unref (source);
 505 }
 506 
 507 static void
 508 cal_shell_sidebar_restore_state_cb (EShellWindow *shell_window,
 509                                     EShellView *shell_view,
 510                                     EShellSidebar *shell_sidebar)
 511 {
 512 	ECalShellSidebarPrivate *priv;
 513 	EShell *shell;
 514 	EShellBackend *shell_backend;
 515 	EShellSettings *shell_settings;
 516 	ESourceRegistry *registry;
 517 	ESourceSelector *selector;
 518 	GSettings *settings;
 519 	GtkTreeModel *model;
 520 	GObject *object;
 521 
 522 	priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (shell_sidebar);
 523 
 524 	shell = e_shell_window_get_shell (shell_window);
 525 	shell_settings = e_shell_get_shell_settings (shell);
 526 
 527 	shell_backend = e_shell_view_get_shell_backend (shell_view);
 528 	g_return_if_fail (E_IS_CAL_SHELL_BACKEND (shell_backend));
 529 
 530 	selector = E_SOURCE_SELECTOR (priv->selector);
 531 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
 532 
 533 	registry = e_shell_get_registry (shell);
 534 
 535 	g_signal_connect_swapped (
 536 		model, "row-changed",
 537 		G_CALLBACK (cal_shell_sidebar_row_changed_cb),
 538 		shell_sidebar);
 539 
 540 	g_signal_connect_swapped (
 541 		selector, "primary-selection-changed",
 542 		G_CALLBACK (cal_shell_sidebar_primary_selection_changed_cb),
 543 		shell_sidebar);
 544 
 545 	g_object_bind_property_full (
 546 		shell_settings, "cal-primary-calendar",
 547 		selector, "primary-selection",
 548 		G_BINDING_BIDIRECTIONAL |
 549 		G_BINDING_SYNC_CREATE,
 550 		(GBindingTransformFunc) e_binding_transform_uid_to_source,
 551 		(GBindingTransformFunc) e_binding_transform_source_to_uid,
 552 		g_object_ref (registry),
 553 		(GDestroyNotify) g_object_unref);
 554 
 555 	/* Bind GObject properties to settings keys. */
 556 
 557 	settings = g_settings_new ("org.gnome.evolution.calendar");
 558 
 559 	object = G_OBJECT (priv->paned);
 560 	g_settings_bind (settings, "date-navigator-pane-position", object, "vposition", G_SETTINGS_BIND_DEFAULT);
 561 
 562 	g_object_unref (G_OBJECT (settings));
 563 }
 564 
 565 static void
 566 cal_shell_sidebar_get_property (GObject *object,
 567                                 guint property_id,
 568                                 GValue *value,
 569                                 GParamSpec *pspec)
 570 {
 571 	switch (property_id) {
 572 		case PROP_DATE_NAVIGATOR:
 573 			g_value_set_object (
 574 				value,
 575 				e_cal_shell_sidebar_get_date_navigator (
 576 				E_CAL_SHELL_SIDEBAR (object)));
 577 			return;
 578 
 579 		case PROP_DEFAULT_CLIENT:
 580 			g_value_set_object (
 581 				value,
 582 				e_cal_shell_sidebar_get_default_client (
 583 				E_CAL_SHELL_SIDEBAR (object)));
 584 			return;
 585 
 586 		case PROP_SELECTOR:
 587 			g_value_set_object (
 588 				value,
 589 				e_cal_shell_sidebar_get_selector (
 590 				E_CAL_SHELL_SIDEBAR (object)));
 591 			return;
 592 	}
 593 
 594 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 595 }
 596 
 597 static void
 598 cal_shell_sidebar_dispose (GObject *object)
 599 {
 600 	ECalShellSidebarPrivate *priv;
 601 
 602 	priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object);
 603 
 604 	if (priv->paned != NULL) {
 605 		g_object_unref (priv->paned);
 606 		priv->paned = NULL;
 607 	}
 608 
 609 	if (priv->selector != NULL) {
 610 		g_object_unref (priv->selector);
 611 		priv->selector = NULL;
 612 	}
 613 
 614 	if (priv->date_navigator != NULL) {
 615 		g_object_unref (priv->date_navigator);
 616 		priv->date_navigator = NULL;
 617 	}
 618 
 619 	if (priv->new_calendar_button != NULL) {
 620 		g_object_unref (priv->new_calendar_button);
 621 		priv->new_calendar_button = NULL;
 622 	}
 623 
 624 	if (priv->default_client != NULL) {
 625 		g_object_unref (priv->default_client);
 626 		priv->default_client = NULL;
 627 	}
 628 
 629 	if (priv->loading_default_client != NULL) {
 630 		g_cancellable_cancel (priv->loading_default_client);
 631 		g_object_unref (priv->loading_default_client);
 632 		priv->loading_default_client = NULL;
 633 	}
 634 
 635 	if (priv->loading_clients != NULL) {
 636 		g_cancellable_cancel (priv->loading_clients);
 637 		g_object_unref (priv->loading_clients);
 638 		priv->loading_clients = NULL;
 639 	}
 640 
 641 	g_hash_table_remove_all (priv->client_table);
 642 
 643 	/* Chain up to parent's dispose() method. */
 644 	G_OBJECT_CLASS (e_cal_shell_sidebar_parent_class)->dispose (object);
 645 }
 646 
 647 static void
 648 cal_shell_sidebar_finalize (GObject *object)
 649 {
 650 	ECalShellSidebarPrivate *priv;
 651 
 652 	priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object);
 653 
 654 	g_hash_table_destroy (priv->client_table);
 655 
 656 	/* Chain up to parent's finalize() method. */
 657 	G_OBJECT_CLASS (e_cal_shell_sidebar_parent_class)->finalize (object);
 658 }
 659 
 660 static void
 661 cal_shell_sidebar_constructed (GObject *object)
 662 {
 663 	ECalShellSidebarPrivate *priv;
 664 	EShell *shell;
 665 	EShellView *shell_view;
 666 	EShellWindow *shell_window;
 667 	EShellBackend *shell_backend;
 668 	EShellSidebar *shell_sidebar;
 669 	EShellSettings *shell_settings;
 670 	ESourceRegistry *registry;
 671 	ECalendarItem *calitem;
 672 	GtkWidget *container;
 673 	GtkWidget *widget;
 674 	AtkObject *a11y;
 675 
 676 	priv = E_CAL_SHELL_SIDEBAR_GET_PRIVATE (object);
 677 
 678 	/* Chain up to parent's constructed() method. */
 679 	G_OBJECT_CLASS (e_cal_shell_sidebar_parent_class)->constructed (object);
 680 
 681 	shell_sidebar = E_SHELL_SIDEBAR (object);
 682 	shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
 683 	shell_backend = e_shell_view_get_shell_backend (shell_view);
 684 	shell_window = e_shell_view_get_shell_window (shell_view);
 685 
 686 	shell = e_shell_backend_get_shell (shell_backend);
 687 	shell_settings = e_shell_get_shell_settings (shell);
 688 
 689 	container = GTK_WIDGET (shell_sidebar);
 690 
 691 	widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
 692 	gtk_container_add (GTK_CONTAINER (container), widget);
 693 	priv->paned = g_object_ref (widget);
 694 	gtk_widget_show (widget);
 695 
 696 	container = widget;
 697 
 698 	widget = gtk_vbox_new (FALSE, 6);
 699 	gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, TRUE);
 700 	gtk_widget_show (widget);
 701 
 702 	container = widget;
 703 
 704 	/* "New Calendar" button is only shown in express mode.
 705 	 * ECalShellView will bind the button to an appropriate
 706 	 * GtkAction so we don't have to reimplement it here. */
 707 	if (e_shell_get_express_mode (shell)) {
 708 		widget = gtk_button_new ();
 709 		gtk_box_pack_end (
 710 			GTK_BOX (container), widget, FALSE, FALSE, 0);
 711 		priv->new_calendar_button = g_object_ref (widget);
 712 		gtk_widget_show (widget);
 713 	}
 714 
 715 	widget = gtk_scrolled_window_new (NULL, NULL);
 716 	gtk_scrolled_window_set_policy (
 717 		GTK_SCROLLED_WINDOW (widget),
 718 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 719 	gtk_scrolled_window_set_shadow_type (
 720 		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
 721 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
 722 	gtk_widget_show (widget);
 723 
 724 	container = widget;
 725 
 726 	registry = e_shell_get_registry (shell);
 727 	widget = e_calendar_selector_new (registry);
 728 	e_source_selector_set_select_new (E_SOURCE_SELECTOR (widget), TRUE);
 729 	gtk_container_add (GTK_CONTAINER (container), widget);
 730 	a11y = gtk_widget_get_accessible (widget);
 731 	atk_object_set_name (a11y, _("Calendar Selector"));
 732 	priv->selector = g_object_ref (widget);
 733 	gtk_widget_show (widget);
 734 
 735 	container = priv->paned;
 736 
 737 	widget = e_calendar_new ();
 738 	calitem = E_CALENDAR (widget)->calitem;
 739 	e_calendar_item_set_days_start_week_sel (calitem, 9);
 740 	e_calendar_item_set_max_days_sel (calitem, 42);
 741 	gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
 742 	priv->date_navigator = g_object_ref (widget);
 743 	gtk_widget_show (widget);
 744 
 745 	g_object_bind_property (
 746 		shell_settings, "cal-show-week-numbers",
 747 		calitem, "show-week-numbers",
 748 		G_BINDING_SYNC_CREATE);
 749 
 750 	g_object_bind_property (
 751 		shell_settings, "cal-week-start-day",
 752 		calitem, "week-start-day",
 753 		G_BINDING_SYNC_CREATE);
 754 
 755 	/* Restore widget state from the last session once
 756 	 * the shell view is fully initialized and visible. */
 757 	g_signal_connect (
 758 		shell_window, "shell-view-created::calendar",
 759 		G_CALLBACK (cal_shell_sidebar_restore_state_cb),
 760 		shell_sidebar);
 761 }
 762 
 763 static guint32
 764 cal_shell_sidebar_check_state (EShellSidebar *shell_sidebar)
 765 {
 766 	ECalShellSidebar *cal_shell_sidebar;
 767 	ESourceSelector *selector;
 768 	ESourceRegistry *registry;
 769 	ESource *source;
 770 	gboolean is_writable = FALSE;
 771 	gboolean is_removable = FALSE;
 772 	gboolean is_remote_creatable = FALSE;
 773 	gboolean is_remote_deletable = FALSE;
 774 	gboolean in_collection = FALSE;
 775 	gboolean refresh_supported = FALSE;
 776 	gboolean has_primary_source = FALSE;
 777 	guint32 state = 0;
 778 
 779 	cal_shell_sidebar = E_CAL_SHELL_SIDEBAR (shell_sidebar);
 780 	selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
 781 	source = e_source_selector_ref_primary_selection (selector);
 782 	registry = e_source_selector_get_registry (selector);
 783 
 784 	if (source != NULL) {
 785 		EClient *client;
 786 		ESource *collection;
 787 		const gchar *uid;
 788 
 789 		has_primary_source = TRUE;
 790 		is_writable = e_source_get_writable (source);
 791 		is_removable = e_source_get_removable (source);
 792 		is_remote_creatable = e_source_get_remote_creatable (source);
 793 		is_remote_deletable = e_source_get_remote_deletable (source);
 794 
 795 		collection = e_source_registry_find_extension (
 796 			registry, source, E_SOURCE_EXTENSION_COLLECTION);
 797 		if (collection != NULL) {
 798 			in_collection = TRUE;
 799 			g_object_unref (collection);
 800 		}
 801 
 802 		uid = e_source_get_uid (source);
 803 		client = g_hash_table_lookup (
 804 			cal_shell_sidebar->priv->client_table, uid);
 805 		refresh_supported =
 806 			client != NULL &&
 807 			e_client_check_refresh_supported (client);
 808 
 809 		g_object_unref (source);
 810 	}
 811 
 812 	if (has_primary_source)
 813 		state |= E_CAL_SHELL_SIDEBAR_HAS_PRIMARY_SOURCE;
 814 	if (is_writable)
 815 		state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_WRITABLE;
 816 	if (is_removable)
 817 		state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOVABLE;
 818 	if (is_remote_creatable)
 819 		state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_CREATABLE;
 820 	if (is_remote_deletable)
 821 		state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IS_REMOTE_DELETABLE;
 822 	if (in_collection)
 823 		state |= E_CAL_SHELL_SIDEBAR_PRIMARY_SOURCE_IN_COLLECTION;
 824 	if (refresh_supported)
 825 		state |= E_CAL_SHELL_SIDEBAR_SOURCE_SUPPORTS_REFRESH;
 826 
 827 	return state;
 828 }
 829 
 830 static void
 831 cal_shell_sidebar_client_removed (ECalShellSidebar *cal_shell_sidebar,
 832                                   ECalClient *client)
 833 {
 834 	ESourceSelector *selector;
 835 	GHashTable *client_table;
 836 	ESource *source;
 837 	const gchar *uid;
 838 
 839 	client_table = cal_shell_sidebar->priv->client_table;
 840 	selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
 841 
 842 	g_signal_handlers_disconnect_matched (
 843 		client, G_SIGNAL_MATCH_DATA, 0, 0,
 844 		NULL, NULL, cal_shell_sidebar);
 845 
 846 	source = e_client_get_source (E_CLIENT (client));
 847 	uid = e_source_get_uid (source);
 848 
 849 	g_hash_table_remove (client_table, uid);
 850 	e_source_selector_unselect_source (selector, source);
 851 
 852 	cal_shell_sidebar_emit_status_message (cal_shell_sidebar, NULL);
 853 }
 854 
 855 static void
 856 e_cal_shell_sidebar_class_init (ECalShellSidebarClass *class)
 857 {
 858 	GObjectClass *object_class;
 859 	EShellSidebarClass *shell_sidebar_class;
 860 
 861 	g_type_class_add_private (class, sizeof (ECalShellSidebarPrivate));
 862 
 863 	object_class = G_OBJECT_CLASS (class);
 864 	object_class->get_property = cal_shell_sidebar_get_property;
 865 	object_class->dispose = cal_shell_sidebar_dispose;
 866 	object_class->finalize = cal_shell_sidebar_finalize;
 867 	object_class->constructed = cal_shell_sidebar_constructed;
 868 
 869 	shell_sidebar_class = E_SHELL_SIDEBAR_CLASS (class);
 870 	shell_sidebar_class->check_state = cal_shell_sidebar_check_state;
 871 
 872 	class->client_removed = cal_shell_sidebar_client_removed;
 873 
 874 	g_object_class_install_property (
 875 		object_class,
 876 		PROP_DATE_NAVIGATOR,
 877 		g_param_spec_object (
 878 			"date-navigator",
 879 			"Date Navigator Widget",
 880 			"This widget displays a miniature calendar",
 881 			E_TYPE_CALENDAR,
 882 			G_PARAM_READABLE));
 883 
 884 	g_object_class_install_property (
 885 		object_class,
 886 		PROP_DEFAULT_CLIENT,
 887 		g_param_spec_object (
 888 			"default-client",
 889 			"Default Calendar ECalClient",
 890 			"Default client for calendar operations",
 891 			E_TYPE_CAL_CLIENT,
 892 			G_PARAM_READABLE));
 893 
 894 	g_object_class_install_property (
 895 		object_class,
 896 		PROP_SELECTOR,
 897 		g_param_spec_object (
 898 			"selector",
 899 			"Source Selector Widget",
 900 			"This widget displays groups of calendars",
 901 			E_TYPE_SOURCE_SELECTOR,
 902 			G_PARAM_READABLE));
 903 
 904 	signals[CLIENT_ADDED] = g_signal_new (
 905 		"client-added",
 906 		G_OBJECT_CLASS_TYPE (object_class),
 907 		G_SIGNAL_RUN_LAST,
 908 		G_STRUCT_OFFSET (ECalShellSidebarClass, client_added),
 909 		NULL, NULL,
 910 		g_cclosure_marshal_VOID__OBJECT,
 911 		G_TYPE_NONE, 1,
 912 		E_TYPE_CAL_CLIENT);
 913 
 914 	signals[CLIENT_REMOVED] = g_signal_new (
 915 		"client-removed",
 916 		G_OBJECT_CLASS_TYPE (object_class),
 917 		G_SIGNAL_RUN_LAST,
 918 		G_STRUCT_OFFSET (ECalShellSidebarClass, client_removed),
 919 		NULL, NULL,
 920 		g_cclosure_marshal_VOID__OBJECT,
 921 		G_TYPE_NONE, 1,
 922 		E_TYPE_CAL_CLIENT);
 923 
 924 	signals[STATUS_MESSAGE] = g_signal_new (
 925 		"status-message",
 926 		G_OBJECT_CLASS_TYPE (object_class),
 927 		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
 928 		G_STRUCT_OFFSET (ECalShellSidebarClass, status_message),
 929 		NULL, NULL,
 930 		g_cclosure_marshal_VOID__STRING,
 931 		G_TYPE_NONE, 1,
 932 		G_TYPE_STRING);
 933 }
 934 
 935 static void
 936 e_cal_shell_sidebar_class_finalize (ECalShellSidebarClass *class)
 937 {
 938 }
 939 
 940 static void
 941 e_cal_shell_sidebar_init (ECalShellSidebar *cal_shell_sidebar)
 942 {
 943 	GHashTable *client_table;
 944 
 945 	client_table = g_hash_table_new_full (
 946 		g_str_hash, g_str_equal,
 947 		(GDestroyNotify) g_free,
 948 		(GDestroyNotify) g_object_unref);
 949 
 950 	cal_shell_sidebar->priv =
 951 		E_CAL_SHELL_SIDEBAR_GET_PRIVATE (cal_shell_sidebar);
 952 
 953 	cal_shell_sidebar->priv->client_table = client_table;
 954 	cal_shell_sidebar->priv->loading_clients = g_cancellable_new ();
 955 
 956 	/* Postpone widget construction until we have a shell view. */
 957 }
 958 
 959 void
 960 e_cal_shell_sidebar_type_register (GTypeModule *type_module)
 961 {
 962 	/* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
 963 	 *     function, so we have to wrap it with a public function in
 964 	 *     order to register types from a separate compilation unit. */
 965 	e_cal_shell_sidebar_register_type (type_module);
 966 }
 967 
 968 GtkWidget *
 969 e_cal_shell_sidebar_new (EShellView *shell_view)
 970 {
 971 	g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
 972 
 973 	return g_object_new (
 974 		E_TYPE_CAL_SHELL_SIDEBAR,
 975 		"shell-view", shell_view, NULL);
 976 }
 977 
 978 GList *
 979 e_cal_shell_sidebar_get_clients (ECalShellSidebar *cal_shell_sidebar)
 980 {
 981 	GHashTable *client_table;
 982 
 983 	g_return_val_if_fail (
 984 		E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
 985 
 986 	client_table = cal_shell_sidebar->priv->client_table;
 987 
 988 	return g_hash_table_get_values (client_table);
 989 }
 990 
 991 ECalendar *
 992 e_cal_shell_sidebar_get_date_navigator (ECalShellSidebar *cal_shell_sidebar)
 993 {
 994 	g_return_val_if_fail (
 995 		E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
 996 
 997 	return E_CALENDAR (cal_shell_sidebar->priv->date_navigator);
 998 }
 999 
1000 ECalClient *
1001 e_cal_shell_sidebar_get_default_client (ECalShellSidebar *cal_shell_sidebar)
1002 {
1003 	g_return_val_if_fail (
1004 		E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
1005 
1006 	return cal_shell_sidebar->priv->default_client;
1007 }
1008 
1009 GtkWidget *
1010 e_cal_shell_sidebar_get_new_calendar_button (ECalShellSidebar *cal_shell_sidebar)
1011 {
1012 	g_return_val_if_fail (
1013 		E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
1014 
1015 	return cal_shell_sidebar->priv->new_calendar_button;
1016 }
1017 
1018 ESourceSelector *
1019 e_cal_shell_sidebar_get_selector (ECalShellSidebar *cal_shell_sidebar)
1020 {
1021 	g_return_val_if_fail (
1022 		E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar), NULL);
1023 
1024 	return E_SOURCE_SELECTOR (cal_shell_sidebar->priv->selector);
1025 }
1026 
1027 void
1028 e_cal_shell_sidebar_add_source (ECalShellSidebar *cal_shell_sidebar,
1029                                 ESource *source)
1030 {
1031 	EShellView *shell_view;
1032 	EShellContent *shell_content;
1033 	EShellSidebar *shell_sidebar;
1034 	ECalShellContent *cal_shell_content;
1035 	ECalClientSourceType source_type;
1036 	ESourceSelector *selector;
1037 	GHashTable *client_table;
1038 	ECalModel *model;
1039 	ECalClient *default_client;
1040 	ECalClient *client;
1041 	icaltimezone *timezone;
1042 	const gchar *display_name;
1043 	const gchar *uid;
1044 	gchar *message;
1045 
1046 	g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar));
1047 	g_return_if_fail (E_IS_SOURCE (source));
1048 
1049 	source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
1050 	client_table = cal_shell_sidebar->priv->client_table;
1051 	default_client = cal_shell_sidebar->priv->default_client;
1052 	selector = e_cal_shell_sidebar_get_selector (cal_shell_sidebar);
1053 
1054 	uid = e_source_get_uid (source);
1055 	client = g_hash_table_lookup (client_table, uid);
1056 
1057 	if (client != NULL)
1058 		return;
1059 
1060 	if (default_client != NULL) {
1061 		ESource *default_source;
1062 		const gchar *default_uid;
1063 
1064 		default_source = e_client_get_source (E_CLIENT (default_client));
1065 		default_uid = e_source_get_uid (default_source);
1066 
1067 		if (g_strcmp0 (uid, default_uid) == 0)
1068 			client = g_object_ref (default_client);
1069 	}
1070 
1071 	if (client == NULL)
1072 		client = e_cal_client_new (source, source_type, NULL);
1073 
1074 	g_return_if_fail (client != NULL);
1075 
1076 	g_signal_connect_swapped (
1077 		client, "backend-died",
1078 		G_CALLBACK (cal_shell_sidebar_backend_died_cb),
1079 		cal_shell_sidebar);
1080 
1081 	g_signal_connect_swapped (
1082 		client, "backend-error",
1083 		G_CALLBACK (cal_shell_sidebar_backend_error_cb),
1084 		cal_shell_sidebar);
1085 
1086 	g_hash_table_insert (client_table, g_strdup (uid), client);
1087 	e_source_selector_select_source (selector, source);
1088 
1089 	display_name = e_source_get_display_name (source);
1090 	message = g_strdup_printf (_("Opening calendar '%s'"), display_name);
1091 	cal_shell_sidebar_emit_status_message (cal_shell_sidebar, message);
1092 	g_free (message);
1093 
1094 	/* FIXME Sidebar should not be accessing the EShellContent.
1095 	 *       This probably needs to be moved to ECalShellView. */
1096 	shell_sidebar = E_SHELL_SIDEBAR (cal_shell_sidebar);
1097 	shell_view = e_shell_sidebar_get_shell_view (shell_sidebar);
1098 	shell_content = e_shell_view_get_shell_content (shell_view);
1099 
1100 	cal_shell_content = E_CAL_SHELL_CONTENT (shell_content);
1101 	model = e_cal_shell_content_get_model (cal_shell_content);
1102 	timezone = e_cal_model_get_timezone (model);
1103 
1104 	e_cal_client_set_default_timezone (client, timezone);
1105 
1106 	e_client_open (
1107 		E_CLIENT (client), FALSE,
1108 		cal_shell_sidebar->priv->loading_clients,
1109 		cal_shell_sidebar_client_opened_cb, cal_shell_sidebar);
1110 }
1111 
1112 void
1113 e_cal_shell_sidebar_remove_source (ECalShellSidebar *cal_shell_sidebar,
1114                                    ESource *source)
1115 {
1116 	GHashTable *client_table;
1117 	ECalClient *client;
1118 	const gchar *uid;
1119 
1120 	g_return_if_fail (E_IS_CAL_SHELL_SIDEBAR (cal_shell_sidebar));
1121 	g_return_if_fail (E_IS_SOURCE (source));
1122 
1123 	client_table = cal_shell_sidebar->priv->client_table;
1124 
1125 	uid = e_source_get_uid (source);
1126 	client = g_hash_table_lookup (client_table, uid);
1127 
1128 	if (client == NULL)
1129 		return;
1130 
1131 	cal_shell_sidebar_emit_client_removed (cal_shell_sidebar, client);
1132 }