498 lines
14 KiB
C
498 lines
14 KiB
C
/* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
|
|
* Copyright (C) 2014-2019 Charles Lehner and the Claws Mail team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#include "claws-features.h"
|
|
#endif
|
|
|
|
#include <gdk/gdk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <glib/gi18n.h>
|
|
#include <string.h>
|
|
|
|
#include "defs.h"
|
|
|
|
#ifdef USE_LDAP
|
|
#include "ldapserver.h"
|
|
#include "ldapupdate.h"
|
|
#endif
|
|
#include "addrbook.h"
|
|
#include "addressbook.h"
|
|
#include "addressitem.h"
|
|
#include "addrmerge.h"
|
|
#include "alertpanel.h"
|
|
#include "gtkutils.h"
|
|
#include "file-utils.h"
|
|
#include "utils.h"
|
|
#include "prefs_common.h"
|
|
|
|
enum
|
|
{
|
|
COL_DISPLAYNAME,
|
|
COL_FIRSTNAME,
|
|
COL_LASTNAME,
|
|
COL_NICKNAME,
|
|
N_NAME_COLS
|
|
};
|
|
|
|
enum
|
|
{
|
|
SET_ICON,
|
|
SET_PERSON,
|
|
N_SET_COLUMNS
|
|
};
|
|
|
|
static void addrmerge_done(struct AddrMergePage *page)
|
|
{
|
|
g_list_free(page->emails);
|
|
g_list_free(page->persons);
|
|
gtk_widget_destroy(GTK_WIDGET(page->dialog));
|
|
g_free(page);
|
|
}
|
|
|
|
static void addrmerge_do_merge(struct AddrMergePage *page)
|
|
{
|
|
GList *node;
|
|
ItemEMail *email;
|
|
ItemPerson *person;
|
|
ItemPerson *target = page->target;
|
|
ItemPerson *nameTarget = page->nameTarget;
|
|
|
|
gtk_cmclist_freeze(GTK_CMCLIST(page->clist));
|
|
|
|
/* Update target name */
|
|
if (nameTarget && nameTarget != target) {
|
|
target->status = UPDATE_ENTRY;
|
|
addritem_person_set_first_name( target, nameTarget->firstName );
|
|
addritem_person_set_last_name( target, nameTarget->lastName );
|
|
addritem_person_set_nick_name( target, nameTarget->nickName );
|
|
addritem_person_set_common_name( target, ADDRITEM_NAME(nameTarget ));
|
|
}
|
|
|
|
/* Merge emails into target */
|
|
for (node = page->emails; node; node = node->next) {
|
|
email = node->data;
|
|
person = ( ItemPerson * ) ADDRITEM_PARENT(email);
|
|
/* Remove the email from the person */
|
|
email = addrbook_person_remove_email( page->abf, person, email );
|
|
if( email ) {
|
|
addrcache_remove_email( page->abf->addressCache, email );
|
|
/* Add the email to the target */
|
|
addrcache_person_add_email( page->abf->addressCache, target, email );
|
|
}
|
|
person->status = UPDATE_ENTRY;
|
|
addressbook_folder_refresh_one_person( page->clist, person );
|
|
}
|
|
|
|
/* Merge persons into target */
|
|
for (node = page->persons; node; node = node->next) {
|
|
GList *nodeE, *nodeA;
|
|
person = node->data;
|
|
|
|
if (person == target) continue;
|
|
person->status = DELETE_ENTRY;
|
|
|
|
/* Move all emails to the target */
|
|
for (nodeE = person->listEMail; nodeE; nodeE = nodeE->next) {
|
|
email = nodeE->data;
|
|
addritem_person_add_email( target, email );
|
|
}
|
|
g_list_free( person->listEMail );
|
|
person->listEMail = NULL;
|
|
|
|
/* Move all attributes to the target */
|
|
for (nodeA = person->listAttrib; nodeA; nodeA = nodeA->next) {
|
|
UserAttribute *attrib = nodeA->data;
|
|
addritem_person_add_attribute( target, attrib );
|
|
}
|
|
g_list_free( person->listAttrib );
|
|
person->listAttrib = NULL;
|
|
|
|
/* Remove the person */
|
|
addrselect_list_remove( page->addressSelect, (AddrItemObject *)person );
|
|
addressbook_folder_remove_one_person( page->clist, person );
|
|
if (page->pobj->type == ADDR_ITEM_FOLDER)
|
|
addritem_folder_remove_person(ADAPTER_FOLDER(page->pobj)->itemFolder, person);
|
|
person = addrbook_remove_person( page->abf, person );
|
|
|
|
if( person ) {
|
|
gchar *filename = addritem_person_get_picture(person);
|
|
if ((g_strcmp0(person->picture, target->picture) &&
|
|
filename && is_file_exist(filename)))
|
|
claws_unlink(filename);
|
|
if (filename)
|
|
g_free(filename);
|
|
addritem_free_item_person( person );
|
|
}
|
|
}
|
|
|
|
addressbook_folder_refresh_one_person( page->clist, target );
|
|
|
|
addrbook_set_dirty( page->abf, TRUE );
|
|
addressbook_export_to_file();
|
|
|
|
#ifdef USE_LDAP
|
|
if (page->ds && page->ds->type == ADDR_IF_LDAP) {
|
|
LdapServer *server = page->ds->rawDataSource;
|
|
ldapsvr_set_modified(server, TRUE);
|
|
ldapsvr_update_book(server, NULL);
|
|
}
|
|
#endif
|
|
gtk_cmclist_thaw(GTK_CMCLIST(page->clist));
|
|
|
|
addrmerge_done(page);
|
|
}
|
|
|
|
static void addrmerge_dialog_cb(GtkWidget* widget, gint action, gpointer data) {
|
|
struct AddrMergePage* page = data;
|
|
|
|
if (action != GTK_RESPONSE_ACCEPT)
|
|
return addrmerge_done(page);
|
|
|
|
addrmerge_do_merge(page);
|
|
}
|
|
|
|
static void addrmerge_update_dialog_sensitive( struct AddrMergePage *page )
|
|
{
|
|
gboolean canMerge = (page->target && page->nameTarget);
|
|
gtk_dialog_set_response_sensitive( GTK_DIALOG(page->dialog),
|
|
GTK_RESPONSE_ACCEPT, canMerge );
|
|
}
|
|
|
|
static void addrmerge_name_selected( GtkCMCList *clist, gint row, gint column, GdkEvent *event, struct AddrMergePage *page )
|
|
{
|
|
ItemPerson *person = gtk_cmclist_get_row_data( clist, row );
|
|
page->nameTarget = person;
|
|
addrmerge_update_dialog_sensitive(page);
|
|
}
|
|
|
|
static void addrmerge_picture_selected(GtkTreeView *treeview,
|
|
struct AddrMergePage *page)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
GList *list;
|
|
ItemPerson *pictureTarget;
|
|
|
|
/* Get selected picture target */
|
|
model = gtk_icon_view_get_model(GTK_ICON_VIEW(page->iconView));
|
|
list = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(page->iconView));
|
|
page->target = NULL;
|
|
if (list != NULL) {
|
|
if (gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)list->data)) {
|
|
gtk_tree_model_get(model, &iter,
|
|
SET_PERSON, &pictureTarget,
|
|
-1);
|
|
page->target = pictureTarget;
|
|
}
|
|
|
|
gtk_tree_path_free(list->data);
|
|
g_list_free(list);
|
|
}
|
|
addrmerge_update_dialog_sensitive(page);
|
|
}
|
|
|
|
static void addrmerge_prompt( struct AddrMergePage *page )
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *frame;
|
|
GtkWidget *mvbox, *vbox, *hbox;
|
|
GtkWidget *label;
|
|
GtkWidget *iconView = NULL;
|
|
GtkWidget *namesList = NULL;
|
|
MainWindow *mainwin = mainwindow_get_mainwindow();
|
|
GtkListStore *store = NULL;
|
|
GtkTreeIter iter;
|
|
GList *node;
|
|
ItemPerson *person;
|
|
GError *error = NULL;
|
|
gchar *msg, *label_msg;
|
|
|
|
dialog = page->dialog = gtk_dialog_new_with_buttons (
|
|
_("Merge addresses"),
|
|
GTK_WINDOW(mainwin->window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
_("_Cancel"),
|
|
GTK_RESPONSE_CANCEL,
|
|
_("_Merge"),
|
|
GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
g_signal_connect ( dialog, "response",
|
|
G_CALLBACK(addrmerge_dialog_cb), page);
|
|
|
|
mvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
|
|
gtk_container_add(GTK_CONTAINER(
|
|
gtk_dialog_get_content_area(GTK_DIALOG(dialog))), mvbox);
|
|
gtk_container_set_border_width(GTK_CONTAINER(mvbox), 8);
|
|
hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
|
gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
|
|
gtk_box_pack_start(GTK_BOX(mvbox),
|
|
hbox, FALSE, FALSE, 0);
|
|
|
|
msg = page->pickPicture || page->pickName ?
|
|
_("Merging %u contacts." ) :
|
|
_("Really merge these %u contacts?" );
|
|
label_msg = g_strdup_printf(msg,
|
|
g_list_length(page->addressSelect->listSelect));
|
|
label = gtk_label_new( label_msg );
|
|
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
|
|
gtk_label_set_xalign(GTK_LABEL(label),0);
|
|
gtk_label_set_yalign(GTK_LABEL(label),0.5);
|
|
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
|
|
g_free(label_msg);
|
|
|
|
if (page->pickPicture) {
|
|
GtkWidget *scrollwinPictures;
|
|
|
|
store = gtk_list_store_new(N_SET_COLUMNS,
|
|
GDK_TYPE_PIXBUF,
|
|
G_TYPE_POINTER,
|
|
-1);
|
|
gtk_list_store_clear(store);
|
|
|
|
vbox = gtkut_get_options_frame(mvbox, &frame,
|
|
_("Keep which picture?"));
|
|
gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
|
|
|
|
scrollwinPictures = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_container_set_border_width(GTK_CONTAINER(scrollwinPictures), 1);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinPictures),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinPictures),
|
|
GTK_SHADOW_IN);
|
|
gtk_box_pack_start (GTK_BOX (vbox), scrollwinPictures, FALSE, FALSE, 0);
|
|
gtk_widget_set_size_request(scrollwinPictures, 464, 192);
|
|
|
|
iconView = gtk_icon_view_new_with_model(GTK_TREE_MODEL(store));
|
|
gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(iconView), GTK_SELECTION_SINGLE);
|
|
gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(iconView), SET_ICON);
|
|
gtk_container_add(GTK_CONTAINER(scrollwinPictures), GTK_WIDGET(iconView));
|
|
g_signal_connect(G_OBJECT(iconView), "selection-changed",
|
|
G_CALLBACK(addrmerge_picture_selected), page);
|
|
|
|
/* Add pictures from persons */
|
|
for (node = page->persons; node; node = node->next) {
|
|
gchar *filename;
|
|
person = node->data;
|
|
filename = addritem_person_get_picture(person);
|
|
if (filename && is_file_exist(filename)) {
|
|
GdkPixbuf *pixbuf;
|
|
GtkWidget *image;
|
|
|
|
pixbuf = gdk_pixbuf_new_from_file(filename, &error);
|
|
if (error) {
|
|
debug_print("Failed to read image: \n%s",
|
|
error->message);
|
|
g_error_free(error);
|
|
continue;
|
|
}
|
|
|
|
image = gtk_image_new();
|
|
gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
|
|
|
|
gtk_list_store_append(store, &iter);
|
|
gtk_list_store_set(store, &iter,
|
|
SET_ICON, pixbuf,
|
|
SET_PERSON, person,
|
|
-1);
|
|
}
|
|
if (filename)
|
|
g_free(filename);
|
|
}
|
|
}
|
|
|
|
if (page->pickName) {
|
|
GtkWidget *scrollwinNames;
|
|
gchar *name_titles[N_NAME_COLS];
|
|
|
|
name_titles[COL_DISPLAYNAME] = _("Display Name");
|
|
name_titles[COL_FIRSTNAME] = _("First Name");
|
|
name_titles[COL_LASTNAME] = _("Last Name");
|
|
name_titles[COL_NICKNAME] = _("Nickname");
|
|
|
|
store = gtk_list_store_new(N_SET_COLUMNS,
|
|
GDK_TYPE_PIXBUF,
|
|
G_TYPE_POINTER,
|
|
-1);
|
|
gtk_list_store_clear(store);
|
|
|
|
vbox = gtkut_get_options_frame(mvbox, &frame,
|
|
_("Keep which name?"));
|
|
gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
|
|
|
|
scrollwinNames = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_container_set_border_width(GTK_CONTAINER(scrollwinNames), 1);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwinNames),
|
|
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwinNames),
|
|
GTK_SHADOW_IN);
|
|
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrollwinNames), FALSE, FALSE, 0);
|
|
|
|
namesList = gtk_cmclist_new_with_titles(N_NAME_COLS, name_titles);
|
|
gtk_widget_set_can_focus(GTK_CMCLIST(namesList)->column[0].button, FALSE);
|
|
gtk_cmclist_set_selection_mode(GTK_CMCLIST(namesList), GTK_SELECTION_BROWSE);
|
|
gtk_cmclist_set_column_width(GTK_CMCLIST(namesList), COL_DISPLAYNAME, 164);
|
|
|
|
gtk_container_add(GTK_CONTAINER(scrollwinNames), namesList);
|
|
|
|
/* Add names from persons */
|
|
for (node = page->persons; node; node = node->next) {
|
|
int row;
|
|
person = node->data;
|
|
gchar *text[N_NAME_COLS];
|
|
text[COL_DISPLAYNAME] = ADDRITEM_NAME(person);
|
|
text[COL_FIRSTNAME] = person->firstName;
|
|
text[COL_LASTNAME] = person->lastName;
|
|
text[COL_NICKNAME] = person->nickName;
|
|
row = gtk_cmclist_insert( GTK_CMCLIST(namesList), -1, text );
|
|
gtk_cmclist_set_row_data( GTK_CMCLIST(namesList), row, person );
|
|
}
|
|
|
|
g_signal_connect(G_OBJECT(namesList), "select_row",
|
|
G_CALLBACK(addrmerge_name_selected), page);
|
|
}
|
|
|
|
page->iconView = iconView;
|
|
page->namesList = namesList;
|
|
|
|
addrmerge_update_dialog_sensitive(page);
|
|
gtk_widget_show_all(dialog);
|
|
}
|
|
|
|
void addrmerge_merge(
|
|
GtkCMCTree *clist,
|
|
AddressObject *pobj,
|
|
AddressDataSource *ds,
|
|
AddrSelectList *list)
|
|
{
|
|
struct AddrMergePage* page;
|
|
AdapterDSource *ads = NULL;
|
|
AddressBookFile *abf;
|
|
gboolean procFlag;
|
|
GList *node;
|
|
AddrSelectItem *item;
|
|
AddrItemObject *aio;
|
|
ItemPerson *person, *target = NULL, *nameTarget = NULL;
|
|
GList *persons = NULL, *emails = NULL;
|
|
gboolean pickPicture = FALSE, pickName = FALSE;
|
|
|
|
/* Test for read only */
|
|
if( ds->interface->readOnly ) {
|
|
alertpanel( _("Merge addresses"),
|
|
_("This address data is readonly and cannot be deleted."),
|
|
_("_Close"), NULL, NULL, ALERTFOCUS_FIRST );
|
|
return;
|
|
}
|
|
|
|
/* Test whether Ok to proceed */
|
|
procFlag = FALSE;
|
|
if( pobj->type == ADDR_DATASOURCE ) {
|
|
ads = ADAPTER_DSOURCE(pobj);
|
|
if( ads->subType == ADDR_BOOK ) procFlag = TRUE;
|
|
}
|
|
else if( pobj->type == ADDR_ITEM_FOLDER ) {
|
|
procFlag = TRUE;
|
|
}
|
|
else if( pobj->type == ADDR_ITEM_GROUP ) {
|
|
procFlag = TRUE;
|
|
}
|
|
if( ! procFlag ) return;
|
|
abf = ds->rawDataSource;
|
|
if( abf == NULL ) return;
|
|
|
|
/* Gather selected persons and emails */
|
|
for (node = list->listSelect; node; node = node->next) {
|
|
item = node->data;
|
|
aio = ( AddrItemObject * ) item->addressItem;
|
|
if( aio->type == ITEMTYPE_EMAIL ) {
|
|
emails = g_list_prepend(emails, aio);
|
|
} else if( aio->type == ITEMTYPE_PERSON ) {
|
|
persons = g_list_prepend(persons, aio);
|
|
}
|
|
}
|
|
|
|
/* Check if more than one person has a picture */
|
|
for (node = persons; node; node = node->next) {
|
|
gchar *filename;
|
|
person = node->data;
|
|
filename = addritem_person_get_picture(person);
|
|
if (filename && is_file_exist(filename)) {
|
|
if (target == NULL) {
|
|
target = person;
|
|
} else {
|
|
pickPicture = TRUE;
|
|
target = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if (filename)
|
|
g_free(filename);
|
|
}
|
|
if (pickPicture || target) {
|
|
/* At least one person had a picture */
|
|
} else if (persons && persons->data) {
|
|
/* No person had a picture. Use the first person as target */
|
|
target = persons->data;
|
|
} else {
|
|
/* No persons in list. Abort */
|
|
goto abort;
|
|
}
|
|
|
|
/* Pick which name to keep */
|
|
for (node = persons; node; node = node->next) {
|
|
person = node->data;
|
|
if (nameTarget == NULL) {
|
|
nameTarget = person;
|
|
} else if (nameTarget == person) {
|
|
continue;
|
|
} else if (g_strcmp0(person->firstName, nameTarget->firstName) ||
|
|
g_strcmp0(person->lastName, nameTarget->lastName) ||
|
|
g_strcmp0(person->nickName, nameTarget->nickName) ||
|
|
g_strcmp0(ADDRITEM_NAME(person), ADDRITEM_NAME(nameTarget))) {
|
|
pickName = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!nameTarget) {
|
|
/* No persons in list */
|
|
goto abort;
|
|
}
|
|
|
|
/* Create object */
|
|
page = g_new0(struct AddrMergePage, 1);
|
|
page->pickPicture = pickPicture;
|
|
page->pickName = pickName;
|
|
page->target = target;
|
|
page->nameTarget = nameTarget;
|
|
page->addressSelect = list;
|
|
page->persons = persons;
|
|
page->emails = emails;
|
|
page->clist = clist;
|
|
page->pobj = pobj;
|
|
page->abf = abf;
|
|
page->ds = ds;
|
|
|
|
addrmerge_prompt(page);
|
|
return;
|
|
|
|
abort:
|
|
g_list_free( emails );
|
|
g_list_free( persons );
|
|
}
|