/*
* e-book-shell-view-private.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see
*
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "e-util/e-util-private.h"
#include "e-book-shell-view-private.h"
#include "addressbook/gui/widgets/gal-view-factory-minicard.h"
static void
open_contact (EBookShellView *book_shell_view,
EContact *contact,
gboolean is_new_contact,
EAddressbookView *view)
{
EShell *shell;
EShellView *shell_view;
EShellWindow *shell_window;
EAddressbookModel *model;
EABEditor *editor;
EBookClient *book;
gboolean editable;
shell_view = E_SHELL_VIEW (book_shell_view);
shell_window = e_shell_view_get_shell_window (shell_view);
shell = e_shell_window_get_shell (shell_window);
model = e_addressbook_view_get_model (view);
book = e_addressbook_model_get_client (model);
editable = e_addressbook_model_get_editable (model);
if (e_contact_get (contact, E_CONTACT_IS_LIST))
editor = e_contact_list_editor_new (
shell, book, contact, is_new_contact, editable);
else
editor = e_contact_editor_new (
shell, book, contact, is_new_contact, editable);
eab_editor_show (editor);
}
static void
popup_event (EBookShellView *book_shell_view,
GdkEvent *button_event)
{
EShellView *shell_view;
const gchar *widget_path;
widget_path = "/contact-popup";
shell_view = E_SHELL_VIEW (book_shell_view);
e_shell_view_show_popup_menu (shell_view, widget_path, button_event);
}
static void
book_shell_view_selection_change_foreach (gint row,
EBookShellView *book_shell_view)
{
EBookShellContent *book_shell_content;
EAddressbookView *view;
EAddressbookModel *model;
EContact *contact;
/* XXX A "foreach" function is kind of a silly way to retrieve
* the one and only selected contact, but this is the only
* means that ESelectionModel provides. */
book_shell_content = book_shell_view->priv->book_shell_content;
view = e_book_shell_content_get_current_view (book_shell_content);
model = e_addressbook_view_get_model (view);
contact = e_addressbook_model_get_contact (model, row);
e_book_shell_content_set_preview_contact (book_shell_content, contact);
book_shell_view->priv->preview_index = row;
if (contact)
g_object_unref (contact);
}
static void
selection_change (EBookShellView *book_shell_view,
EAddressbookView *view)
{
EBookShellContent *book_shell_content;
EAddressbookView *current_view;
ESelectionModel *selection_model;
EShellView *shell_view;
gint n_selected;
shell_view = E_SHELL_VIEW (book_shell_view);
book_shell_content = book_shell_view->priv->book_shell_content;
current_view = e_book_shell_content_get_current_view (book_shell_content);
if (view != current_view)
return;
e_shell_view_update_actions (shell_view);
selection_model = e_addressbook_view_get_selection_model (view);
n_selected = (selection_model != NULL) ?
e_selection_model_selected_count (selection_model) : 0;
if (n_selected == 1)
e_selection_model_foreach (
selection_model, (EForeachFunc)
book_shell_view_selection_change_foreach,
book_shell_view);
else {
e_book_shell_content_set_preview_contact (
book_shell_content, NULL);
book_shell_view->priv->preview_index = -1;
}
}
static void
contact_changed (EBookShellView *book_shell_view,
gint index,
EAddressbookModel *model)
{
EBookShellContent *book_shell_content;
EContact *contact;
g_return_if_fail (E_IS_SHELL_VIEW (book_shell_view));
g_return_if_fail (book_shell_view->priv != NULL);
book_shell_content = book_shell_view->priv->book_shell_content;
contact = e_addressbook_model_contact_at (model, index);
if (book_shell_view->priv->preview_index != index)
return;
/* Re-render the same contact. */
e_book_shell_content_set_preview_contact (book_shell_content, contact);
}
static void
contacts_removed (EBookShellView *book_shell_view,
GArray *removed_indices,
EAddressbookModel *model)
{
EBookShellContent *book_shell_content;
EContact *preview_contact;
g_return_if_fail (E_IS_SHELL_VIEW (book_shell_view));
g_return_if_fail (book_shell_view->priv != NULL);
book_shell_content = book_shell_view->priv->book_shell_content;
preview_contact =
e_book_shell_content_get_preview_contact (book_shell_content);
if (preview_contact == NULL)
return;
/* Is the displayed contact still in the model? */
if (e_addressbook_model_find (model, preview_contact) < 0)
return;
/* If not, clear the contact display. */
e_book_shell_content_set_preview_contact (book_shell_content, NULL);
book_shell_view->priv->preview_index = -1;
}
static void
model_query_changed_cb (EBookShellView *book_shell_view,
GParamSpec *param,
EAddressbookModel *model)
{
EBookShellContent *book_shell_content;
EAddressbookView *current_view;
book_shell_content = book_shell_view->priv->book_shell_content;
current_view = e_book_shell_content_get_current_view (book_shell_content);
if (!current_view || e_addressbook_view_get_model (current_view) != model)
return;
/* clear the contact preview when model's query changed */
e_book_shell_content_set_preview_contact (book_shell_content, NULL);
book_shell_view->priv->preview_index = -1;
}
static void
book_shell_view_client_connect_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
EAddressbookView *view = user_data;
EClient *client;
EAddressbookModel *model;
GError *error = NULL;
client = e_book_client_connect_finish (result, &error);
/* Sanity check. */
g_return_if_fail (
((client != NULL) && (error == NULL)) ||
((client == NULL) && (error != NULL)));
/* Ignore cancellations. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_error_free (error);
goto exit;
} else if (error != NULL) {
EShellView *shell_view;
EShellContent *shell_content;
EAlertSink *alert_sink;
ESource *source;
source = e_addressbook_view_get_source (view);
shell_view = e_addressbook_view_get_shell_view (view);
shell_content = e_shell_view_get_shell_content (shell_view);
alert_sink = E_ALERT_SINK (shell_content);
eab_load_error_dialog (NULL, alert_sink, source, error);
g_error_free (error);
goto exit;
}
model = e_addressbook_view_get_model (view);
e_addressbook_model_set_client (model, E_BOOK_CLIENT (client));
e_addressbook_model_force_folder_bar_message (model);
exit:
g_object_unref (view);
}
static void
book_shell_view_activate_selected_source (EBookShellView *book_shell_view,
ESourceSelector *selector)
{
EShellView *shell_view;
EBookShellContent *book_shell_content;
EAddressbookView *view;
EAddressbookModel *model;
ESource *source;
GalViewInstance *view_instance;
GHashTable *hash_table;
GtkWidget *widget;
const gchar *uid;
gchar *view_id;
shell_view = E_SHELL_VIEW (book_shell_view);
book_shell_content = book_shell_view->priv->book_shell_content;
source = e_source_selector_ref_primary_selection (selector);
if (source == NULL)
return;
uid = e_source_get_uid (source);
hash_table = book_shell_view->priv->uid_to_view;
widget = g_hash_table_lookup (hash_table, uid);
if (widget != NULL) {
/* There is a view for this UID. Make sure the view
* actually contains an EBook. The absence of an EBook
* suggests a previous load failed, so try again. */
view = E_ADDRESSBOOK_VIEW (widget);
model = e_addressbook_view_get_model (view);
source = e_addressbook_view_get_source (view);
if (e_addressbook_model_get_client (model) == NULL)
/* XXX No way to cancel this? */
e_book_client_connect (
source, NULL,
book_shell_view_client_connect_cb,
g_object_ref (view));
} else {
/* Create a view for this UID. */
widget = e_addressbook_view_new (shell_view, source);
gtk_widget_show (widget);
/* Default searching options for a new view. */
e_addressbook_view_set_search (
E_ADDRESSBOOK_VIEW (widget),
CONTACT_FILTER_ANY_CATEGORY,
CONTACT_SEARCH_NAME_CONTAINS,
NULL, NULL);
e_book_shell_content_insert_view (
book_shell_content,
E_ADDRESSBOOK_VIEW (widget));
g_hash_table_insert (
hash_table, g_strdup (uid),
g_object_ref (widget));
g_signal_connect_object (
widget, "open-contact", G_CALLBACK (open_contact),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
widget, "popup-event", G_CALLBACK (popup_event),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
widget, "command-state-change",
G_CALLBACK (e_shell_view_update_actions),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
widget, "selection-change", G_CALLBACK (selection_change),
book_shell_view, G_CONNECT_SWAPPED);
view = E_ADDRESSBOOK_VIEW (widget);
model = e_addressbook_view_get_model (view);
/* XXX No way to cancel this? */
e_book_client_connect (
source, NULL,
book_shell_view_client_connect_cb,
g_object_ref (view));
g_signal_connect_object (
model, "contact-changed",
G_CALLBACK (contact_changed),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
model, "contacts-removed",
G_CALLBACK (contacts_removed),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
model, "notify::query",
G_CALLBACK (model_query_changed_cb),
book_shell_view, G_CONNECT_SWAPPED);
}
e_book_shell_content_set_current_view (
book_shell_content, E_ADDRESSBOOK_VIEW (widget));
/* XXX We have to keep the addressbook selector informed of the
* current view so it can move contacts via drag-and-drop. */
e_addressbook_selector_set_current_view (
E_ADDRESSBOOK_SELECTOR (selector),
E_ADDRESSBOOK_VIEW (widget));
view_instance = e_addressbook_view_get_view_instance (view);
/* This must come after e_book_shell_content_set_current_view()
* because book_shell_view_notify_view_id_cb() relies on it. */
gal_view_instance_load (view_instance);
view_id = gal_view_instance_get_current_view_id (view_instance);
e_shell_view_set_view_id (shell_view, view_id);
g_free (view_id);
e_addressbook_model_force_folder_bar_message (model);
selection_change (book_shell_view, view);
g_object_unref (source);
}
static gboolean
book_shell_view_show_popup_menu (GdkEvent *button_event,
EShellView *shell_view)
{
const gchar *widget_path;
widget_path = "/address-book-popup";
e_shell_view_show_popup_menu (shell_view, widget_path, button_event);
return TRUE;
}
static gboolean
book_shell_view_selector_button_press_event_cb (EShellView *shell_view,
GdkEvent *button_event)
{
guint event_button = 0;
/* XXX Use ESourceSelector's "popup-event" signal instead. */
gdk_event_get_button (button_event, &event_button);
if (button_event->type != GDK_BUTTON_PRESS || event_button != 3)
return FALSE;
return book_shell_view_show_popup_menu (button_event, shell_view);
}
static gboolean
book_shell_view_selector_popup_menu_cb (EShellView *shell_view)
{
/* XXX Use ESourceSelector's "popup-event" signal instead. */
return book_shell_view_show_popup_menu (NULL, shell_view);
}
static void
book_shell_view_source_removed_cb (ESourceRegistry *registry,
ESource *source,
EBookShellView *book_shell_view)
{
EBookShellViewPrivate *priv = book_shell_view->priv;
EBookShellContent *book_shell_content;
EAddressbookView *view;
const gchar *uid;
uid = e_source_get_uid (source);
book_shell_content = book_shell_view->priv->book_shell_content;
/* Remove the EAddressbookView for the deleted source. */
view = g_hash_table_lookup (priv->uid_to_view, uid);
if (view != NULL) {
e_book_shell_content_remove_view (book_shell_content, view);
g_hash_table_remove (priv->uid_to_view, uid);
}
e_shell_view_update_actions (E_SHELL_VIEW (book_shell_view));
}
static void
book_shell_view_load_view_collection (EShellViewClass *shell_view_class)
{
GalViewCollection *collection;
GalViewFactory *factory;
ETableSpecification *spec;
const gchar *base_dir;
gchar *filename;
collection = shell_view_class->view_collection;
base_dir = EVOLUTION_ETSPECDIR;
spec = e_table_specification_new ();
filename = g_build_filename (base_dir, ETSPEC_FILENAME, NULL);
if (!e_table_specification_load_from_file (spec, filename))
g_critical (
"Unable to load ETable specification file "
"for address book");
g_free (filename);
factory = gal_view_factory_etable_new (spec);
gal_view_collection_add_factory (collection, factory);
g_object_unref (factory);
g_object_unref (spec);
factory = gal_view_factory_minicard_new ();
gal_view_collection_add_factory (collection, factory);
g_object_unref (factory);
gal_view_collection_load (collection);
}
static void
book_shell_view_notify_view_id_cb (EBookShellView *book_shell_view)
{
EBookShellContent *book_shell_content;
EAddressbookView *address_view;
GalViewInstance *view_instance;
const gchar *view_id;
book_shell_content = book_shell_view->priv->book_shell_content;
address_view = e_book_shell_content_get_current_view (book_shell_content);
view_instance = e_addressbook_view_get_view_instance (address_view);
view_id = e_shell_view_get_view_id (E_SHELL_VIEW (book_shell_view));
/* A NULL view ID implies we're in a custom view. But you can
* only get to a custom view via the "Define Views" dialog, which
* would have already modified the view instance appropriately.
* Furthermore, there's no way to refer to a custom view by ID
* anyway, since custom views have no IDs. */
if (view_id == NULL)
return;
gal_view_instance_set_current_view_id (view_instance, view_id);
}
void
e_book_shell_view_private_init (EBookShellView *book_shell_view,
EShellViewClass *shell_view_class)
{
EBookShellViewPrivate *priv = book_shell_view->priv;
GHashTable *uid_to_view;
uid_to_view = g_hash_table_new_full (
g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
priv->uid_to_view = uid_to_view;
priv->preview_index = -1;
if (!gal_view_collection_loaded (shell_view_class->view_collection))
book_shell_view_load_view_collection (shell_view_class);
g_signal_connect (
book_shell_view, "notify::view-id",
G_CALLBACK (book_shell_view_notify_view_id_cb), NULL);
}
void
e_book_shell_view_private_constructed (EBookShellView *book_shell_view)
{
EBookShellViewPrivate *priv = book_shell_view->priv;
EShell *shell;
EShellView *shell_view;
EShellWindow *shell_window;
EShellContent *shell_content;
EShellSidebar *shell_sidebar;
EShellBackend *shell_backend;
ESourceSelector *selector;
gulong handler_id;
shell_view = E_SHELL_VIEW (book_shell_view);
shell_backend = e_shell_view_get_shell_backend (shell_view);
shell_content = e_shell_view_get_shell_content (shell_view);
shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
shell_window = e_shell_view_get_shell_window (shell_view);
shell = e_shell_window_get_shell (shell_window);
e_shell_window_add_action_group (shell_window, "contacts");
e_shell_window_add_action_group (shell_window, "contacts-filter");
/* Cache these to avoid lots of awkward casting. */
priv->book_shell_backend = g_object_ref (shell_backend);
priv->book_shell_content = g_object_ref (shell_content);
priv->book_shell_sidebar = g_object_ref (shell_sidebar);
/* Keep our own reference to this so we can
* disconnect our signal handler in dispose(). */
priv->registry = g_object_ref (e_shell_get_registry (shell));
selector = e_book_shell_sidebar_get_selector (
E_BOOK_SHELL_SIDEBAR (shell_sidebar));
handler_id = g_signal_connect (
priv->registry, "source-removed",
G_CALLBACK (book_shell_view_source_removed_cb),
book_shell_view);
priv->source_removed_handler_id = handler_id;
g_signal_connect_object (
selector, "button-press-event",
G_CALLBACK (book_shell_view_selector_button_press_event_cb),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
selector, "popup-menu",
G_CALLBACK (book_shell_view_selector_popup_menu_cb),
book_shell_view, G_CONNECT_SWAPPED);
g_signal_connect_object (
selector, "primary-selection-changed",
G_CALLBACK (book_shell_view_activate_selected_source),
book_shell_view, G_CONNECT_SWAPPED);
e_categories_add_change_hook (
(GHookFunc) e_book_shell_view_update_search_filter,
book_shell_view);
e_book_shell_view_actions_init (book_shell_view);
book_shell_view_activate_selected_source (book_shell_view, selector);
e_book_shell_view_update_search_filter (book_shell_view);
}
void
e_book_shell_view_private_dispose (EBookShellView *book_shell_view)
{
EBookShellViewPrivate *priv = book_shell_view->priv;
if (priv->source_removed_handler_id > 0) {
g_signal_handler_disconnect (
priv->registry,
priv->source_removed_handler_id);
priv->source_removed_handler_id = 0;
}
g_clear_object (&priv->book_shell_backend);
g_clear_object (&priv->book_shell_content);
g_clear_object (&priv->book_shell_sidebar);
g_clear_object (&priv->registry);
g_hash_table_remove_all (priv->uid_to_view);
}
void
e_book_shell_view_private_finalize (EBookShellView *book_shell_view)
{
EBookShellViewPrivate *priv = book_shell_view->priv;
g_hash_table_destroy (priv->uid_to_view);
}