No issues found
1 /*
2 * e-book-shell-view-private.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-util/e-util-private.h"
27
28 #include "e-book-shell-view-private.h"
29
30 #include "widgets/menus/gal-view-factory-etable.h"
31 #include "addressbook/gui/widgets/gal-view-factory-minicard.h"
32
33 static void
34 open_contact (EBookShellView *book_shell_view,
35 EContact *contact,
36 gboolean is_new_contact,
37 EAddressbookView *view)
38 {
39 EShell *shell;
40 EShellView *shell_view;
41 EShellWindow *shell_window;
42 EAddressbookModel *model;
43 EABEditor *editor;
44 EBookClient *book;
45 gboolean editable;
46
47 shell_view = E_SHELL_VIEW (book_shell_view);
48 shell_window = e_shell_view_get_shell_window (shell_view);
49 shell = e_shell_window_get_shell (shell_window);
50
51 model = e_addressbook_view_get_model (view);
52 book = e_addressbook_model_get_client (model);
53 editable = e_addressbook_model_get_editable (model);
54
55 if (e_contact_get (contact, E_CONTACT_IS_LIST))
56 editor = e_contact_list_editor_new (
57 shell, book, contact, is_new_contact, editable);
58 else
59 editor = e_contact_editor_new (
60 shell, book, contact, is_new_contact, editable);
61
62 eab_editor_show (editor);
63 }
64
65 static void
66 popup_event (EBookShellView *book_shell_view,
67 GdkEventButton *event)
68 {
69 EShellView *shell_view;
70 const gchar *widget_path;
71
72 widget_path = "/contact-popup";
73 shell_view = E_SHELL_VIEW (book_shell_view);
74
75 e_shell_view_show_popup_menu (shell_view, widget_path, event);
76 }
77
78 static void
79 book_shell_view_selection_change_foreach (gint row,
80 EBookShellView *book_shell_view)
81 {
82 EBookShellContent *book_shell_content;
83 EAddressbookView *view;
84 EAddressbookModel *model;
85 EContact *contact;
86
87 /* XXX A "foreach" function is kind of a silly way to retrieve
88 * the one and only selected contact, but this is the only
89 * means that ESelectionModel provides. */
90
91 book_shell_content = book_shell_view->priv->book_shell_content;
92 view = e_book_shell_content_get_current_view (book_shell_content);
93 model = e_addressbook_view_get_model (view);
94 contact = e_addressbook_model_get_contact (model, row);
95
96 e_book_shell_content_set_preview_contact (book_shell_content, contact);
97 book_shell_view->priv->preview_index = row;
98
99 if (contact)
100 g_object_unref (contact);
101 }
102
103 static void
104 selection_change (EBookShellView *book_shell_view,
105 EAddressbookView *view)
106 {
107 EBookShellContent *book_shell_content;
108 EAddressbookView *current_view;
109 ESelectionModel *selection_model;
110 EShellView *shell_view;
111 gint n_selected;
112
113 shell_view = E_SHELL_VIEW (book_shell_view);
114 book_shell_content = book_shell_view->priv->book_shell_content;
115 current_view = e_book_shell_content_get_current_view (book_shell_content);
116
117 if (view != current_view)
118 return;
119
120 e_shell_view_update_actions (shell_view);
121
122 selection_model = e_addressbook_view_get_selection_model (view);
123
124 n_selected = (selection_model != NULL) ?
125 e_selection_model_selected_count (selection_model) : 0;
126
127 if (n_selected == 1)
128 e_selection_model_foreach (
129 selection_model, (EForeachFunc)
130 book_shell_view_selection_change_foreach,
131 book_shell_view);
132 else {
133 e_book_shell_content_set_preview_contact (
134 book_shell_content, NULL);
135 book_shell_view->priv->preview_index = -1;
136 }
137 }
138
139 static void
140 contact_changed (EBookShellView *book_shell_view,
141 gint index,
142 EAddressbookModel *model)
143 {
144 EBookShellContent *book_shell_content;
145 EContact *contact;
146
147 g_return_if_fail (E_IS_SHELL_VIEW (book_shell_view));
148 g_return_if_fail (book_shell_view->priv != NULL);
149
150 book_shell_content = book_shell_view->priv->book_shell_content;
151
152 contact = e_addressbook_model_contact_at (model, index);
153
154 if (book_shell_view->priv->preview_index != index)
155 return;
156
157 /* Re-render the same contact. */
158 e_book_shell_content_set_preview_contact (book_shell_content, contact);
159 }
160
161 static void
162 contacts_removed (EBookShellView *book_shell_view,
163 GArray *removed_indices,
164 EAddressbookModel *model)
165 {
166 EBookShellContent *book_shell_content;
167 EContact *preview_contact;
168
169 g_return_if_fail (E_IS_SHELL_VIEW (book_shell_view));
170 g_return_if_fail (book_shell_view->priv != NULL);
171
172 book_shell_content = book_shell_view->priv->book_shell_content;
173
174 preview_contact =
175 e_book_shell_content_get_preview_contact (book_shell_content);
176
177 if (preview_contact == NULL)
178 return;
179
180 /* Is the displayed contact still in the model? */
181 if (e_addressbook_model_find (model, preview_contact) < 0)
182 return;
183
184 /* If not, clear the contact display. */
185 e_book_shell_content_set_preview_contact (book_shell_content, NULL);
186 book_shell_view->priv->preview_index = -1;
187 }
188
189 static void
190 model_query_changed_cb (EBookShellView *book_shell_view,
191 GParamSpec *param,
192 EAddressbookModel *model)
193 {
194 EBookShellContent *book_shell_content;
195 EAddressbookView *current_view;
196
197 book_shell_content = book_shell_view->priv->book_shell_content;
198 current_view = e_book_shell_content_get_current_view (book_shell_content);
199
200 if (!current_view || e_addressbook_view_get_model (current_view) != model)
201 return;
202
203 /* clear the contact preview when model's query changed */
204 e_book_shell_content_set_preview_contact (book_shell_content, NULL);
205 book_shell_view->priv->preview_index = -1;
206 }
207
208 static void
209 book_shell_view_loaded_cb (GObject *source_object,
210 GAsyncResult *result,
211 gpointer user_data)
212 {
213 ESource *source = E_SOURCE (source_object);
214 EAddressbookView *view = user_data;
215 EClient *client = NULL;
216 EAddressbookModel *model;
217 GError *error = NULL;
218
219 e_client_utils_open_new_finish (source, result, &client, &error);
220
221 /* Ignore cancellations. */
222 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
223 g_warn_if_fail (client == NULL);
224 g_error_free (error);
225 goto exit;
226
227 } else if (error != NULL) {
228 EShellView *shell_view;
229 EShellContent *shell_content;
230 EAlertSink *alert_sink;
231
232 g_warn_if_fail (client == NULL);
233
234 shell_view = e_addressbook_view_get_shell_view (view);
235 shell_content = e_shell_view_get_shell_content (shell_view);
236 alert_sink = E_ALERT_SINK (shell_content);
237
238 eab_load_error_dialog (NULL, alert_sink, source, error);
239
240 g_error_free (error);
241 goto exit;
242 }
243
244 g_return_if_fail (E_IS_CLIENT (client));
245
246 model = e_addressbook_view_get_model (view);
247 e_addressbook_model_set_client (model, E_BOOK_CLIENT (client));
248 e_addressbook_model_force_folder_bar_message (model);
249
250 exit:
251 g_object_unref (view);
252 }
253
254 static void
255 book_shell_view_activate_selected_source (EBookShellView *book_shell_view,
256 ESourceSelector *selector)
257 {
258 EShellView *shell_view;
259 EBookShellContent *book_shell_content;
260 EAddressbookView *view;
261 EAddressbookModel *model;
262 ESource *source;
263 GalViewInstance *view_instance;
264 GHashTable *hash_table;
265 GtkWidget *widget;
266 const gchar *uid;
267 gchar *view_id;
268
269 shell_view = E_SHELL_VIEW (book_shell_view);
270
271 book_shell_content = book_shell_view->priv->book_shell_content;
272 source = e_source_selector_ref_primary_selection (selector);
273
274 if (source == NULL)
275 return;
276
277 uid = e_source_get_uid (source);
278 hash_table = book_shell_view->priv->uid_to_view;
279 widget = g_hash_table_lookup (hash_table, uid);
280
281 if (widget != NULL) {
282 /* There is a view for this UID. Make sure the view
283 * actually contains an EBook. The absence of an EBook
284 * suggests a previous load failed, so try again. */
285 view = E_ADDRESSBOOK_VIEW (widget);
286 model = e_addressbook_view_get_model (view);
287 source = e_addressbook_view_get_source (view);
288
289 if (e_addressbook_model_get_client (model) == NULL)
290 /* XXX No way to cancel this? */
291 e_client_utils_open_new (
292 source, E_CLIENT_SOURCE_TYPE_CONTACTS,
293 FALSE, NULL,
294 book_shell_view_loaded_cb,
295 g_object_ref (view));
296
297 } else {
298 /* Create a view for this UID. */
299 widget = e_addressbook_view_new (shell_view, source);
300 gtk_widget_show (widget);
301
302 /* Default searching options for a new view. */
303 e_addressbook_view_set_search (
304 E_ADDRESSBOOK_VIEW (widget),
305 CONTACT_FILTER_ANY_CATEGORY,
306 CONTACT_SEARCH_NAME_CONTAINS,
307 NULL, NULL);
308
309 e_book_shell_content_insert_view (
310 book_shell_content,
311 E_ADDRESSBOOK_VIEW (widget));
312
313 g_hash_table_insert (
314 hash_table, g_strdup (uid),
315 g_object_ref (widget));
316
317 g_signal_connect_object (
318 widget, "open-contact", G_CALLBACK (open_contact),
319 book_shell_view, G_CONNECT_SWAPPED);
320
321 g_signal_connect_object (
322 widget, "popup-event", G_CALLBACK (popup_event),
323 book_shell_view, G_CONNECT_SWAPPED);
324
325 g_signal_connect_object (
326 widget, "command-state-change",
327 G_CALLBACK (e_shell_view_update_actions),
328 book_shell_view, G_CONNECT_SWAPPED);
329
330 g_signal_connect_object (
331 widget, "selection-change", G_CALLBACK (selection_change),
332 book_shell_view, G_CONNECT_SWAPPED);
333
334 view = E_ADDRESSBOOK_VIEW (widget);
335 model = e_addressbook_view_get_model (view);
336
337 /* XXX No way to cancel this? */
338 e_client_utils_open_new (
339 source, E_CLIENT_SOURCE_TYPE_CONTACTS, FALSE, NULL,
340 book_shell_view_loaded_cb, g_object_ref (view));
341
342 g_signal_connect_object (
343 model, "contact-changed",
344 G_CALLBACK (contact_changed),
345 book_shell_view, G_CONNECT_SWAPPED);
346
347 g_signal_connect_object (
348 model, "contacts-removed",
349 G_CALLBACK (contacts_removed),
350 book_shell_view, G_CONNECT_SWAPPED);
351
352 g_signal_connect_object (
353 model, "notify::query",
354 G_CALLBACK (model_query_changed_cb),
355 book_shell_view, G_CONNECT_SWAPPED);
356 }
357
358 e_book_shell_content_set_current_view (
359 book_shell_content, E_ADDRESSBOOK_VIEW (widget));
360
361 /* XXX We have to keep the addressbook selector informed of the
362 * current view so it can move contacts via drag-and-drop. */
363 e_addressbook_selector_set_current_view (
364 E_ADDRESSBOOK_SELECTOR (selector),
365 E_ADDRESSBOOK_VIEW (widget));
366
367 view_instance = e_addressbook_view_get_view_instance (view);
368
369 /* This must come after e_book_shell_content_set_current_view()
370 * because book_shell_view_notify_view_id_cb() relies on it. */
371 gal_view_instance_load (view_instance);
372
373 view_id = gal_view_instance_get_current_view_id (view_instance);
374 e_shell_view_set_view_id (shell_view, view_id);
375 g_free (view_id);
376
377 e_addressbook_model_force_folder_bar_message (model);
378 selection_change (book_shell_view, view);
379
380 g_object_unref (source);
381 }
382
383 static gboolean
384 book_shell_view_show_popup_menu (GdkEventButton *event,
385 EShellView *shell_view)
386 {
387 const gchar *widget_path;
388
389 widget_path = "/address-book-popup";
390 e_shell_view_show_popup_menu (shell_view, widget_path, event);
391
392 return TRUE;
393 }
394
395 static gboolean
396 book_shell_view_selector_button_press_event_cb (EShellView *shell_view,
397 GdkEventButton *event)
398 {
399 /* XXX Use ESourceSelector's "popup-event" signal instead. */
400
401 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
402 return book_shell_view_show_popup_menu (event, shell_view);
403
404 return FALSE;
405 }
406
407 static gboolean
408 book_shell_view_selector_popup_menu_cb (EShellView *shell_view)
409 {
410 /* XXX Use ESourceSelector's "popup-event" signal instead. */
411
412 return book_shell_view_show_popup_menu (NULL, shell_view);
413 }
414
415 static void
416 book_shell_view_source_removed_cb (ESourceRegistry *registry,
417 ESource *source,
418 EBookShellView *book_shell_view)
419 {
420 EBookShellViewPrivate *priv = book_shell_view->priv;
421 EBookShellContent *book_shell_content;
422 EAddressbookView *view;
423 const gchar *uid;
424
425 uid = e_source_get_uid (source);
426
427 book_shell_content = book_shell_view->priv->book_shell_content;
428
429 /* Remove the EAddressbookView for the deleted source. */
430 view = g_hash_table_lookup (priv->uid_to_view, uid);
431 if (view != NULL) {
432 e_book_shell_content_remove_view (book_shell_content, view);
433 g_hash_table_remove (priv->uid_to_view, uid);
434 }
435
436 e_shell_view_update_actions (E_SHELL_VIEW (book_shell_view));
437 }
438
439 static void
440 book_shell_view_load_view_collection (EShellViewClass *shell_view_class)
441 {
442 GalViewCollection *collection;
443 GalViewFactory *factory;
444 ETableSpecification *spec;
445 const gchar *base_dir;
446 gchar *filename;
447
448 collection = shell_view_class->view_collection;
449
450 base_dir = EVOLUTION_ETSPECDIR;
451 spec = e_table_specification_new ();
452 filename = g_build_filename (base_dir, ETSPEC_FILENAME, NULL);
453 if (!e_table_specification_load_from_file (spec, filename))
454 g_critical (
455 "Unable to load ETable specification file "
456 "for address book");
457 g_free (filename);
458
459 factory = gal_view_factory_etable_new (spec);
460 gal_view_collection_add_factory (collection, factory);
461 g_object_unref (factory);
462 g_object_unref (spec);
463
464 factory = gal_view_factory_minicard_new ();
465 gal_view_collection_add_factory (collection, factory);
466 g_object_unref (factory);
467
468 gal_view_collection_load (collection);
469 }
470
471 static void
472 book_shell_view_notify_view_id_cb (EBookShellView *book_shell_view)
473 {
474 EBookShellContent *book_shell_content;
475 EAddressbookView *address_view;
476 GalViewInstance *view_instance;
477 const gchar *view_id;
478
479 book_shell_content = book_shell_view->priv->book_shell_content;
480 address_view = e_book_shell_content_get_current_view (book_shell_content);
481 view_instance = e_addressbook_view_get_view_instance (address_view);
482 view_id = e_shell_view_get_view_id (E_SHELL_VIEW (book_shell_view));
483
484 /* A NULL view ID implies we're in a custom view. But you can
485 * only get to a custom view via the "Define Views" dialog, which
486 * would have already modified the view instance appropriately.
487 * Furthermore, there's no way to refer to a custom view by ID
488 * anyway, since custom views have no IDs. */
489 if (view_id == NULL)
490 return;
491
492 gal_view_instance_set_current_view_id (view_instance, view_id);
493 }
494
495 void
496 e_book_shell_view_private_init (EBookShellView *book_shell_view,
497 EShellViewClass *shell_view_class)
498 {
499 EBookShellViewPrivate *priv = book_shell_view->priv;
500 GHashTable *uid_to_view;
501
502 uid_to_view = g_hash_table_new_full (
503 g_str_hash, g_str_equal,
504 (GDestroyNotify) g_free,
505 (GDestroyNotify) g_object_unref);
506
507 priv->uid_to_view = uid_to_view;
508 priv->preview_index = -1;
509
510 if (!gal_view_collection_loaded (shell_view_class->view_collection))
511 book_shell_view_load_view_collection (shell_view_class);
512
513 g_signal_connect (
514 book_shell_view, "notify::view-id",
515 G_CALLBACK (book_shell_view_notify_view_id_cb), NULL);
516 }
517
518 void
519 e_book_shell_view_private_constructed (EBookShellView *book_shell_view)
520 {
521 EBookShellViewPrivate *priv = book_shell_view->priv;
522 EShell *shell;
523 EShellView *shell_view;
524 EShellWindow *shell_window;
525 EShellContent *shell_content;
526 EShellSidebar *shell_sidebar;
527 EShellBackend *shell_backend;
528 ESourceSelector *selector;
529
530 shell_view = E_SHELL_VIEW (book_shell_view);
531 shell_backend = e_shell_view_get_shell_backend (shell_view);
532 shell_content = e_shell_view_get_shell_content (shell_view);
533 shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
534 shell_window = e_shell_view_get_shell_window (shell_view);
535 shell = e_shell_window_get_shell (shell_window);
536
537 e_shell_window_add_action_group (shell_window, "contacts");
538 e_shell_window_add_action_group (shell_window, "contacts-filter");
539
540 /* Cache these to avoid lots of awkward casting. */
541 priv->book_shell_backend = g_object_ref (shell_backend);
542 priv->book_shell_content = g_object_ref (shell_content);
543 priv->book_shell_sidebar = g_object_ref (shell_sidebar);
544
545 /* Keep our own reference to this so we can
546 * disconnect our signal handler in dispose(). */
547 priv->registry = g_object_ref (e_shell_get_registry (shell));
548
549 selector = e_book_shell_sidebar_get_selector (
550 E_BOOK_SHELL_SIDEBAR (shell_sidebar));
551
552 g_signal_connect (
553 priv->registry, "source-removed",
554 G_CALLBACK (book_shell_view_source_removed_cb),
555 book_shell_view);
556
557 g_signal_connect_object (
558 selector, "button-press-event",
559 G_CALLBACK (book_shell_view_selector_button_press_event_cb),
560 book_shell_view, G_CONNECT_SWAPPED);
561
562 g_signal_connect_object (
563 selector, "popup-menu",
564 G_CALLBACK (book_shell_view_selector_popup_menu_cb),
565 book_shell_view, G_CONNECT_SWAPPED);
566
567 g_signal_connect_object (
568 selector, "primary-selection-changed",
569 G_CALLBACK (book_shell_view_activate_selected_source),
570 book_shell_view, G_CONNECT_SWAPPED);
571
572 e_categories_add_change_hook (
573 (GHookFunc) e_book_shell_view_update_search_filter,
574 book_shell_view);
575
576 e_book_shell_view_actions_init (book_shell_view);
577 book_shell_view_activate_selected_source (book_shell_view, selector);
578 e_book_shell_view_update_search_filter (book_shell_view);
579 }
580
581 void
582 e_book_shell_view_private_dispose (EBookShellView *book_shell_view)
583 {
584 EBookShellViewPrivate *priv = book_shell_view->priv;
585
586 DISPOSE (priv->book_shell_backend);
587 DISPOSE (priv->book_shell_content);
588 DISPOSE (priv->book_shell_sidebar);
589
590 if (priv->registry != NULL) {
591 g_signal_handlers_disconnect_matched (
592 priv->registry, G_SIGNAL_MATCH_DATA,
593 0, 0, NULL, NULL, book_shell_view);
594 g_object_unref (priv->registry);
595 priv->registry = NULL;
596 }
597
598 g_hash_table_remove_all (priv->uid_to_view);
599 }
600
601 void
602 e_book_shell_view_private_finalize (EBookShellView *book_shell_view)
603 {
604 EBookShellViewPrivate *priv = book_shell_view->priv;
605
606 g_hash_table_destroy (priv->uid_to_view);
607 }