2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2012 Match Grun and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * Contains address clipboard objects and related functions. The address
22 * clipboard is implemented as a linked list of AddrSelectItem objects.
23 * The address clipboard offers two groups of functions:
25 * a) Cut, copy and paste of address item objects (ItemFolder, ItemGroup,
26 * ItemPerson) into a folder. With this method, we can paste ItemPerson
27 * objects but not unattached ItemEMail objects into a folder. ItemEMail
28 * objects are owned by an ItemPerson object. Any ItemEMail objects that
29 * appear in the clipboard are ignored. If an ItemPerson object is found,
30 * the ItemPerson *and* ItemEMail objects that it owns are pasted.
32 * b) Copy and paste of ItemEMail address objects only into (below)
33 * ItemPerson objects. All ItemEMail objects which are owned by
34 * ItemPerson and referenced by ItemGroup objects are pasted. Any
35 * ItemFolder objects in the clipboard, and any objects owned by
36 * ItemFolder objects are ignored.
38 * Objects are inserted to the clipboard by copying (cloning)
39 * AddrSelectItem objects from the address books selection list to the
40 * clipboard's internal selection list. The clipboard makes use of the
41 * object id's and address cache id's to access objects contained in
42 * the address cache. If the referenced object is not found, it is
43 * ignored. This eliminates the need to delete pointers in multiple
44 * linked lists when an address object is deleted.
50 #include <glib/gi18n.h>
52 #include "addrcache.h"
54 #include "addrselect.h"
55 #include "addrindex.h"
57 #include "alertpanel.h"
63 AddressClipboard
*addrclip_create( void ) {
64 AddressClipboard
*clipBoard
;
66 clipBoard
= g_new0( AddressClipboard
, 1 );
67 clipBoard
->cutFlag
= FALSE
;
68 clipBoard
->objectList
= NULL
;
75 void addrclip_clear( AddressClipboard
*clipBoard
) {
79 cm_return_if_fail( clipBoard
!= NULL
);
80 node
= clipBoard
->objectList
;
83 addrselect_item_free( item
);
85 node
= g_list_next( node
);
87 g_list_free( clipBoard
->objectList
);
88 clipBoard
->objectList
= NULL
;
92 * Free up a clipboard.
94 void addrclip_free( AddressClipboard
*clipBoard
) {
95 cm_return_if_fail( clipBoard
!= NULL
);
97 addrclip_clear( clipBoard
);
98 clipBoard
->cutFlag
= FALSE
;
102 * Setup reference to address index.
104 void addrclip_set_index(
105 AddressClipboard
*clipBoard
, AddressIndex
*addrIndex
)
107 cm_return_if_fail( clipBoard
!= NULL
);
108 cm_return_if_fail( addrIndex
!= NULL
);
109 clipBoard
->addressIndex
= addrIndex
;
113 * Test whether clipboard is empty.
114 * Enter: clipBoard Clipboard.
115 * Return: TRUE if clipboard is empty.
117 gboolean
addrclip_is_empty( AddressClipboard
*clipBoard
) {
118 gboolean retVal
= TRUE
;
121 if( clipBoard
->objectList
) retVal
= FALSE
;
127 * Add a list of address selection objects to clipbard.
128 * Enter: clipBoard Clipboard.
129 * addrList List of address selection objects.
131 void addrclip_add( AddressClipboard
*clipBoard
, AddrSelectList
*asl
) {
134 cm_return_if_fail( clipBoard
!= NULL
);
135 cm_return_if_fail( asl
!= NULL
);
136 node
= asl
->listSelect
;
138 AddrSelectItem
*item
, *itemCopy
;
141 itemCopy
= addrselect_item_copy( item
);
142 clipBoard
->objectList
=
143 g_list_append( clipBoard
->objectList
, itemCopy
);
144 node
= g_list_next( node
);
149 * Show clipboard contents.
150 * Enter: clipBoard Clipboard.
151 * stream Output stream.
153 void addrclip_list_show( AddressClipboard
*clipBoard
, FILE *stream
) {
158 cm_return_if_fail( clipBoard
!= NULL
);
159 node
= clipBoard
->objectList
;
160 while( node
!= NULL
) {
161 AddrSelectItem
*item
;
164 addrselect_item_print( item
, stream
);
166 cache
= addrindex_get_cache( clipBoard
->addressIndex
, item
->cacheID
);
167 aio
= addrcache_get_object( cache
, item
->uid
);
169 if( ADDRITEM_TYPE(aio
) == ITEMTYPE_PERSON
) {
170 addritem_print_item_person( ( ItemPerson
* ) aio
, stream
);
172 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_EMAIL
) {
173 addritem_print_item_email( ( ItemEMail
* ) aio
, stream
);
175 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_GROUP
) {
176 addritem_print_item_group( ( ItemGroup
* ) aio
, stream
);
178 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_FOLDER
) {
179 addritem_print_item_folder( ( ItemFolder
* ) aio
, stream
);
182 node
= g_list_next( node
);
186 /* Pasted address pointers */
187 typedef struct _AddrClip_EMail_ AddrClip_EMail
;
188 struct _AddrClip_EMail_
{
194 * Free up specified list of addresses.
196 static void addrclip_free_copy_list( GList
*copyList
) {
201 AddrClip_EMail
*em
= node
->data
;
206 node
= g_list_next( node
);
211 * Paste person into cache.
212 * Enter: cache Address cache to paste into.
213 * folder Folder to store
214 * person Person to paste.
215 * copyLIst List of email addresses pasted.
216 * Return: Update list of email addresses pasted.
218 static GList
*addrclip_cache_add_person(
219 AddressCache
*cache
, ItemFolder
*folder
, ItemPerson
*person
,
222 ItemPerson
*newPerson
;
225 UserAttribute
*attrib
;
226 UserAttribute
*newAttrib
;
231 newPerson
= addritem_copy_item_person( person
);
232 addrcache_id_person( cache
, newPerson
);
233 addrcache_folder_add_person( cache
, folder
, newPerson
);
235 /* Copy email addresses */
236 node
= person
->listEMail
;
239 newEMail
= addritem_copy_item_email( email
);
240 addrcache_id_email( cache
, newEMail
);
241 addrcache_person_add_email( cache
, newPerson
, newEMail
);
242 node
= g_list_next( node
);
244 /* Take a copy of the original */
245 em
= g_new0( AddrClip_EMail
, 1 );
246 em
->original
= email
;
248 copyList
= g_list_append( copyList
, em
);
251 /* Copy user attributes */
252 node
= person
->listAttrib
;
255 newAttrib
= addritem_copy_attribute( attrib
);
256 addrcache_id_attribute( cache
, newAttrib
);
257 addritem_person_add_attribute( newPerson
, newAttrib
);
258 node
= g_list_next( node
);
261 /* Set picture name and create picture file (from copy) if missing */
262 addritem_person_set_picture(newPerson
, ADDRITEM_ID(newPerson
));
263 if( strcmp(ADDRITEM_ID(newPerson
), ADDRITEM_ID(person
)) ) {
265 gchar
*newPictureFile
;
267 pictureFile
= g_strconcat( get_rc_dir(), G_DIR_SEPARATOR_S
, ADDRBOOK_DIR
, G_DIR_SEPARATOR_S
,
268 person
->picture
, ".png", NULL
);
269 newPictureFile
= g_strconcat( get_rc_dir(), G_DIR_SEPARATOR_S
, ADDRBOOK_DIR
, G_DIR_SEPARATOR_S
,
270 newPerson
->picture
, ".png", NULL
);
271 if (file_exist(pictureFile
, FALSE
) && !file_exist(newPictureFile
, FALSE
)) {
272 debug_print("copying contact picture file: %s -> %s\n", person
->picture
, newPerson
->picture
);
273 copy_file(pictureFile
, newPictureFile
, FALSE
);
275 g_free( pictureFile
);
276 g_free( newPictureFile
);
283 * Search for new email record in copied email list.
284 * Enter: copyList List of copied email address mappings.
285 * emailOrig Original email item.
286 * Return: New email item corresponding to original item if pasted. Or NULL if
289 static ItemEMail
*addrclip_find_copied_email(
290 GList
*copyList
, ItemEMail
*emailOrig
)
292 ItemEMail
*emailCopy
;
300 if( em
->original
== emailOrig
) {
301 emailCopy
= em
->copy
;
304 node
= g_list_next( node
);
310 * Paste group into cache.
311 * Enter: cache Address cache to paste into.
312 * folder Folder to store
313 * group Group to paste.
314 * copyList List of email addresses pasted.
315 * Return: Group added.
317 static ItemGroup
*addrclip_cache_add_group(
318 AddressCache
*cache
, ItemFolder
*folder
, ItemGroup
*group
,
322 ItemEMail
*emailOrig
, *emailCopy
;
326 newGroup
= addritem_copy_item_group( group
);
327 addrcache_id_group( cache
, newGroup
);
328 addrcache_folder_add_group( cache
, folder
, newGroup
);
330 /* Add references of copied addresses to group */
331 node
= group
->listEMail
;
333 emailOrig
= ( ItemEMail
* ) node
->data
;
334 emailCopy
= addrclip_find_copied_email( copyList
, emailOrig
);
336 addrcache_group_add_email( cache
, newGroup
, emailCopy
);
338 node
= g_list_next( node
);
344 * Copy specified folder into cache. Note this functions uses pointers to
345 * folders to copy from. There should not be any deleted items referenced
346 * by these pointers!!!
347 * Enter: cache Address cache to copy into.
348 * targetFolder Target folder.
349 * folder Folder to copy.
350 * Return: Folder added.
352 static ItemFolder
*addrclip_cache_copy_folder(
353 AddressCache
*cache
, ItemFolder
*targetFolder
, ItemFolder
*folder
)
355 ItemFolder
*newFolder
;
361 newFolder
= addritem_copy_item_folder( folder
);
362 addrcache_id_folder( cache
, newFolder
);
363 addrcache_folder_add_folder( cache
, targetFolder
, newFolder
);
365 /* Copy people to new folder */
367 node
= folder
->listPerson
;
369 ItemPerson
*item
= node
->data
;
370 node
= g_list_next( node
);
371 copyList
= addrclip_cache_add_person(
372 cache
, newFolder
, item
, copyList
);
375 /* Copy groups to new folder */
376 node
= folder
->listGroup
;
378 ItemGroup
*item
= node
->data
;
379 node
= g_list_next( node
);
380 newGroup
= addrclip_cache_add_group(
381 cache
, newFolder
, item
, copyList
);
382 if (newGroup
== NULL
) {
383 g_message("error allocating memory for new group\n");
386 g_list_free( copyList
);
388 /* Copy folders to new folder (recursive) */
389 node
= folder
->listFolder
;
391 ItemFolder
*item
= node
->data
;
392 node
= g_list_next( node
);
393 addrclip_cache_copy_folder( cache
, newFolder
, item
);
399 static gboolean
addrclip_is_subfolder_of(ItemFolder
*is_parent
, ItemFolder
*is_child
)
404 cm_return_val_if_fail(is_parent
!= NULL
, FALSE
);
405 cm_return_val_if_fail(is_child
!= NULL
, FALSE
);
407 if (is_parent
== is_child
)
411 obj
= folder
->obj
.parent
;
413 if ((void*)obj
== (void*)is_parent
)
421 * Paste item list into address book.
422 * Enter: cache Target address cache.
423 * folder Target folder where data is pasted.
424 * itemList List of items to paste.
425 * clipBoard Clipboard.
426 * Return: List of group or folder items added.
428 static GList
*addrclip_cache_add_folder(
429 AddressCache
*cache
, ItemFolder
*folder
, GList
*itemList
,
430 AddressClipboard
*clipBoard
)
434 AddrSelectItem
*item
;
436 AddressCache
*cacheFrom
;
446 node
= g_list_next( node
);
448 cacheFrom
= addrindex_get_cache(
449 clipBoard
->addressIndex
, item
->cacheID
);
450 if( cacheFrom
== NULL
) continue;
452 aio
= addrcache_get_object( cacheFrom
, item
->uid
);
454 if( ADDRITEM_TYPE(aio
) == ITEMTYPE_PERSON
) {
457 person
= ( ItemPerson
* ) aio
;
458 copyList
= addrclip_cache_add_person(
459 cache
, folder
, person
, copyList
);
462 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_EMAIL ) {
465 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_GROUP
) {
466 haveGroups
= TRUE
; /* Process later */
468 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_FOLDER
) {
469 ItemFolder
*itemFolder
, *newFolder
;
471 itemFolder
= ( ItemFolder
* ) aio
;
472 if (!addrclip_is_subfolder_of(itemFolder
, folder
)) {
473 newFolder
= addrclip_cache_copy_folder(
474 cache
, folder
, itemFolder
);
476 g_list_append( folderGroup
, newFolder
);
479 _("Cannot copy a folder to itself or to its sub-structure.") );
485 if( item
->objectType
== ITEMTYPE_DATASOURCE
) {
487 * Must be an address book - allow copy only if
488 * copying from a different cache.
490 if( cache
!= cacheFrom
) {
491 ItemFolder
*itemFolder
, *newFolder
;
493 itemFolder
= cacheFrom
->rootFolder
;
494 newFolder
= addrclip_cache_copy_folder(
495 cache
, folder
, itemFolder
);
496 addritem_folder_set_name( newFolder
,
497 addrcache_get_name( cacheFrom
) );
499 g_list_append( folderGroup
, newFolder
);
502 _("Cannot copy an address book to itself.") );
508 /* Finally add any groups */
513 node
= g_list_next( node
);
514 cacheFrom
= addrindex_get_cache(
515 clipBoard
->addressIndex
, item
->cacheID
);
516 if( cacheFrom
== NULL
) continue;
517 aio
= addrcache_get_object( cacheFrom
, item
->uid
);
519 if( ADDRITEM_TYPE(aio
) == ITEMTYPE_GROUP
) {
520 ItemGroup
*group
, *newGroup
;
522 group
= ( ItemGroup
* ) aio
;
523 newGroup
= addrclip_cache_add_group(
524 cache
, folder
, group
, copyList
);
526 g_list_append( folderGroup
, newGroup
);
533 addrclip_free_copy_list( copyList
);
534 g_list_free( copyList
);
541 * Move items in list into new folder
542 * Enter: cache Target address cache.
543 * targetFolder Target folder where data is pasted.
544 * itemList List of items to paste.
545 * clipBoard Clipboard.
546 * Return: List of group or folder items added.
548 static GList
*addrclip_cache_move_items(
549 AddressCache
*cache
, ItemFolder
*targetFolder
, GList
*itemList
,
550 AddressClipboard
*clipBoard
)
554 AddrSelectItem
*item
;
556 AddressCache
*cacheFrom
;
562 node
= g_list_next( node
);
563 cacheFrom
= addrindex_get_cache(
564 clipBoard
->addressIndex
, item
->cacheID
);
565 if( cacheFrom
== NULL
) continue;
566 aio
= addrcache_get_object( cacheFrom
, item
->uid
);
568 if( ADDRITEM_TYPE(aio
) == ITEMTYPE_PERSON
) {
571 person
= ( ItemPerson
* ) aio
;
572 addrcache_folder_move_person(
573 cache
, person
, targetFolder
);
575 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_GROUP
) {
578 group
= ( ItemGroup
* ) aio
;
579 addrcache_folder_move_group(
580 cache
, group
, targetFolder
);
581 folderGroup
= g_list_append( folderGroup
, group
);
583 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_FOLDER
) {
584 ItemFolder
*folder
= ( ItemFolder
* ) aio
;
586 if (!addrclip_is_subfolder_of(folder
, targetFolder
)) {
587 addrcache_folder_move_folder(
588 cache
, folder
, targetFolder
);
590 g_list_append( folderGroup
, folder
);
593 _("Cannot move a folder to itself or to its sub-structure.") );
602 * Get address cache of first item in list. This assumes that all items in
603 * the clipboard are located in the same cache.
604 * Enter: clipBoard Clipboard.
605 * Return: List of group or folder items added.
607 static AddressCache
*addrclip_list_get_cache( AddressClipboard
*clipBoard
) {
610 AddrSelectItem
*item
;
613 itemList
= clipBoard
->objectList
;
615 item
= itemList
->data
;
616 cache
= addrindex_get_cache(
617 clipBoard
->addressIndex
, item
->cacheID
);
623 * Paste (copy) clipboard into address book.
624 * Enter: clipBoard Clipboard.
625 * book Target address book.
626 * folder Target folder where data is pasted, or null for root folder.
627 * Return: List of group or folder items added.
629 GList
*addrclip_paste_copy(
630 AddressClipboard
*clipBoard
, AddressBookFile
*book
,
637 cm_return_val_if_fail( clipBoard
!= NULL
, NULL
);
639 cache
= book
->addressCache
;
640 if( folder
== NULL
) folder
= cache
->rootFolder
;
643 itemList
= clipBoard
->objectList
;
644 folderGroup
= addrclip_cache_add_folder(
645 cache
, folder
, itemList
, clipBoard
);
651 * Remove items that were cut from clipboard.
652 * Enter: clipBoard Clipboard.
654 void addrclip_delete_item( AddressClipboard
*clipBoard
) {
655 AddrSelectItem
*item
;
657 AddressCache
*cacheFrom
;
660 /* If cutting within current cache, no deletion is necessary */
661 if( clipBoard
->moveFlag
) return;
664 node
= clipBoard
->objectList
;
667 node
= g_list_next( node
);
668 cacheFrom
= addrindex_get_cache(
669 clipBoard
->addressIndex
, item
->cacheID
);
670 if( cacheFrom
== NULL
) continue;
671 aio
= addrcache_get_object( cacheFrom
, item
->uid
);
673 if( ADDRITEM_TYPE(aio
) == ITEMTYPE_GROUP
) {
676 group
= ( ItemGroup
* ) aio
;
677 group
= addrcache_remove_group( cacheFrom
, group
);
679 addritem_free_item_group( group
);
685 /* Remove persons and folders */
686 node
= clipBoard
->objectList
;
689 node
= g_list_next( node
);
691 cacheFrom
= addrindex_get_cache(
692 clipBoard
->addressIndex
, item
->cacheID
);
693 if( cacheFrom
== NULL
) continue;
695 aio
= addrcache_get_object( cacheFrom
, item
->uid
);
697 if( ADDRITEM_TYPE(aio
) == ITEMTYPE_PERSON
) {
700 person
= ( ItemPerson
* ) aio
;
701 person
= addrcache_remove_person( cacheFrom
, person
);
703 addritem_free_item_person( person
);
706 else if( ADDRITEM_TYPE(aio
) == ITEMTYPE_FOLDER
) {
707 ItemFolder
*itemFolder
;
709 itemFolder
= ( ItemFolder
* ) aio
;
710 itemFolder
= addrcache_remove_folder_delete(
711 cacheFrom
, itemFolder
);
712 addritem_free_item_folder( itemFolder
);
719 * Paste (move) clipboard into address book.
720 * Enter: clipBoard Clipboard.
721 * book Target address book.
722 * folder Target folder where data is pasted, or null for root folder.
723 * Return: List of group or folder items added.
725 GList
*addrclip_paste_cut(
726 AddressClipboard
*clipBoard
, AddressBookFile
*book
,
729 AddressCache
*cache
, *cacheFrom
;
733 cm_return_val_if_fail( clipBoard
!= NULL
, NULL
);
735 cache
= book
->addressCache
;
736 if( folder
== NULL
) folder
= cache
->rootFolder
;
739 clipBoard
->moveFlag
= FALSE
;
740 cacheFrom
= addrclip_list_get_cache( clipBoard
);
741 if( cacheFrom
&& cacheFrom
== cache
) {
742 /* Move items between folders in same book */
743 itemList
= clipBoard
->objectList
;
744 folderGroup
= addrclip_cache_move_items(
745 cache
, folder
, itemList
, clipBoard
);
746 clipBoard
->moveFlag
= TRUE
;
749 /* Move items across address books */
750 itemList
= clipBoard
->objectList
;
751 folderGroup
= addrclip_cache_add_folder(
752 cache
, folder
, itemList
, clipBoard
);