talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

addrmerge.c (11332B)


      1 /* Claws Mail -- a GTK based, lightweight, and fast e-mail client
      2  * Copyright (C) 2014-2023 the Claws Mail team and Charles Lehner
      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 #include <gdk/gdk.h>
     19 #include <gdk/gdkkeysyms.h>
     20 #include <glib/gi18n.h>
     21 #include <string.h>
     22 
     23 #include "defs.h"
     24 
     25 #include "addrbook.h"
     26 #include "addressbook.h"
     27 #include "addressitem.h"
     28 #include "addrmerge.h"
     29 #include "alertpanel.h"
     30 #include "gtkutils.h"
     31 #include "file-utils.h"
     32 #include "utils.h"
     33 #include "prefs_common.h"
     34 
     35 enum
     36 {
     37 	COL_DISPLAYNAME,
     38 	COL_FIRSTNAME,
     39 	COL_LASTNAME,
     40 	COL_NICKNAME,
     41 	N_NAME_COLS
     42 };
     43 
     44 enum
     45 {
     46 	SET_ICON,
     47 	SET_PERSON,
     48 	N_SET_COLUMNS
     49 };
     50 
     51 static void addrmerge_done(struct AddrMergePage *page)
     52 {
     53 	g_list_free(page->emails);
     54 	g_list_free(page->persons);
     55 	gtk_widget_destroy(GTK_WIDGET(page->dialog));
     56 	g_free(page);
     57 }
     58 
     59 static void addrmerge_do_merge(struct AddrMergePage *page)
     60 {
     61 	GList *node;
     62 	ItemEMail *email;
     63 	ItemPerson *person;
     64 	ItemPerson *target = page->target;
     65 	ItemPerson *nameTarget = page->nameTarget;
     66 
     67 	gtk_cmclist_freeze(GTK_CMCLIST(page->clist));
     68 
     69 	/* Update target name */
     70 	if (nameTarget && nameTarget != target) {
     71 		target->status = UPDATE_ENTRY;
     72 		addritem_person_set_first_name( target, nameTarget->firstName );
     73 		addritem_person_set_last_name( target, nameTarget->lastName );
     74 		addritem_person_set_nick_name( target, nameTarget->nickName );
     75 		addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget ));
     76 	}
     77 
     78 	/* Merge emails into target */
     79 	for (node = page->emails; node; node = node->next) {
     80 		email = node->data;
     81 		person = ( ItemPerson * ) ADDRITEM_PARENT(email);
     82 		/* Remove the email from the person */
     83 		email = addrbook_person_remove_email( page->abf, person, email );
     84 		if( email ) {
     85 			addrcache_remove_email( page->abf->addressCache, email );
     86 			/* Add the email to the target */
     87 			addrcache_person_add_email( page->abf->addressCache, target, email );
     88 		}
     89 		person->status = UPDATE_ENTRY;
     90 		addressbook_folder_refresh_one_person( page->clist, person );
     91 	}
     92 
     93 	/* Merge persons into target */
     94 	for (node = page->persons; node; node = node->next) {
     95 		GList *nodeE, *nodeA;
     96 		person = node->data;
     97 
     98 		if (person == target) continue;
     99 		person->status = DELETE_ENTRY;
    100 
    101 		/* Move all emails to the target */
    102 		for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) {
    103 			email = nodeE->data;
    104 			addritem_person_add_email( target, email );
    105 		}
    106 		g_list_free( person->listEMail );
    107 		person->listEMail = NULL;
    108 
    109 		/* Move all attributes to the target */
    110 		for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) {
    111 			UserAttribute *attrib = nodeA->data;
    112 			addritem_person_add_attribute( target, attrib );
    113 		}
    114 		g_list_free( person->listAttrib );
    115 		person->listAttrib = NULL;
    116 
    117 		/* Remove the person */
    118 		addrselect_list_remove( page->addressSelect, (AddrItemObject *)person );
    119 		addressbook_folder_remove_one_person( page->clist, person );
    120 		if (page->pobj->type == ADDR_ITEM_FOLDER)
    121 			addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person);
    122 		person = addrbook_remove_person( page->abf, person );
    123 
    124 		if (person) {
    125 			char filename[PATH_MAX];
    126 			if (addritem_person_get_picture(filename, person))
    127 				remove(filename);
    128 		}
    129 	}
    130 
    131 	addressbook_folder_refresh_one_person( page->clist, target );
    132 
    133 	addrbook_set_dirty( page->abf, TRUE );
    134 	addressbook_export_to_file();
    135 
    136 	gtk_cmclist_thaw(GTK_CMCLIST(page->clist));
    137 
    138 	addrmerge_done(page);
    139 }
    140 
    141 static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) {
    142 	struct AddrMergePage* page = data;
    143 
    144 	if (action != GTK_RESPONSE_ACCEPT)
    145 		return addrmerge_done(page);
    146 
    147 	addrmerge_do_merge(page);
    148 }
    149 
    150 static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page )
    151 {
    152 	gboolean canMerge = (page->target && page->nameTarget);
    153 	gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog),
    154 			GTK_RESPONSE_ACCEPT, canMerge );
    155 }
    156 
    157 static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page )
    158 {
    159 	ItemPerson *person = gtk_cmclist_get_row_data( clist, row );
    160 	page->nameTarget = person;
    161 	addrmerge_update_dialog_sensitive(page);
    162 }
    163 
    164 static void addrmerge_picture_selected(GtkTreeView *treeview,
    165 		struct AddrMergePage *page)
    166 {
    167 	GtkTreeModel *model;
    168 	GtkTreeIter iter;
    169 	GList *list;
    170 	ItemPerson *pictureTarget;
    171 
    172 	/* Get selected picture target */
    173 	model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView));
    174 	list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView));
    175 	page->target = NULL;
    176 	if (list != NULL) {
    177 		if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) {
    178 			gtk_tree_model_get(model, &iter,
    179 					SET_PERSON, &pictureTarget,
    180 					-1);
    181 			page->target = pictureTarget;
    182 		}
    183 
    184 		gtk_tree_path_free(list->data);
    185 		g_list_free(list);
    186 	}
    187 	addrmerge_update_dialog_sensitive(page);
    188 }
    189 
    190 static void addrmerge_prompt( struct AddrMergePage *page )
    191 {
    192 	GtkWidget *dialog;
    193 	GtkWidget *frame;
    194 	GtkWidget *mvbox, *vbox, *hbox;
    195 	GtkWidget *label;
    196 	GtkWidget *iconView = NULL;
    197 	GtkWidget *namesList = NULL;
    198 	MainWindow *mainwin = mainwindow_get_mainwindow();
    199 	GtkListStore *store = NULL;
    200 	GList *node;
    201 	ItemPerson *person;
    202 	gchar *msg, *label_msg;
    203 
    204 	dialog = page->dialog = gtk_dialog_new_with_buttons (
    205 			_("Merge addresses"),
    206 			GTK_WINDOW(mainwin->window),
    207 			GTK_DIALOG_DESTROY_WITH_PARENT,
    208 			_("_Cancel"),
    209 			GTK_RESPONSE_CANCEL,
    210 			_("_Merge"),
    211 			GTK_RESPONSE_ACCEPT,
    212 			NULL);
    213 
    214 	g_signal_connect ( dialog, "response",
    215 			G_CALLBACK(addrmerge_dialog_cb), page);
    216 
    217 	mvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    218 	gtk_container_add(GTK_CONTAINER(
    219 			gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox);
    220 	gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8);
    221 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
    222 	gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
    223 	gtk_box_pack_start(GTK_BOX(mvbox),
    224 			hbox, FALSE, FALSE, 0);
    225 
    226 	msg = page->pickPicture || page->pickName ?
    227 		_("Merging %u contacts." ) :
    228 		_("Really merge these %u contacts?" );
    229 	label_msg = g_strdup_printf(msg,
    230 			g_list_length(page->addressSelect->listSelect));
    231 	label = gtk_label_new( label_msg );
    232 	gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
    233 	gtk_label_set_xalign(GTK_LABEL(label),0);
    234 	gtk_label_set_yalign(GTK_LABEL(label),0.5);
    235 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    236 	g_free(label_msg);
    237 
    238 	if (page->pickName) {
    239 		GtkWidget *scrollwinNames;
    240 		gchar *name_titles[N_NAME_COLS];
    241 
    242 		name_titles[COL_DISPLAYNAME] = _("Display Name");
    243 		name_titles[COL_FIRSTNAME] = _("First Name");
    244 		name_titles[COL_LASTNAME] = _("Last Name");
    245 		name_titles[COL_NICKNAME] = _("Nickname");
    246 
    247 		store = gtk_list_store_new(N_SET_COLUMNS,
    248 					GDK_TYPE_PIXBUF,
    249 					G_TYPE_POINTER,
    250 					-1);
    251 		gtk_list_store_clear(store);
    252 
    253 		vbox = gtkut_get_options_frame(mvbox, &frame,
    254 				_("Keep which name?"));
    255 		gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
    256 
    257 		scrollwinNames = gtk_scrolled_window_new(NULL, NULL);
    258 		gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1);
    259 		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames),
    260 				GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    261 		gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames),
    262 				GTK_SHADOW_IN);
    263 		gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0);
    264 
    265 		namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles);
    266 		gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE);
    267 		gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE);
    268 		gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164);
    269 
    270 		gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList);
    271 
    272 		/* Add names from persons */
    273 		for (node = page->persons; node; node = node->next) {
    274 			int row;
    275 			person = node->data;
    276 			gchar *text[N_NAME_COLS];
    277 			text[COL_DISPLAYNAME] = ADDRITEM_NAME(person);
    278 			text[COL_FIRSTNAME] = person->firstName;
    279 			text[COL_LASTNAME] = person->lastName;
    280 			text[COL_NICKNAME] = person->nickName;
    281 			row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text );
    282 			gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person );
    283 		}
    284 
    285 		g_signal_connect(G_OBJECT(namesList), "select_row",
    286 				G_CALLBACK(addrmerge_name_selected), page);
    287 	}
    288 
    289 	page->iconView = iconView;
    290 	page->namesList = namesList;
    291 
    292 	addrmerge_update_dialog_sensitive(page);
    293 	gtk_widget_show_all(dialog);
    294 }
    295 
    296 void addrmerge_merge(
    297 		GtkCMCTree *clist,
    298 		AddressObject *pobj,
    299 		AddressDataSource *ds,
    300 		AddrSelectList *list)
    301 {
    302 	struct AddrMergePage* page;
    303 	AdapterDSource *ads = NULL;
    304 	AddressBookFile *abf;
    305 	gboolean procFlag;
    306 	GList *node;
    307 	AddrSelectItem *item;
    308 	AddrItemObject *aio;
    309 	ItemPerson *person, *target = NULL, *nameTarget = NULL;
    310 	GList *persons = NULL, *emails = NULL;
    311 	gboolean pickName = FALSE;
    312 
    313 	if( ds->interface->readOnly ) {
    314 		alertpanel_warning("This address data is read-only and cannot be deleted.");
    315 		return;
    316 	}
    317 
    318 	/* Test whether Ok to proceed */
    319 	procFlag = FALSE;
    320 	if( pobj->type == ADDR_DATASOURCE ) {
    321 		ads = ADAPTER_DSOURCE(pobj);
    322 		if( ads->subType == ADDR_BOOK ) procFlag = TRUE;
    323 	}
    324 	else if( pobj->type == ADDR_ITEM_FOLDER ) {
    325 		procFlag = TRUE;
    326 	}
    327 	else if( pobj->type == ADDR_ITEM_GROUP ) {
    328 		procFlag = TRUE;
    329 	}
    330 	if( ! procFlag ) return;
    331 	abf = ds->rawDataSource;
    332 	if( abf == NULL ) return;
    333 
    334 	/* Gather selected persons and emails */
    335 	for (node = list->listSelect; node; node = node->next) {
    336 		item = node->data;
    337 		aio = ( AddrItemObject * ) item->addressItem;
    338 		if( aio->type == ITEMTYPE_EMAIL ) {
    339 			emails = g_list_prepend(emails, aio);
    340 		} else if( aio->type == ITEMTYPE_PERSON ) {
    341 			persons = g_list_prepend(persons, aio);
    342 		}
    343 	}
    344 
    345 	if (persons && persons->data) {
    346 		target = persons->data;
    347 	} else {
    348 		/* No persons in list. Abort */
    349 		goto abort;
    350 	}
    351 
    352 	/* Pick which name to keep */
    353 	for (node = persons; node; node = node->next) {
    354 		person = node->data;
    355 		if (nameTarget == NULL) {
    356 			nameTarget = person;
    357 		} else if (nameTarget == person) {
    358 			continue;
    359 		} else if (g_strcmp0(person->firstName, nameTarget->firstName) ||
    360 				g_strcmp0(person->lastName, nameTarget->lastName) ||
    361 				g_strcmp0(person->nickName, nameTarget->nickName) ||
    362 				g_strcmp0(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) {
    363 			pickName = TRUE;
    364 			break;
    365 		}
    366 	}
    367 	if (!nameTarget) {
    368 		/* No persons in list */
    369 		goto abort;
    370 	}
    371 
    372 	/* Create object */
    373 	page = g_new0(struct AddrMergePage, 1);
    374 	page->pickPicture = FALSE;
    375 	page->pickName = pickName;
    376 	page->target = target;
    377 	page->nameTarget = nameTarget;
    378 	page->addressSelect = list;
    379 	page->persons = persons;
    380 	page->emails = emails;
    381 	page->clist = clist;
    382 	page->pobj = pobj;
    383 	page->abf = abf;
    384 	page->ds = ds;
    385 
    386 	addrmerge_prompt(page);
    387 	return;
    388 
    389 abort:
    390 	g_list_free( emails );
    391 	g_list_free( persons );
    392 }