Fix bug #3574: Template addressing
[claws.git] / src / addrclip.c
blob1d469c25115be0de3b6ee694dd476320310a938d
1 /*
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.
48 #include <stdio.h>
49 #include <glib.h>
50 #include <glib/gi18n.h>
52 #include "addrcache.h"
53 #include "addrbook.h"
54 #include "addrselect.h"
55 #include "addrindex.h"
56 #include "addrclip.h"
57 #include "alertpanel.h"
58 #include "defs.h"
61 * Create a clipboard.
63 AddressClipboard *addrclip_create( void ) {
64 AddressClipboard *clipBoard;
66 clipBoard = g_new0( AddressClipboard, 1 );
67 clipBoard->cutFlag = FALSE;
68 clipBoard->objectList = NULL;
69 return clipBoard;
73 * Clear clipboard.
75 void addrclip_clear( AddressClipboard *clipBoard ) {
76 GList *node;
77 AddrSelectItem *item;
79 cm_return_if_fail( clipBoard != NULL );
80 node = clipBoard->objectList;
81 while( node ) {
82 item = node->data;
83 addrselect_item_free( item );
84 node->data = NULL;
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;
120 if( clipBoard ) {
121 if( clipBoard->objectList ) retVal = FALSE;
123 return retVal;
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 ) {
132 GList *node;
134 cm_return_if_fail( clipBoard != NULL );
135 cm_return_if_fail( asl != NULL );
136 node = asl->listSelect;
137 while( node ) {
138 AddrSelectItem *item, *itemCopy;
140 item = node->data;
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 ) {
154 GList *node;
155 AddrItemObject *aio;
156 AddressCache *cache;
158 cm_return_if_fail( clipBoard != NULL );
159 node = clipBoard->objectList;
160 while( node != NULL ) {
161 AddrSelectItem *item;
163 item = node->data;
164 addrselect_item_print( item, stream );
166 cache = addrindex_get_cache( clipBoard->addressIndex, item->cacheID );
167 aio = addrcache_get_object( cache, item->uid );
168 if( aio ) {
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_ {
189 ItemEMail *original;
190 ItemEMail *copy;
194 * Free up specified list of addresses.
196 static void addrclip_free_copy_list( GList *copyList ) {
197 GList *node;
199 node = copyList;
200 while( node ) {
201 AddrClip_EMail *em = node->data;
202 em->original = NULL;
203 em->copy = NULL;
204 g_free( em );
205 em = NULL;
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,
220 GList *copyList )
222 ItemPerson *newPerson;
223 ItemEMail *email;
224 ItemEMail *newEMail;
225 UserAttribute *attrib;
226 UserAttribute *newAttrib;
227 GList *node;
228 AddrClip_EMail *em;
230 /* Copy person */
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;
237 while( node ) {
238 email = node->data;
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;
247 em->copy = newEMail;
248 copyList = g_list_append( copyList, em );
251 /* Copy user attributes */
252 node = person->listAttrib;
253 while( node ) {
254 attrib = node->data;
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)) ) {
264 gchar *pictureFile;
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 );
279 return copyList;
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
287 * not found.
289 static ItemEMail *addrclip_find_copied_email(
290 GList *copyList, ItemEMail *emailOrig )
292 ItemEMail *emailCopy;
293 GList *node;
294 AddrClip_EMail *em;
296 emailCopy = NULL;
297 node = copyList;
298 while( node ) {
299 em = node->data;
300 if( em->original == emailOrig ) {
301 emailCopy = em->copy;
302 break;
304 node = g_list_next( node );
306 return emailCopy;
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,
319 GList *copyList )
321 ItemGroup *newGroup;
322 ItemEMail *emailOrig, *emailCopy;
323 GList *node;
325 /* Copy group */
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;
332 while( node ) {
333 emailOrig = ( ItemEMail * ) node->data;
334 emailCopy = addrclip_find_copied_email( copyList, emailOrig );
335 if( emailCopy ) {
336 addrcache_group_add_email( cache, newGroup, emailCopy );
338 node = g_list_next( node );
340 return newGroup;
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;
356 ItemGroup *newGroup;
357 GList *node;
358 GList *copyList;
360 /* Copy folder */
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 */
366 copyList = NULL;
367 node = folder->listPerson;
368 while( node ) {
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;
377 while( node ) {
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;
390 while( node ) {
391 ItemFolder *item = node->data;
392 node = g_list_next( node );
393 addrclip_cache_copy_folder( cache, newFolder, item );
396 return newFolder;
399 static gboolean addrclip_is_subfolder_of(ItemFolder *is_parent, ItemFolder *is_child)
401 ItemFolder *folder;
402 AddrItemObject *obj;
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)
408 return TRUE;
410 folder = is_child;
411 obj = folder->obj.parent;
412 while (obj) {
413 if ((void*)obj == (void*)is_parent)
414 return TRUE;
415 obj = obj->parent;
417 return FALSE;
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 )
432 GList *folderGroup;
433 GList *node;
434 AddrSelectItem *item;
435 AddrItemObject *aio;
436 AddressCache *cacheFrom;
437 gboolean haveGroups;
438 GList *copyList;
440 folderGroup = NULL;
441 copyList = NULL;
442 haveGroups = FALSE;
443 node = itemList;
444 while( node ) {
445 item = node->data;
446 node = g_list_next( node );
448 cacheFrom = addrindex_get_cache(
449 clipBoard->addressIndex, item->cacheID );
450 if( cacheFrom == NULL ) continue;
451 if( item->uid ) {
452 aio = addrcache_get_object( cacheFrom, item->uid );
453 if( aio ) {
454 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
455 ItemPerson *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 );
475 folderGroup =
476 g_list_append( folderGroup, newFolder );
477 } else {
478 alertpanel_error(
479 _("Cannot copy a folder to itself or to its sub-structure.") );
484 else {
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 ) );
498 folderGroup =
499 g_list_append( folderGroup, newFolder );
500 } else {
501 alertpanel_error(
502 _("Cannot copy an address book to itself.") );
508 /* Finally add any groups */
509 if( haveGroups ) {
510 node = itemList;
511 while( node ) {
512 item = node->data;
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 );
518 if( aio ) {
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 );
525 folderGroup =
526 g_list_append( folderGroup, newGroup );
532 /* Free up stuff */
533 addrclip_free_copy_list( copyList );
534 g_list_free( copyList );
535 copyList = NULL;
537 return folderGroup;
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 )
552 GList *folderGroup;
553 GList *node;
554 AddrSelectItem *item;
555 AddrItemObject *aio;
556 AddressCache *cacheFrom;
558 folderGroup = NULL;
559 node = itemList;
560 while( node ) {
561 item = node->data;
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 );
567 if( aio ) {
568 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
569 ItemPerson *person;
571 person = ( ItemPerson * ) aio;
572 addrcache_folder_move_person(
573 cache, person, targetFolder );
575 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
576 ItemGroup *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 );
589 folderGroup =
590 g_list_append( folderGroup, folder );
591 } else {
592 alertpanel_error(
593 _("Cannot move a folder to itself or to its sub-structure.") );
598 return folderGroup;
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 ) {
608 AddressCache *cache;
609 GList *itemList;
610 AddrSelectItem *item;
612 cache = NULL;
613 itemList = clipBoard->objectList;
614 if( itemList ) {
615 item = itemList->data;
616 cache = addrindex_get_cache(
617 clipBoard->addressIndex, item->cacheID );
619 return cache;
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,
631 ItemFolder *folder )
633 AddressCache *cache;
634 GList *itemList;
635 GList *folderGroup;
637 cm_return_val_if_fail( clipBoard != NULL, NULL );
639 cache = book->addressCache;
640 if( folder == NULL ) folder = cache->rootFolder;
642 folderGroup = NULL;
643 itemList = clipBoard->objectList;
644 folderGroup = addrclip_cache_add_folder(
645 cache, folder, itemList, clipBoard );
647 return folderGroup;
651 * Remove items that were cut from clipboard.
652 * Enter: clipBoard Clipboard.
654 void addrclip_delete_item( AddressClipboard *clipBoard ) {
655 AddrSelectItem *item;
656 AddrItemObject *aio;
657 AddressCache *cacheFrom;
658 GList *node;
660 /* If cutting within current cache, no deletion is necessary */
661 if( clipBoard->moveFlag ) return;
663 /* Remove groups */
664 node = clipBoard->objectList;
665 while( node ) {
666 item = node->data;
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 );
672 if( aio ) {
673 if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
674 ItemGroup *group;
676 group = ( ItemGroup * ) aio;
677 group = addrcache_remove_group( cacheFrom, group );
678 if( group ) {
679 addritem_free_item_group( group );
685 /* Remove persons and folders */
686 node = clipBoard->objectList;
687 while( node ) {
688 item = node->data;
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 );
696 if( aio ) {
697 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
698 ItemPerson *person;
700 person = ( ItemPerson * ) aio;
701 person = addrcache_remove_person( cacheFrom, person );
702 if( 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,
727 ItemFolder *folder )
729 AddressCache *cache, *cacheFrom;
730 GList *itemList;
731 GList *folderGroup;
733 cm_return_val_if_fail( clipBoard != NULL, NULL );
735 cache = book->addressCache;
736 if( folder == NULL ) folder = cache->rootFolder;
738 folderGroup = NULL;
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;
748 else {
749 /* Move items across address books */
750 itemList = clipBoard->objectList;
751 folderGroup = addrclip_cache_add_folder(
752 cache, folder, itemList, clipBoard );
755 return folderGroup;
758 * End of Source.