move 'msg selection when entering a folder" button to Message List page
[claws.git] / src / addrmerge.c
1 /* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
2  * Copyright (C) 2014 Charles Lehner and the Claws Mail team
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
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
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
16  */
17
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <gdk/gdk.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <glib/gi18n.h>
27 #include <string.h>
28
29 #include "defs.h"
30
31 #ifdef USE_LDAP
32 #include "ldapserver.h"
33 #include "ldapupdate.h"
34 #endif
35 #include "addrbook.h"
36 #include "addressbook.h"
37 #include "addressitem.h"
38 #include "addrmerge.h"
39 #include "alertpanel.h"
40 #include "gtkutils.h"
41 #include "file-utils.h"
42 #include "utils.h"
43 #include "prefs_common.h"
44
45 enum
46 {
47         COL_DISPLAYNAME,
48         COL_FIRSTNAME,
49         COL_LASTNAME,
50         COL_NICKNAME,
51         N_NAME_COLS
52 };
53
54 enum
55 {
56         SET_ICON,
57         SET_PERSON,
58         N_SET_COLUMNS
59 };
60
61 static void addrmerge_done(struct AddrMergePage *page)
62 {
63         g_list_free(page->emails);
64         g_list_free(page->persons);
65         gtk_widget_destroy(GTK_WIDGET(page->dialog));
66         g_free(page);
67 }
68
69 static void addrmerge_do_merge(struct AddrMergePage *page)
70 {
71         GList *node;
72         ItemEMail *email;
73         ItemPerson *person;
74         ItemPerson *target = page->target;
75         ItemPerson *nameTarget = page->nameTarget;
76
77         gtk_cmclist_freeze(GTK_CMCLIST(page->clist));
78
79         /* Update target name */
80         if (nameTarget && nameTarget != target) {
81                 target->status = UPDATE_ENTRY;
82                 addritem_person_set_first_name( target, nameTarget->firstName );
83                 addritem_person_set_last_name( target, nameTarget->lastName );
84                 addritem_person_set_nick_name( target, nameTarget->nickName );
85                 addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget ));
86         }
87
88         /* Merge emails into target */
89         for (node = page->emails; node; node = node->next) {
90                 email = node->data;
91                 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
92                 /* Remove the email from the person */
93                 email = addrbook_person_remove_email( page->abf, person, email );
94                 if( email ) {
95                         addrcache_remove_email( page->abf->addressCache, email );
96                         /* Add the email to the target */
97                         addrcache_person_add_email( page->abf->addressCache, target, email );
98                 }
99                 person->status = UPDATE_ENTRY;
100                 addressbook_folder_refresh_one_person( page->clist, person );
101         }
102
103         /* Merge persons into target */
104         for (node = page->persons; node; node = node->next) {
105                 GList *nodeE, *nodeA;
106                 person = node->data;
107
108                 if (person == target) continue;
109                 person->status = DELETE_ENTRY;
110
111                 /* Move all emails to the target */
112                 for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) {
113                         email = nodeE->data;
114                         addritem_person_add_email( target, email );
115                 }
116                 g_list_free( person->listEMail );
117                 person->listEMail = NULL;
118
119                 /* Move all attributes to the target */
120                 for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) {
121                         UserAttribute *attrib = nodeA->data;
122                         addritem_person_add_attribute( target, attrib );
123                 }
124                 g_list_free( person->listAttrib );
125                 person->listAttrib = NULL;
126
127                 /* Remove the person */
128                 addrselect_list_remove( page->addressSelect, (AddrItemObject *)person );
129                 addressbook_folder_remove_one_person( page->clist, person );
130                 if (page->pobj->type == ADDR_ITEM_FOLDER)
131                         addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person);
132                 person = addrbook_remove_person( page->abf, person );
133
134                 if( person ) {
135                         gchar *filename = addritem_person_get_picture(person);
136                         if ((strcmp2(person->picture, target->picture) &&
137                                         filename && is_file_exist(filename)))
138                                 claws_unlink(filename);
139                         if (filename)
140                                 g_free(filename);
141                         addritem_free_item_person( person );
142                 }
143         }
144
145         addressbook_folder_refresh_one_person( page->clist, target );
146
147         addrbook_set_dirty( page->abf, TRUE );
148         addressbook_export_to_file();
149
150 #ifdef USE_LDAP
151         if (page->ds && page->ds->type == ADDR_IF_LDAP) {
152                 LdapServer *server = page->ds->rawDataSource;
153                 ldapsvr_set_modified(server, TRUE);
154                 ldapsvr_update_book(server, NULL);
155         }
156 #endif
157         gtk_cmclist_thaw(GTK_CMCLIST(page->clist));
158
159         addrmerge_done(page);
160 }
161
162 static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) {
163         struct AddrMergePage* page = data;
164
165         if (action != GTK_RESPONSE_ACCEPT)
166                 return addrmerge_done(page);
167
168         addrmerge_do_merge(page);
169 }
170
171 static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page )
172 {
173         gboolean canMerge = (page->target && page->nameTarget);
174         gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog),
175                         GTK_RESPONSE_ACCEPT, canMerge );
176 }
177
178 static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page )
179 {
180         ItemPerson *person = gtk_cmclist_get_row_data( clist, row );
181         page->nameTarget = person;
182         addrmerge_update_dialog_sensitive(page);
183 }
184
185 static void addrmerge_picture_selected(GtkTreeView *treeview,
186                 struct AddrMergePage *page)
187 {
188         GtkTreeModel *model;
189         GtkTreeIter iter;
190         GList *list;
191         ItemPerson *pictureTarget;
192
193         /* Get selected picture target */ 
194         model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView));
195         list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView));
196         page->target = NULL;
197         if (list != NULL) {
198                 if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) {
199                         gtk_tree_model_get(model, &iter,
200                                         SET_PERSON, &pictureTarget,
201                                         -1);
202                         page->target = pictureTarget;
203                 }
204
205                 gtk_tree_path_free(list->data);
206                 g_list_free(list);
207         }
208         addrmerge_update_dialog_sensitive(page);
209 }
210
211 static void addrmerge_prompt( struct AddrMergePage *page )
212 {
213         GtkWidget *dialog;
214         GtkWidget *frame;
215         GtkWidget *mvbox, *vbox, *hbox;
216         GtkWidget *label;
217         GtkWidget *iconView = NULL;
218         GtkWidget *namesList = NULL;
219         MainWindow *mainwin = mainwindow_get_mainwindow();
220         GtkListStore *store = NULL;
221         GtkTreeIter iter;
222         GList *node;
223         ItemPerson *person;
224         GError *error = NULL;
225         gchar *msg, *label_msg;
226
227         dialog = page->dialog = gtk_dialog_new_with_buttons (
228                         _("Merge addresses"),
229                         GTK_WINDOW(mainwin->window),
230                         GTK_DIALOG_DESTROY_WITH_PARENT,
231                         GTK_STOCK_CANCEL,
232                         GTK_RESPONSE_CANCEL,
233                         "_Merge",
234                         GTK_RESPONSE_ACCEPT,
235                         NULL);
236
237         g_signal_connect ( dialog, "response",
238                         G_CALLBACK(addrmerge_dialog_cb), page);
239
240         mvbox = gtk_vbox_new(FALSE, 4);
241         gtk_container_add(GTK_CONTAINER(
242                         gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox);
243         gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8);
244         hbox = gtk_hbox_new(FALSE, 4);
245         gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
246         gtk_box_pack_start(GTK_BOX(mvbox),
247                         hbox, FALSE, FALSE, 0);
248
249         msg = page->pickPicture || page->pickName ?
250                 _("Merging %u contacts." ) :
251                 _("Really merge these %u contacts?" );
252         label_msg = g_strdup_printf(msg,
253                         g_list_length(page->addressSelect->listSelect));
254         label = gtk_label_new( label_msg );
255         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
256         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
257         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
258         g_free(label_msg);
259
260         if (page->pickPicture) {
261                 GtkWidget *scrollwinPictures;
262
263                 store = gtk_list_store_new(N_SET_COLUMNS,
264                                         GDK_TYPE_PIXBUF,
265                                         G_TYPE_POINTER,
266                                         -1);
267                 gtk_list_store_clear(store);
268
269                 vbox = gtkut_get_options_frame(mvbox, &frame,
270                                 _("Keep which picture?"));
271                 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
272
273                 scrollwinPictures = gtk_scrolled_window_new(NULL, NULL);
274                 gtk_container_set_border_width(GTK_CONTAINER(scrollwinPictures), 1);
275                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinPictures),
276                                 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
277                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinPictures),
278                                 GTK_SHADOW_IN);
279                 gtk_box_pack_start (GTK_BOX (vbox), scrollwinPictures, FALSE, FALSE, 0);
280                 gtk_widget_set_size_request(scrollwinPictures, 464, 192);
281
282                 iconView = gtk_icon_view_new_with_model(GTK_TREE_MODEL(store));
283                 gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(iconView), GTK_SELECTION_SINGLE);
284                 gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(iconView), SET_ICON);
285                 gtk_container_add(GTK_CONTAINER(scrollwinPictures), GTK_WIDGET(iconView));
286                 g_signal_connect(G_OBJECT(iconView), "selection-changed",
287                                 G_CALLBACK(addrmerge_picture_selected), page);
288
289                 /* Add pictures from persons */
290                 for (node = page->persons; node; node = node->next) {
291                         gchar *filename;
292                         person = node->data;
293                         filename = addritem_person_get_picture(person);
294                         if (filename && is_file_exist(filename)) {
295                                 GdkPixbuf *pixbuf;
296                                 GtkWidget *image;
297
298                                 pixbuf = gdk_pixbuf_new_from_file(filename, &error);
299                                 if (error) {
300                                         debug_print("Failed to read image: \n%s",
301                                                         error->message);
302                                         g_error_free(error);
303                                         continue;
304                                 }
305
306                                 image = gtk_image_new();
307                                 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
308
309                                 gtk_list_store_append(store, &iter);
310                                 gtk_list_store_set(store, &iter,
311                                                 SET_ICON, pixbuf,
312                                                 SET_PERSON, person,
313                                                 -1);
314                         }
315                         if (filename)
316                                 g_free(filename);
317                 }
318         }
319
320         if (page->pickName) {
321                 GtkWidget *scrollwinNames;
322                 gchar *name_titles[N_NAME_COLS];
323
324                 name_titles[COL_DISPLAYNAME] = _("Display Name");
325                 name_titles[COL_FIRSTNAME] = _("First Name");
326                 name_titles[COL_LASTNAME] = _("Last Name");
327                 name_titles[COL_NICKNAME] = _("Nickname");
328
329                 store = gtk_list_store_new(N_SET_COLUMNS,
330                                         GDK_TYPE_PIXBUF,
331                                         G_TYPE_POINTER,
332                                         -1);
333                 gtk_list_store_clear(store);
334
335                 vbox = gtkut_get_options_frame(mvbox, &frame,
336                                 _("Keep which name?"));
337                 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
338
339                 scrollwinNames = gtk_scrolled_window_new(NULL, NULL);
340                 gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1);
341                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames),
342                                 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
343                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames),
344                                 GTK_SHADOW_IN);
345                 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0);
346
347                 namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles);
348                 gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE);
349                 gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE);
350                 gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164);
351
352                 gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList);
353
354                 /* Add names from persons */
355                 for (node = page->persons; node; node = node->next) {
356                         int row;
357                         person = node->data;
358                         gchar *text[N_NAME_COLS];
359                         text[COL_DISPLAYNAME] = ADDRITEM_NAME(person);
360                         text[COL_FIRSTNAME] = person->firstName;
361                         text[COL_LASTNAME] = person->lastName;
362                         text[COL_NICKNAME] = person->nickName;
363                         row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text );
364                         gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person );
365                 }
366
367                 g_signal_connect(G_OBJECT(namesList), "select_row",
368                                 G_CALLBACK(addrmerge_name_selected), page);
369         }
370
371         page->iconView = iconView;
372         page->namesList = namesList;
373
374         addrmerge_update_dialog_sensitive(page);
375         gtk_widget_show_all(dialog);
376 }
377
378 void addrmerge_merge(
379                 GtkCMCTree *clist,
380                 AddressObject *pobj,
381                 AddressDataSource *ds,
382                 AddrSelectList *list)
383 {
384         struct AddrMergePage* page;
385         AdapterDSource *ads = NULL;
386         AddressBookFile *abf;
387         gboolean procFlag;
388         GList *node;
389         AddrSelectItem *item;
390         AddrItemObject *aio;
391         ItemPerson *person, *target = NULL, *nameTarget = NULL;
392         GList *persons = NULL, *emails = NULL;
393         gboolean pickPicture = FALSE, pickName = FALSE;
394
395         /* Test for read only */
396         if( ds->interface->readOnly ) {
397                 alertpanel( _("Merge addresses"),
398                         _("This address data is readonly and cannot be deleted."),
399                         GTK_STOCK_CLOSE, NULL, NULL, ALERTFOCUS_FIRST );
400                 return;
401         }
402
403         /* Test whether Ok to proceed */
404         procFlag = FALSE;
405         if( pobj->type == ADDR_DATASOURCE ) {
406                 ads = ADAPTER_DSOURCE(pobj);
407                 if( ads->subType == ADDR_BOOK ) procFlag = TRUE;
408         }
409         else if( pobj->type == ADDR_ITEM_FOLDER ) {
410                 procFlag = TRUE;
411         }
412         else if( pobj->type == ADDR_ITEM_GROUP ) {
413                 procFlag = TRUE;
414         }
415         if( ! procFlag ) return;
416         abf = ds->rawDataSource;
417         if( abf == NULL ) return;
418
419         /* Gather selected persons and emails */
420         for (node = list->listSelect; node; node = node->next) {
421                 item = node->data;
422                 aio = ( AddrItemObject * ) item->addressItem;
423                 if( aio->type == ITEMTYPE_EMAIL ) {
424                         emails = g_list_prepend(emails, aio);
425                 } else if( aio->type == ITEMTYPE_PERSON ) {
426                         persons = g_list_prepend(persons, aio);
427                 }
428         }
429
430         /* Check if more than one person has a picture */
431         for (node = persons; node; node = node->next) {
432                 gchar *filename;
433                 person = node->data;
434                 filename = addritem_person_get_picture(person);
435                 if (filename && is_file_exist(filename)) {
436                         if (target == NULL) {
437                                 target = person;
438                         } else {
439                                 pickPicture = TRUE;
440                                 target = NULL;
441                                 break;
442                         }
443                 }
444                 if (filename)
445                         g_free(filename);
446         }
447         if (pickPicture || target) {
448                 /* At least one person had a picture */
449         } else if (persons && persons->data) {
450                 /* No person had a picture. Use the first person as target */
451                 target = persons->data;
452         } else {
453                 /* No persons in list. Abort */
454                 goto abort;
455         }
456
457         /* Pick which name to keep */
458         for (node = persons; node; node = node->next) {
459                 person = node->data;
460                 if (nameTarget == NULL) {
461                         nameTarget = person;
462                 } else if (nameTarget == person) {
463                         continue;
464                 } else if (strcmp2(person->firstName, nameTarget->firstName) ||
465                                 strcmp2(person->lastName, nameTarget->lastName) ||
466                                 strcmp2(person->nickName, nameTarget->nickName) ||
467                                 strcmp2(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) {
468                         pickName = TRUE;
469                         break;
470                 }
471         }
472         if (!nameTarget) {
473                 /* No persons in list */
474                 goto abort;
475         }
476
477         /* Create object */
478         page = g_new0(struct AddrMergePage, 1);
479         page->pickPicture = pickPicture;
480         page->pickName = pickName;
481         page->target = target;
482         page->nameTarget = nameTarget;
483         page->addressSelect = list;
484         page->persons = persons;
485         page->emails = emails;
486         page->clist = clist;
487         page->pobj = pobj;
488         page->abf = abf;
489         page->ds = ds;
490
491         addrmerge_prompt(page);
492         return;
493
494 abort:
495         g_list_free( emails );
496         g_list_free( persons );
497 }