enable external editor on windows. patch by Thorsten Maerz
[claws.git] / src / addrclip.c
blob58d59477644e8dc3fd98163f6098f2c4dc367cc8
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"
59 #include "file-utils.h"
62 * Create a clipboard.
64 AddressClipboard *addrclip_create( void ) {
65 AddressClipboard *clipBoard;
67 clipBoard = g_new0( AddressClipboard, 1 );
68 clipBoard->cutFlag = FALSE;
69 clipBoard->objectList = NULL;
70 return clipBoard;
74 * Clear clipboard.
76 void addrclip_clear( AddressClipboard *clipBoard ) {
77 GList *node;
78 AddrSelectItem *item;
80 cm_return_if_fail( clipBoard != NULL );
81 node = clipBoard->objectList;
82 while( node ) {
83 item = node->data;
84 addrselect_item_free( item );
85 node->data = NULL;
86 node = g_list_next( node );
88 g_list_free( clipBoard->objectList );
89 clipBoard->objectList = NULL;
93 * Free up a clipboard.
95 void addrclip_free( AddressClipboard *clipBoard ) {
96 cm_return_if_fail( clipBoard != NULL );
98 addrclip_clear( clipBoard );
99 clipBoard->cutFlag = FALSE;
100 g_free(clipBoard);
104 * Setup reference to address index.
106 void addrclip_set_index(
107 AddressClipboard *clipBoard, AddressIndex *addrIndex )
109 cm_return_if_fail( clipBoard != NULL );
110 cm_return_if_fail( addrIndex != NULL );
111 clipBoard->addressIndex = addrIndex;
115 * Test whether clipboard is empty.
116 * Enter: clipBoard Clipboard.
117 * Return: TRUE if clipboard is empty.
119 gboolean addrclip_is_empty( AddressClipboard *clipBoard ) {
120 gboolean retVal = TRUE;
122 if( clipBoard ) {
123 if( clipBoard->objectList ) retVal = FALSE;
125 return retVal;
129 * Add a list of address selection objects to clipbard.
130 * Enter: clipBoard Clipboard.
131 * addrList List of address selection objects.
133 void addrclip_add( AddressClipboard *clipBoard, AddrSelectList *asl ) {
134 GList *node;
136 cm_return_if_fail( clipBoard != NULL );
137 cm_return_if_fail( asl != NULL );
138 node = asl->listSelect;
139 while( node ) {
140 AddrSelectItem *item, *itemCopy;
142 item = node->data;
143 itemCopy = addrselect_item_copy( item );
144 clipBoard->objectList =
145 g_list_append( clipBoard->objectList, itemCopy );
146 node = g_list_next( node );
151 * Show clipboard contents.
152 * Enter: clipBoard Clipboard.
153 * stream Output stream.
155 void addrclip_list_show( AddressClipboard *clipBoard, FILE *stream ) {
156 GList *node;
157 AddrItemObject *aio;
158 AddressCache *cache;
160 cm_return_if_fail( clipBoard != NULL );
161 node = clipBoard->objectList;
162 while( node != NULL ) {
163 AddrSelectItem *item;
165 item = node->data;
166 addrselect_item_print( item, stream );
168 cache = addrindex_get_cache( clipBoard->addressIndex, item->cacheID );
169 aio = addrcache_get_object( cache, item->uid );
170 if( aio ) {
171 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
172 addritem_print_item_person( ( ItemPerson * ) aio, stream );
174 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_EMAIL ) {
175 addritem_print_item_email( ( ItemEMail * ) aio, stream );
177 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
178 addritem_print_item_group( ( ItemGroup * ) aio, stream );
180 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
181 addritem_print_item_folder( ( ItemFolder * ) aio, stream );
184 node = g_list_next( node );
188 /* Pasted address pointers */
189 typedef struct _AddrClip_EMail_ AddrClip_EMail;
190 struct _AddrClip_EMail_ {
191 ItemEMail *original;
192 ItemEMail *copy;
196 * Free up specified list of addresses.
198 static void addrclip_free_copy_list( GList *copyList ) {
199 GList *node;
201 node = copyList;
202 while( node ) {
203 AddrClip_EMail *em = node->data;
204 em->original = NULL;
205 em->copy = NULL;
206 g_free( em );
207 em = NULL;
208 node = g_list_next( node );
213 * Paste person into cache.
214 * Enter: cache Address cache to paste into.
215 * folder Folder to store
216 * person Person to paste.
217 * copyLIst List of email addresses pasted.
218 * Return: Update list of email addresses pasted.
220 static GList *addrclip_cache_add_person(
221 AddressCache *cache, ItemFolder *folder, ItemPerson *person,
222 GList *copyList )
224 ItemPerson *newPerson;
225 ItemEMail *email;
226 ItemEMail *newEMail;
227 UserAttribute *attrib;
228 UserAttribute *newAttrib;
229 GList *node;
230 AddrClip_EMail *em;
232 /* Copy person */
233 newPerson = addritem_copy_item_person( person );
234 addrcache_id_person( cache, newPerson );
235 addrcache_folder_add_person( cache, folder, newPerson );
237 /* Copy email addresses */
238 node = person->listEMail;
239 while( node ) {
240 email = node->data;
241 newEMail = addritem_copy_item_email( email );
242 addrcache_id_email( cache, newEMail );
243 addrcache_person_add_email( cache, newPerson, newEMail );
244 node = g_list_next( node );
246 /* Take a copy of the original */
247 em = g_new0( AddrClip_EMail, 1 );
248 em->original = email;
249 em->copy = newEMail;
250 copyList = g_list_append( copyList, em );
253 /* Copy user attributes */
254 node = person->listAttrib;
255 while( node ) {
256 attrib = node->data;
257 newAttrib = addritem_copy_attribute( attrib );
258 addrcache_id_attribute( cache, newAttrib );
259 addritem_person_add_attribute( newPerson, newAttrib );
260 node = g_list_next( node );
263 /* Set picture name and create picture file (from copy) if missing */
264 addritem_person_set_picture(newPerson, ADDRITEM_ID(newPerson));
265 if( strcmp(ADDRITEM_ID(newPerson), ADDRITEM_ID(person)) ) {
266 gchar *pictureFile;
267 gchar *newPictureFile;
269 pictureFile = g_strconcat( get_rc_dir(), G_DIR_SEPARATOR_S, ADDRBOOK_DIR, G_DIR_SEPARATOR_S,
270 person->picture, ".png", NULL );
271 newPictureFile = g_strconcat( get_rc_dir(), G_DIR_SEPARATOR_S, ADDRBOOK_DIR, G_DIR_SEPARATOR_S,
272 newPerson->picture, ".png", NULL );
273 if (file_exist(pictureFile, FALSE) && !file_exist(newPictureFile, FALSE)) {
274 debug_print("copying contact picture file: %s -> %s\n", person->picture, newPerson->picture);
275 copy_file(pictureFile, newPictureFile, FALSE);
277 g_free( pictureFile );
278 g_free( newPictureFile );
281 return copyList;
285 * Search for new email record in copied email list.
286 * Enter: copyList List of copied email address mappings.
287 * emailOrig Original email item.
288 * Return: New email item corresponding to original item if pasted. Or NULL if
289 * not found.
291 static ItemEMail *addrclip_find_copied_email(
292 GList *copyList, ItemEMail *emailOrig )
294 ItemEMail *emailCopy;
295 GList *node;
296 AddrClip_EMail *em;
298 emailCopy = NULL;
299 node = copyList;
300 while( node ) {
301 em = node->data;
302 if( em->original == emailOrig ) {
303 emailCopy = em->copy;
304 break;
306 node = g_list_next( node );
308 return emailCopy;
312 * Paste group into cache.
313 * Enter: cache Address cache to paste into.
314 * folder Folder to store
315 * group Group to paste.
316 * copyList List of email addresses pasted.
317 * Return: Group added.
319 static ItemGroup *addrclip_cache_add_group(
320 AddressCache *cache, ItemFolder *folder, ItemGroup *group,
321 GList *copyList )
323 ItemGroup *newGroup;
324 ItemEMail *emailOrig, *emailCopy;
325 GList *node;
327 /* Copy group */
328 newGroup = addritem_copy_item_group( group );
329 addrcache_id_group( cache, newGroup );
330 addrcache_folder_add_group( cache, folder, newGroup );
332 /* Add references of copied addresses to group */
333 node = group->listEMail;
334 while( node ) {
335 emailOrig = ( ItemEMail * ) node->data;
336 emailCopy = addrclip_find_copied_email( copyList, emailOrig );
337 if( emailCopy ) {
338 addrcache_group_add_email( cache, newGroup, emailCopy );
340 node = g_list_next( node );
342 return newGroup;
346 * Copy specified folder into cache. Note this functions uses pointers to
347 * folders to copy from. There should not be any deleted items referenced
348 * by these pointers!!!
349 * Enter: cache Address cache to copy into.
350 * targetFolder Target folder.
351 * folder Folder to copy.
352 * Return: Folder added.
354 static ItemFolder *addrclip_cache_copy_folder(
355 AddressCache *cache, ItemFolder *targetFolder, ItemFolder *folder )
357 ItemFolder *newFolder;
358 ItemGroup *newGroup;
359 GList *node;
360 GList *copyList;
362 /* Copy folder */
363 newFolder = addritem_copy_item_folder( folder );
364 addrcache_id_folder( cache, newFolder );
365 addrcache_folder_add_folder( cache, targetFolder, newFolder );
367 /* Copy people to new folder */
368 copyList = NULL;
369 node = folder->listPerson;
370 while( node ) {
371 ItemPerson *item = node->data;
372 node = g_list_next( node );
373 copyList = addrclip_cache_add_person(
374 cache, newFolder, item, copyList );
377 /* Copy groups to new folder */
378 node = folder->listGroup;
379 while( node ) {
380 ItemGroup *item = node->data;
381 node = g_list_next( node );
382 newGroup = addrclip_cache_add_group(
383 cache, newFolder, item, copyList );
384 if (newGroup == NULL) {
385 g_message("error allocating memory for new group\n");
388 g_list_free( copyList );
390 /* Copy folders to new folder (recursive) */
391 node = folder->listFolder;
392 while( node ) {
393 ItemFolder *item = node->data;
394 node = g_list_next( node );
395 addrclip_cache_copy_folder( cache, newFolder, item );
398 return newFolder;
401 static gboolean addrclip_is_subfolder_of(ItemFolder *is_parent, ItemFolder *is_child)
403 ItemFolder *folder;
404 AddrItemObject *obj;
406 cm_return_val_if_fail(is_parent != NULL, FALSE);
407 cm_return_val_if_fail(is_child != NULL, FALSE);
409 if (is_parent == is_child)
410 return TRUE;
412 folder = is_child;
413 obj = folder->obj.parent;
414 while (obj) {
415 if ((void*)obj == (void*)is_parent)
416 return TRUE;
417 obj = obj->parent;
419 return FALSE;
423 * Paste item list into address book.
424 * Enter: cache Target address cache.
425 * folder Target folder where data is pasted.
426 * itemList List of items to paste.
427 * clipBoard Clipboard.
428 * Return: List of group or folder items added.
430 static GList *addrclip_cache_add_folder(
431 AddressCache *cache, ItemFolder *folder, GList *itemList,
432 AddressClipboard *clipBoard )
434 GList *folderGroup;
435 GList *node;
436 AddrSelectItem *item;
437 AddrItemObject *aio;
438 AddressCache *cacheFrom;
439 gboolean haveGroups;
440 GList *copyList;
442 folderGroup = NULL;
443 copyList = NULL;
444 haveGroups = FALSE;
445 node = itemList;
446 while( node ) {
447 item = node->data;
448 node = g_list_next( node );
450 cacheFrom = addrindex_get_cache(
451 clipBoard->addressIndex, item->cacheID );
452 if( cacheFrom == NULL ) continue;
453 if( item->uid ) {
454 aio = addrcache_get_object( cacheFrom, item->uid );
455 if( aio ) {
456 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
457 ItemPerson *person;
459 person = ( ItemPerson * ) aio;
460 copyList = addrclip_cache_add_person(
461 cache, folder, person, copyList );
464 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_EMAIL ) {
467 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
468 haveGroups = TRUE; /* Process later */
470 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
471 ItemFolder *itemFolder, *newFolder;
473 itemFolder = ( ItemFolder * ) aio;
474 if (!addrclip_is_subfolder_of(itemFolder, folder)) {
475 newFolder = addrclip_cache_copy_folder(
476 cache, folder, itemFolder );
477 folderGroup =
478 g_list_append( folderGroup, newFolder );
479 } else {
480 alertpanel_error(
481 _("Cannot copy a folder to itself or to its sub-structure.") );
486 else {
487 if( item->objectType == ITEMTYPE_DATASOURCE ) {
489 * Must be an address book - allow copy only if
490 * copying from a different cache.
492 if( cache != cacheFrom ) {
493 ItemFolder *itemFolder, *newFolder;
495 itemFolder = cacheFrom->rootFolder;
496 newFolder = addrclip_cache_copy_folder(
497 cache, folder, itemFolder );
498 addritem_folder_set_name( newFolder,
499 addrcache_get_name( cacheFrom ) );
500 folderGroup =
501 g_list_append( folderGroup, newFolder );
502 } else {
503 alertpanel_error(
504 _("Cannot copy an address book to itself.") );
510 /* Finally add any groups */
511 if( haveGroups ) {
512 node = itemList;
513 while( node ) {
514 item = node->data;
515 node = g_list_next( node );
516 cacheFrom = addrindex_get_cache(
517 clipBoard->addressIndex, item->cacheID );
518 if( cacheFrom == NULL ) continue;
519 aio = addrcache_get_object( cacheFrom, item->uid );
520 if( aio ) {
521 if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
522 ItemGroup *group, *newGroup;
524 group = ( ItemGroup * ) aio;
525 newGroup = addrclip_cache_add_group(
526 cache, folder, group, copyList );
527 folderGroup =
528 g_list_append( folderGroup, newGroup );
534 /* Free up stuff */
535 addrclip_free_copy_list( copyList );
536 g_list_free( copyList );
537 copyList = NULL;
539 return folderGroup;
543 * Move items in list into new folder
544 * Enter: cache Target address cache.
545 * targetFolder Target folder where data is pasted.
546 * itemList List of items to paste.
547 * clipBoard Clipboard.
548 * Return: List of group or folder items added.
550 static GList *addrclip_cache_move_items(
551 AddressCache *cache, ItemFolder *targetFolder, GList *itemList,
552 AddressClipboard *clipBoard )
554 GList *folderGroup;
555 GList *node;
556 AddrSelectItem *item;
557 AddrItemObject *aio;
558 AddressCache *cacheFrom;
560 folderGroup = NULL;
561 node = itemList;
562 while( node ) {
563 item = node->data;
564 node = g_list_next( node );
565 cacheFrom = addrindex_get_cache(
566 clipBoard->addressIndex, item->cacheID );
567 if( cacheFrom == NULL ) continue;
568 aio = addrcache_get_object( cacheFrom, item->uid );
569 if( aio ) {
570 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
571 ItemPerson *person;
573 person = ( ItemPerson * ) aio;
574 addrcache_folder_move_person(
575 cache, person, targetFolder );
577 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
578 ItemGroup *group;
580 group = ( ItemGroup * ) aio;
581 addrcache_folder_move_group(
582 cache, group, targetFolder );
583 folderGroup = g_list_append( folderGroup, group );
585 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
586 ItemFolder *folder = ( ItemFolder * ) aio;
588 if (!addrclip_is_subfolder_of(folder, targetFolder)) {
589 addrcache_folder_move_folder(
590 cache, folder, targetFolder );
591 folderGroup =
592 g_list_append( folderGroup, folder );
593 } else {
594 alertpanel_error(
595 _("Cannot move a folder to itself or to its sub-structure.") );
600 return folderGroup;
604 * Get address cache of first item in list. This assumes that all items in
605 * the clipboard are located in the same cache.
606 * Enter: clipBoard Clipboard.
607 * Return: List of group or folder items added.
609 static AddressCache *addrclip_list_get_cache( AddressClipboard *clipBoard ) {
610 AddressCache *cache;
611 GList *itemList;
612 AddrSelectItem *item;
614 cache = NULL;
615 itemList = clipBoard->objectList;
616 if( itemList ) {
617 item = itemList->data;
618 cache = addrindex_get_cache(
619 clipBoard->addressIndex, item->cacheID );
621 return cache;
625 * Paste (copy) clipboard into address book.
626 * Enter: clipBoard Clipboard.
627 * book Target address book.
628 * folder Target folder where data is pasted, or null for root folder.
629 * Return: List of group or folder items added.
631 GList *addrclip_paste_copy(
632 AddressClipboard *clipBoard, AddressBookFile *book,
633 ItemFolder *folder )
635 AddressCache *cache;
636 GList *itemList;
637 GList *folderGroup;
639 cm_return_val_if_fail( clipBoard != NULL, NULL );
641 cache = book->addressCache;
642 if( folder == NULL ) folder = cache->rootFolder;
644 folderGroup = NULL;
645 itemList = clipBoard->objectList;
646 folderGroup = addrclip_cache_add_folder(
647 cache, folder, itemList, clipBoard );
649 return folderGroup;
653 * Remove items that were cut from clipboard.
654 * Enter: clipBoard Clipboard.
656 void addrclip_delete_item( AddressClipboard *clipBoard ) {
657 AddrSelectItem *item;
658 AddrItemObject *aio;
659 AddressCache *cacheFrom;
660 GList *node;
662 /* If cutting within current cache, no deletion is necessary */
663 if( clipBoard->moveFlag ) return;
665 /* Remove groups */
666 node = clipBoard->objectList;
667 while( node ) {
668 item = node->data;
669 node = g_list_next( node );
670 cacheFrom = addrindex_get_cache(
671 clipBoard->addressIndex, item->cacheID );
672 if( cacheFrom == NULL ) continue;
673 aio = addrcache_get_object( cacheFrom, item->uid );
674 if( aio ) {
675 if( ADDRITEM_TYPE(aio) == ITEMTYPE_GROUP ) {
676 ItemGroup *group;
678 group = ( ItemGroup * ) aio;
679 group = addrcache_remove_group( cacheFrom, group );
680 if( group ) {
681 addritem_free_item_group( group );
687 /* Remove persons and folders */
688 node = clipBoard->objectList;
689 while( node ) {
690 item = node->data;
691 node = g_list_next( node );
693 cacheFrom = addrindex_get_cache(
694 clipBoard->addressIndex, item->cacheID );
695 if( cacheFrom == NULL ) continue;
697 aio = addrcache_get_object( cacheFrom, item->uid );
698 if( aio ) {
699 if( ADDRITEM_TYPE(aio) == ITEMTYPE_PERSON ) {
700 ItemPerson *person;
702 person = ( ItemPerson * ) aio;
703 person = addrcache_remove_person( cacheFrom, person );
704 if( person ) {
705 addritem_free_item_person( person );
708 else if( ADDRITEM_TYPE(aio) == ITEMTYPE_FOLDER ) {
709 ItemFolder *itemFolder;
711 itemFolder = ( ItemFolder * ) aio;
712 itemFolder = addrcache_remove_folder_delete(
713 cacheFrom, itemFolder );
714 addritem_free_item_folder( itemFolder );
721 * Paste (move) clipboard into address book.
722 * Enter: clipBoard Clipboard.
723 * book Target address book.
724 * folder Target folder where data is pasted, or null for root folder.
725 * Return: List of group or folder items added.
727 GList *addrclip_paste_cut(
728 AddressClipboard *clipBoard, AddressBookFile *book,
729 ItemFolder *folder )
731 AddressCache *cache, *cacheFrom;
732 GList *itemList;
733 GList *folderGroup;
735 cm_return_val_if_fail( clipBoard != NULL, NULL );
737 cache = book->addressCache;
738 if( folder == NULL ) folder = cache->rootFolder;
740 folderGroup = NULL;
741 clipBoard->moveFlag = FALSE;
742 cacheFrom = addrclip_list_get_cache( clipBoard );
743 if( cacheFrom && cacheFrom == cache ) {
744 /* Move items between folders in same book */
745 itemList = clipBoard->objectList;
746 folderGroup = addrclip_cache_move_items(
747 cache, folder, itemList, clipBoard );
748 clipBoard->moveFlag = TRUE;
750 else {
751 /* Move items across address books */
752 itemList = clipBoard->objectList;
753 folderGroup = addrclip_cache_add_folder(
754 cache, folder, itemList, clipBoard );
757 return folderGroup;
760 * End of Source.