2006-07-31 [wwp] 2.4.0cvs3
[claws.git] / src / addressbook.c
index 4c590e13f2169a9d76c03aed5ea647430c4fcc43..60074e11a6a48cabc8390c9a16f88af6dc070b9c 100644 (file)
@@ -69,6 +69,7 @@
 #include "addrbook.h"
 #include "addrindex.h"
 #include "addressadd.h"
+#include "addressbook_foldersel.h"
 #include "vcard.h"
 #include "editvcard.h"
 #include "editgroup.h"
@@ -77,6 +78,7 @@
 #include "importldif.h"
 #include "importmutt.h"
 #include "importpine.h"
+#include "manual.h"
 
 #ifdef USE_JPILOT
 #include "jpilot.h"
@@ -114,6 +116,19 @@ typedef enum
        N_LIST_COLS     = 3
 } AddressListColumns;
 
+typedef struct {
+       AddressBookFile *book;
+       ItemFolder      *folder;
+} FolderInfo;
+
+typedef struct {
+       gchar **folder_path;
+       gboolean matched;
+       gint index;
+       AddressDataSource *book;
+       ItemFolder *folder;
+} FolderPathMatch;
+
 static gchar *list_titles[] = { N_("Name"),
                                 N_("Email Address"),
                                 N_("Remarks") };
@@ -308,9 +323,6 @@ static gboolean addressbook_entry_key_pressed       (GtkWidget      *widget,
 static gint addressbook_treenode_compare_func  (GtkCList       *clist,
                                                 gconstpointer   ptr1,
                                                 gconstpointer   ptr2);
-static gint addressbook_list_compare_func      (GtkCList       *clist,
-                                                gconstpointer   ptr1,
-                                                gconstpointer   ptr2);
 static void addressbook_folder_load_one_person (GtkCTree *clist, 
                                                 ItemPerson *person,  
                                                 AddressTypeControlItem *atci, 
@@ -685,13 +697,46 @@ static void addressbook_size_allocate_cb(GtkWidget *widget,
        prefs_common.addressbookwin_height = allocation->height;
 }
 
+static gint sort_column_number = 0;
+static GtkSortType sort_column_type = GTK_SORT_ASCENDING;
+
+static gint list_case_sort(
+       GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2 )
+{
+       GtkCListRow *row1 = (GtkCListRow *) ptr1;
+       GtkCListRow *row2 = (GtkCListRow *) ptr2;
+       gchar *name1 = NULL, *name2 = NULL;
+       AddrItemObject *aio1 = ((GtkCListRow *)ptr1)->data;
+       AddrItemObject *aio2 = ((GtkCListRow *)ptr2)->data;
+
+       if( aio1->type == aio2->type ) {
+               if( row1 ) 
+                       name1 = GTK_CELL_TEXT (row1->cell[sort_column_number])->text;
+               if( row2 ) 
+                       name2 = GTK_CELL_TEXT (row2->cell[sort_column_number])->text;
+               if( ! name1 ) return ( name2 != NULL );
+               if( ! name2 ) return -1;
+               return strcasecmp( name1, name2 );
+       } else {
+               /* Order groups before person */
+               if( aio1->type == ITEMTYPE_GROUP ) {
+                       return (sort_column_type==GTK_SORT_ASCENDING) ? -1:+1;
+               } else if( aio2->type == ITEMTYPE_GROUP ) {
+                       return (sort_column_type==GTK_SORT_ASCENDING) ? +1:-1;
+               }
+               return 0;
+       }
+}
+
 static void addressbook_sort_list(GtkCList *clist, const gint col,
                const GtkSortType sort_type)
 {
        gint pos;
        GtkWidget *hbox, *label, *arrow;
 
-       gtk_clist_set_compare_func(clist, NULL);
+       sort_column_number = col;
+       sort_column_type = sort_type;
+       gtk_clist_set_compare_func(clist, list_case_sort);
        gtk_clist_set_sort_type(clist, sort_type);
        gtk_clist_set_sort_column(clist, col);  
 
@@ -700,7 +745,7 @@ static void addressbook_sort_list(GtkCList *clist, const gint col,
        
        for(pos = 0 ; pos < N_LIST_COLS ; pos++) {
                hbox = gtk_hbox_new(FALSE, 4);
-               label = gtk_label_new(list_titles[pos]);
+               label = gtk_label_new(gettext(list_titles[pos]));
                gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
                
                if(pos == col) {
@@ -776,6 +821,7 @@ static void addressbook_create(void)
        GtkWidget *statusbar;
        GtkWidget *hbbox;
        GtkWidget *hsbox;
+       GtkWidget *help_btn;
        GtkWidget *del_btn;
        GtkWidget *edit_btn;
        GtkWidget *reg_btn;
@@ -893,9 +939,8 @@ static void addressbook_create(void)
                                   COL_NAME_WIDTH);
        gtk_clist_set_column_width(GTK_CLIST(clist), COL_ADDRESS,
                                   COL_ADDRESS_WIDTH);
-       gtk_clist_set_compare_func(GTK_CLIST(clist),
-                                  addressbook_list_compare_func);
 
+       addressbook_sort_list(GTK_CLIST(clist), COL_NAME, GTK_SORT_ASCENDING);
        g_signal_connect(G_OBJECT(GTK_CLIST(clist)->column[COL_NAME].button),
                "clicked", G_CALLBACK(addressbook_name_clicked), clist);
        g_signal_connect(G_OBJECT(GTK_CLIST(clist)->column[COL_ADDRESS].button),
@@ -957,6 +1002,8 @@ static void addressbook_create(void)
        gtk_container_set_border_width(GTK_CONTAINER(hbbox), 4);
        gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
 
+       gtkut_stock_button_add_help(hbbox, &help_btn);
+
 #if GTK_CHECK_VERSION(2, 6, 0)
        edit_btn = gtk_button_new_from_stock(GTK_STOCK_EDIT);
 #else
@@ -976,6 +1023,10 @@ static void addressbook_create(void)
        GTK_WIDGET_SET_FLAGS(lup_btn, GTK_CAN_DEFAULT);
        gtk_box_pack_start(GTK_BOX(hbox), lup_btn, TRUE, TRUE, 0);
 
+       g_signal_connect(G_OBJECT(help_btn), "clicked",
+                        G_CALLBACK(manual_open_with_anchor_cb),
+                        MANUAL_ANCHOR_ADDRBOOK);
+
        g_signal_connect(G_OBJECT(edit_btn), "clicked",
                         G_CALLBACK(addressbook_edit_clicked), NULL);
        g_signal_connect(G_OBJECT(del_btn), "clicked",
@@ -1074,6 +1125,7 @@ static void addressbook_create(void)
        addrbook.status_cid = gtk_statusbar_get_context_id(
                        GTK_STATUSBAR(statusbar), "Addressbook Window" );
 
+       addrbook.help_btn = help_btn;
        addrbook.edit_btn = edit_btn;
        addrbook.del_btn = del_btn;
        addrbook.reg_btn = reg_btn;
@@ -2907,7 +2959,7 @@ static void addressbook_folder_load_one_person(
 
        if( person == NULL ) return;
 
-       text[COL_NAME] = NULL;
+       text[COL_NAME] = "";
        node = person->listEMail;
        while( node ) {
                ItemEMail *email = node->data;
@@ -2954,8 +3006,8 @@ static void addressbook_folder_load_one_person(
        if( ! haveAddr ) {
                /* Have name without EMail */
                text[COL_NAME] = ADDRITEM_NAME(person);
-               text[COL_ADDRESS] = NULL;
-               text[COL_REMARKS] = NULL;
+               text[COL_ADDRESS] = "";
+               text[COL_REMARKS] = "";
                nodePerson = gtk_sctree_insert_node(
                                clist, NULL, NULL,
                                text, FOLDER_SPACING,
@@ -3042,8 +3094,8 @@ static void addressbook_folder_load_group( GtkCTree *clist, ItemFolder *itemFold
                ItemGroup *group = items->data;
                if( group == NULL ) continue;
                text[COL_NAME] = ADDRITEM_NAME(group);
-               text[COL_ADDRESS] = NULL;
-               text[COL_REMARKS] = NULL;
+               text[COL_ADDRESS] = "";
+               text[COL_REMARKS] = "";
                nodeGroup = gtk_sctree_insert_node(clist, NULL, NULL,
                                      text, FOLDER_SPACING,
                                      atci->iconXpm, atci->maskXpm,
@@ -3650,38 +3702,6 @@ static gint addressbook_treenode_compare_func(
        return g_utf8_collate( name1, name2 );
 }
 
-/*
-* Comparison using object names and types.
-*/
-static gint addressbook_list_compare_func(
-       GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2 )
-{
-       AddrItemObject *aio1 = ((GtkCListRow *)ptr1)->data;
-       AddrItemObject *aio2 = ((GtkCListRow *)ptr2)->data;
-       gchar *name1 = NULL, *name2 = NULL;
-
-       /* Order by cell contents */
-       name1 = ADDRITEM_NAME( aio1 );
-       name2 = ADDRITEM_NAME( aio2 );
-
-       if( aio1->type == aio2->type ) {
-               /* Order by name */
-               if( ! name1 ) return ( name2 != NULL );
-               if( ! name2 ) return -1;
-               return g_utf8_collate( name1, name2 );
-       }
-       else {
-               /* Order groups before person */
-               if( aio1->type == ITEMTYPE_GROUP ) {
-                       return -1;
-               }
-               else if( aio2->type == ITEMTYPE_GROUP ) {
-                       return 1;
-               }
-               return 0;
-       }
-}
-
 static void addressbook_new_book_cb( gpointer data, guint action, GtkWidget *widget ) {
        AdapterDSource *ads;
        AdapterInterface *adapter;
@@ -4244,7 +4264,7 @@ void addrbookctl_build_map( GtkWidget *window ) {
        atci->showInTree = TRUE;
        atci->treeExpand = TRUE;
        atci->treeLeaf = FALSE;
-       atci->displayName = _( "LDAP Server" );
+       atci->displayName = _( "LDAP servers" );
        atci->iconXpm = ldapxpm;
        atci->maskXpm = ldapxpmmask;
        atci->iconXpmOpen = ldapxpm;
@@ -4447,6 +4467,198 @@ gboolean addressbook_add_contact(
        return TRUE;
 }
 
+/* ***********************************************************************
+ * Book/folder selection.
+ * ***********************************************************************
+ */
+
+/*
+ * This function is used by the matcher dialog to select a book/folder.
+ */
+gboolean addressbook_folder_selection( gchar **folderpath )
+{
+       AddressBookFile *book = NULL;
+       ItemFolder *folder = NULL;
+       gchar *path;
+
+       g_return_val_if_fail( folderpath != NULL, FALSE);
+
+       path = *folderpath;
+       *folderpath = NULL;
+       if ( addressbook_foldersel_selection( _addressIndex_, &book, &folder, path )
+               && book != NULL ) {
+               if ( folder != NULL) {
+                       gchar *tmp = NULL;
+                       gchar *oldtmp = NULL;
+                       AddrItemObject *obj = NULL;
+
+                       /* walk thru folder->parent to build the full folder path */
+                       /* TODO: wwp: optimize this */
+                       obj = &folder->obj;
+                       tmp = g_strdup(obj->uid);
+                       while ( obj->parent ) {
+                               obj = obj->parent;
+                               if ( obj->name != NULL ) {
+                                       oldtmp = g_strdup(tmp);
+                                       g_free(tmp);
+                                       tmp = g_strdup_printf("%s/%s", obj->uid, oldtmp);
+                                       g_free(oldtmp);
+                               }
+                       }
+                       *folderpath = g_strdup_printf("%s/%s", book->fileName, tmp);
+                       g_free(tmp);
+               } else {
+                       *folderpath = g_strdup_printf("%s", book->fileName);
+               }
+               debug_print( "addressbook_foldersel: %s\n", *folderpath);
+               return (*folderpath != NULL);
+       }
+       return FALSE;
+}
+
+/* ***********************************************************************
+ * Book/folder checking.
+ * ***********************************************************************
+ */
+
+static FolderInfo *addressbook_peek_subfolder_exists_create_folderinfo( AddressBookFile *abf, ItemFolder *folder )
+{
+       FolderInfo *fi = g_new0( FolderInfo, 1 );
+       fi->book   = abf;
+       fi->folder = folder;
+       return fi;
+}
+
+static void addressbook_peek_subfolder_exists_load_folder( ItemFolder *parentFolder,
+                                       FolderInfo *fiParent, FolderPathMatch *match )
+{
+       GList *list;
+       ItemFolder *folder;
+       gchar *fName;
+       FolderInfo *fi;
+       FolderPathMatch *nextmatch = NULL;
+
+       list = parentFolder->listFolder;
+       while ( list ) {
+               folder = list->data;
+               fName = g_strdup( ADDRITEM_NAME(folder) );
+
+               /* match folder name, match pointer will be set to NULL if next recursive call
+                  doesn't need to match subfolder name */
+               if ( match != NULL &&
+                        match->matched == FALSE ) {
+                       if ( strcmp(match->folder_path[match->index], folder->obj.uid) == 0 ) {
+                               /* folder name matches, prepare next subfolder match */
+                               debug_print("matched folder name '%s'\n", fName);
+                               match->index++;
+                               if ( match->folder_path[match->index] == NULL ) {
+                                       /* we've matched all elements */
+                                       match->matched = TRUE;
+                                       match->folder = folder;
+                                       debug_print("book/folder path matched!\n");
+                               } else {
+                                       /* keep on matching */
+                                       nextmatch = match;
+                               }
+                       }
+               }
+
+               g_free( fName );
+
+               fi = addressbook_peek_subfolder_exists_create_folderinfo( fiParent->book, folder );
+               addressbook_peek_subfolder_exists_load_folder( folder, fi, nextmatch );
+               list = g_list_next( list );
+       }
+}
+
+/*
+ * This function is used by to check if a matcher book/folder path corresponds to an
+   existing addressbook book/folder ("" or "Any" are considered as valid, NULL invalid).
+ */
+
+gboolean addressbook_peek_folder_exists( gchar *folderpath,
+                                                                                AddressDataSource **book,
+                                                                                ItemFolder **folder )
+{
+       AddressDataSource *ds;
+       GList *list, *nodeDS;
+       ItemFolder *rootFolder;
+       AddressBookFile *abf;
+       FolderInfo *fi;
+       FolderPathMatch folder_path_match = { NULL, FALSE, 0, NULL, NULL };
+       FolderPathMatch *nextmatch;
+
+       if ( book )
+               *book = NULL;
+       if ( folder )
+               *folder = NULL;
+
+       if ( folderpath == NULL )
+               return FALSE;
+
+       if ( strcasecmp(folderpath, _("Any")) == 0 || *folderpath == '\0' )
+               return TRUE;
+
+       /* split the folder path we've received, we'll try to match this path, subpath by
+          subpath against the book/folder structure in order */
+       folder_path_match.folder_path = g_strsplit( folderpath, "/", 256 );
+
+       list = addrindex_get_interface_list( _addressIndex_ );
+       while ( list ) {
+               AddressInterface *interface = list->data;
+               if ( interface->type == ADDR_IF_BOOK ) {
+                       nodeDS = interface->listSource;
+                       while ( nodeDS ) {
+                               ds = nodeDS->data;
+
+                               /* Read address book */
+                               if( ! addrindex_ds_get_read_flag( ds ) ) {
+                                       addrindex_ds_read_data( ds );
+                               }
+
+                               /* Add node for address book */
+                               abf = ds->rawDataSource;
+
+                               /* try to match subfolders if this book is the right book
+                                       (and if there's smth to match, and not yet matched) */
+                               nextmatch = NULL;
+                               if ( folder_path_match.folder_path != NULL &&
+                                        folder_path_match.matched == FALSE &&
+                                        strcmp(folder_path_match.folder_path[0], abf->fileName) == 0 ) {
+                                       debug_print("matched book name '%s'\n", abf->fileName);
+                                       folder_path_match.index = 1;
+                                       if ( folder_path_match.folder_path[folder_path_match.index] == NULL ) {
+                                               /* we've matched all elements */
+                                               folder_path_match.matched = TRUE;
+                                               folder_path_match.book = ds;
+                                               debug_print("book path matched!\n");
+                                       } else {
+                                               /* keep on matching */
+                                               nextmatch = &folder_path_match;
+                                       }
+                               }
+
+                               fi = addressbook_peek_subfolder_exists_create_folderinfo( abf, NULL );
+
+                               rootFolder = addrindex_ds_get_root_folder( ds );
+                               addressbook_peek_subfolder_exists_load_folder( rootFolder, fi, nextmatch );
+
+                               nodeDS = g_list_next( nodeDS );
+                       }
+               }
+               list = g_list_next( list );
+       }
+
+       g_strfreev( folder_path_match.folder_path );
+
+       if ( book )
+               *book = folder_path_match.book;
+       if ( folder )
+               *folder = folder_path_match.folder;
+       return folder_path_match.matched;
+}
+
+
 /* **********************************************************************
  * Address Import.
  * ***********************************************************************