added add-sender-to-addressbook patch
[claws.git] / src / addressbook.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999,2000 Hiroyuki Yamamoto
4  *
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 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtkwindow.h>
29 #include <gtk/gtksignal.h>
30 #include <gtk/gtkvbox.h>
31 #include <gtk/gtkscrolledwindow.h>
32 #include <gtk/gtkhpaned.h>
33 #include <gtk/gtkhbox.h>
34 #include <gtk/gtklabel.h>
35 #include <gtk/gtkentry.h>
36 #include <gtk/gtkctree.h>
37 #include <gtk/gtkclist.h>
38 #include <gtk/gtktable.h>
39 #include <gtk/gtkhbbox.h>
40 #include <gtk/gtkbutton.h>
41 #include <gtk/gtkmenu.h>
42 #include <gtk/gtkmenuitem.h>
43 #include <gtk/gtkitemfactory.h>
44 #include <string.h>
45 #include <setjmp.h>
46
47 #include "intl.h"
48 #include "main.h"
49 #include "addressbook.h"
50 #include "manage_window.h"
51 #include "prefs_common.h"
52 #include "alertpanel.h"
53 #include "inputdialog.h"
54 #include "menu.h"
55 #include "xml.h"
56 #include "prefs.h"
57 #include "procmime.h"
58 #include "utils.h"
59 #include "gtkutils.h"
60 #include "codeconv.h"
61 #include "about.h"
62 #include "addr_compl.h"
63
64 #include "pixmaps/dir-close.xpm"
65 #include "pixmaps/dir-open.xpm"
66 #include "pixmaps/group.xpm"
67
68 typedef enum
69 {
70         COL_NAME        = 0,
71         COL_ADDRESS     = 1,
72         COL_REMARKS     = 2
73 } AddressBookColumnPos;
74
75 #define N_COLS  3
76 #define COL_NAME_WIDTH          144
77 #define COL_ADDRESS_WIDTH       144
78
79 #define COL_FOLDER_WIDTH        170
80 #define ADDRESSBOOK_WIDTH       640
81 #define ADDRESSBOOK_HEIGHT      340
82
83 static GdkPixmap *folderxpm;
84 static GdkBitmap *folderxpmmask;
85 static GdkPixmap *folderopenxpm;
86 static GdkBitmap *folderopenxpmmask;
87 static GdkPixmap *groupxpm;
88 static GdkBitmap *groupxpmmask;
89
90 static AddressBook addrbook;
91
92 static struct _AddressEdit
93 {
94         GtkWidget *window;
95         GtkWidget *name_entry;
96         GtkWidget *addr_entry;
97         GtkWidget *rem_entry;
98         GtkWidget *ok_btn;
99         GtkWidget *cancel_btn;
100 } addredit;
101
102 static void addressbook_create                  (gboolean show);
103 static gint addressbook_close                   (void);
104 static void addressbook_button_set_sensitive    (void);
105
106 /* callback functions */
107 static void addressbook_del_clicked             (GtkButton      *button,
108                                                  gpointer        data);
109 static void addressbook_reg_clicked             (GtkButton      *button,
110                                                  gpointer        data);
111 static void addressbook_to_clicked              (GtkButton      *button,
112                                                  gpointer        data);
113
114 static void addressbook_tree_selected           (GtkCTree       *ctree,
115                                                  GtkCTreeNode   *node,
116                                                  gint            column,
117                                                  gpointer        data);
118 static void addressbook_list_selected           (GtkCList       *clist,
119                                                  gint            row,
120                                                  gint            column,
121                                                  GdkEvent       *event,
122                                                  gpointer        data);
123 #if 0
124 static void addressbook_entry_changed           (GtkWidget      *widget);
125 #endif
126
127 static void addressbook_list_button_pressed     (GtkWidget      *widget,
128                                                  GdkEventButton *event,
129                                                  gpointer        data);
130 static void addressbook_list_button_released    (GtkWidget      *widget,
131                                                  GdkEventButton *event,
132                                                  gpointer        data);
133 static void addressbook_tree_button_pressed     (GtkWidget      *ctree,
134                                                  GdkEventButton *event,
135                                                  gpointer        data);
136 static void addressbook_tree_button_released    (GtkWidget      *ctree,
137                                                  GdkEventButton *event,
138                                                  gpointer        data);
139 static void addressbook_popup_close             (GtkMenuShell   *menu_shell,
140                                                  gpointer        data);
141
142 static void addressbook_new_folder_cb           (gpointer        data,
143                                                  guint           action,
144                                                  GtkWidget      *widget);
145 static void addressbook_new_group_cb            (gpointer        data,
146                                                  guint           action,
147                                                  GtkWidget      *widget);
148 static void addressbook_edit_folder_cb          (gpointer        data,
149                                                  guint           action,
150                                                  GtkWidget      *widget);
151 static void addressbook_delete_folder_cb        (gpointer        data,
152                                                  guint           action,
153                                                  GtkWidget      *widget);
154
155 static void addressbook_change_node_name        (GtkCTreeNode   *node,
156                                                  const gchar    *name);
157 static void addressbook_edit_group              (GtkCTreeNode   *group_node);
158
159 static void addressbook_edit_address_create     (gboolean       *cancelled);
160 static void edit_address_ok                     (GtkWidget      *widget,
161                                                  gboolean       *cancelled);
162 static void edit_address_cancel                 (GtkWidget      *widget,
163                                                  gboolean       *cancelled);
164 static gint edit_address_delete_event           (GtkWidget      *widget,
165                                                  GdkEventAny    *event,
166                                                  gboolean *cancelled);
167 static void edit_address_key_pressed            (GtkWidget      *widget,
168                                                  GdkEventKey    *event,
169                                                  gboolean       *cancelled);
170 static AddressItem *addressbook_edit_address    (AddressItem    *item);
171
172 static void addressbook_new_address_cb          (gpointer        data,
173                                                  guint           action,
174                                                  GtkWidget      *widget);
175 static void addressbook_edit_address_cb         (gpointer        data,
176                                                  guint           action,
177                                                  GtkWidget      *widget);
178 static void addressbook_delete_address_cb       (gpointer        data,
179                                                  guint           action,
180                                                  GtkWidget      *widget);
181
182 static void close_cb                            (gpointer        data,
183                                                  guint           action,
184                                                  GtkWidget      *widget);
185
186 static AddressItem *addressbook_parse_address   (const gchar    *str);
187 static void addressbook_append_to_compose_entry (AddressItem    *item,
188                                                  ComposeEntryType type);
189
190 static void addressbook_set_clist               (AddressObject  *obj);
191
192 static void addressbook_read_file               (void);
193 static void addressbook_get_tree                (XMLFile        *file,
194                                                  GtkCTreeNode   *node,
195                                                  const gchar    *folder_tag);
196 static void addressbook_add_objs                (XMLFile        *file,
197                                                  GtkCTreeNode   *node);
198
199 static GtkCTreeNode *addressbook_add_object     (GtkCTreeNode   *node,
200                                                  AddressObject  *obj);
201 static void addressbook_delete_object           (AddressObject  *obj);
202 static AddressObject *addressbook_find_object_by_name
203                                                 (GtkCTreeNode   *node,
204                                                  const gchar    *name);
205
206 static AddressItem *addressbook_parse_item      (XMLFile        *file);
207 static void addressbook_xml_recursive_write     (GtkCTreeNode   *node,
208                                                  FILE           *fp);
209 static void addressbook_node_write_begin        (GtkCTreeNode   *node,
210                                                  FILE           *fp);
211 static void addressbook_node_write_end          (GtkCTreeNode   *node,
212                                                  FILE           *fp);
213 static void addressbook_write_items             (FILE           *fp,
214                                                  GList          *items,
215                                                  guint           level);
216 static void tab_indent_out                      (FILE           *fp,
217                                                  guint           level);
218
219 static void key_pressed                         (GtkWidget      *widget,
220                                                  GdkEventKey    *event,
221                                                  gpointer        data);
222 static gint addressbook_list_compare_func       (GtkCList       *clist,
223                                                  gconstpointer   ptr1,
224                                                  gconstpointer   ptr2);
225 static gint addressbook_obj_name_compare        (gconstpointer   a,
226                                                  gconstpointer   b);
227
228 static GtkItemFactoryEntry addressbook_entries[] =
229 {
230         {N_("/_File"),                  NULL, NULL, 0, "<Branch>"},
231         {N_("/_File/New _address"),     "<alt>N", addressbook_new_address_cb, 0, NULL},
232         {N_("/_File/New _group"),       "<alt>G", addressbook_new_group_cb,   0, NULL},
233         {N_("/_File/New _folder"),      "<alt>R", addressbook_new_folder_cb,  0, NULL},
234         {N_("/_File/---"),              NULL, NULL, 0, "<Separator>"},
235         {N_("/_File/_Edit"),            "<alt>Return", addressbook_edit_address_cb, 0, NULL},
236         {N_("/_File/_Delete"),          NULL, addressbook_delete_address_cb, 0, NULL},
237         {N_("/_File/---"),              NULL, NULL, 0, "<Separator>"},
238         {N_("/_File/_Close"),           "<alt>W", close_cb, 0, NULL},
239         {N_("/_Help"),                  NULL, NULL, 0, "<LastBranch>"},
240         {N_("/_Help/_About"),           NULL, about_show, 0, NULL}
241 };
242
243 static GtkItemFactoryEntry addressbook_tree_popup_entries[] =
244 {
245         {N_("/New _address"),   NULL, addressbook_new_address_cb, 0, NULL},
246         {N_("/New _group"),     NULL, addressbook_new_group_cb,   0, NULL},
247         {N_("/New _folder"),    NULL, addressbook_new_folder_cb,  0, NULL},
248         {N_("/---"),            NULL, NULL, 0, "<Separator>"},
249         {N_("/_Edit"),          NULL, addressbook_edit_folder_cb,   0, NULL},
250         {N_("/_Delete"),        NULL, addressbook_delete_folder_cb, 0, NULL}
251 };
252
253 static GtkItemFactoryEntry addressbook_list_popup_entries[] =
254 {
255         {N_("/New _address"),   NULL, addressbook_new_address_cb,  0, NULL},
256         {N_("/New _group"),     NULL, addressbook_new_group_cb,    0, NULL},
257         {N_("/New _folder"),    NULL, addressbook_new_folder_cb,   0, NULL},
258         {N_("/---"),            NULL, NULL, 0, "<Separator>"},
259         {N_("/_Edit"),          NULL, addressbook_edit_address_cb,   0, NULL},
260         {N_("/_Delete"),        NULL, addressbook_delete_address_cb, 0, NULL}
261 };
262
263 void addressbook_open(Compose *target)
264 {
265         if (!addrbook.window) {
266                 addressbook_create(TRUE);
267                 addressbook_read_file();
268                 addrbook.open_folder = TRUE;
269                 gtk_ctree_select(GTK_CTREE(addrbook.ctree),
270                                  GTK_CTREE_NODE(GTK_CLIST(addrbook.ctree)->row_list));
271         } else
272                 gtk_widget_hide(addrbook.window);
273         
274         gtk_widget_show_all(addrbook.window);
275         addressbook_set_target_compose(target);
276 }
277
278 void addressbook_set_target_compose(Compose *target)
279 {
280         addrbook.target_compose = target;
281
282         addressbook_button_set_sensitive();
283 }
284
285 Compose *addressbook_get_target_compose(void)
286 {
287         return addrbook.target_compose;
288 }
289
290 static void addressbook_create(gboolean show)
291 {
292         GtkWidget *window;
293         GtkWidget *vbox;
294         GtkWidget *menubar;
295         GtkWidget *vbox2;
296         GtkWidget *ctree_swin;
297         GtkWidget *ctree;
298         GtkWidget *clist_vbox;
299         GtkWidget *clist_swin;
300         GtkWidget *clist;
301         GtkWidget *paned;
302         GtkWidget *hbox;
303         GtkWidget *label;
304         GtkWidget *entry;
305         GtkWidget *hbbox;
306         GtkWidget *del_btn;
307         GtkWidget *reg_btn;
308         GtkWidget *lup_btn;
309         GtkWidget *to_btn;
310         GtkWidget *cc_btn;
311         GtkWidget *bcc_btn;
312         GtkWidget *tree_popup;
313         GtkWidget *list_popup;
314         GtkItemFactory *tree_factory;
315         GtkItemFactory *list_factory;
316         gint n_entries;
317
318         gchar *titles[N_COLS] = {_("Name"), _("E-Mail address"), _("Remarks")};
319         gchar *text;
320         gint i;
321
322         debug_print("Creating addressbook window...\n");
323
324         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
325         gtk_window_set_title(GTK_WINDOW(window), _("Address book"));
326         gtk_widget_set_usize(window, ADDRESSBOOK_WIDTH, ADDRESSBOOK_HEIGHT);
327         //gtk_container_set_border_width(GTK_CONTAINER(window), BORDER_WIDTH);
328         gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, TRUE);
329         gtk_widget_realize(window);
330
331         gtk_signal_connect(GTK_OBJECT(window), "delete_event",
332                            GTK_SIGNAL_FUNC(addressbook_close), NULL);
333         gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
334                            GTK_SIGNAL_FUNC(key_pressed), NULL);
335         gtk_signal_connect(GTK_OBJECT(window), "focus_in_event",
336                            GTK_SIGNAL_FUNC(manage_window_focus_in), NULL);
337         gtk_signal_connect(GTK_OBJECT(window), "focus_out_event",
338                            GTK_SIGNAL_FUNC(manage_window_focus_out), NULL);
339
340         vbox = gtk_vbox_new(FALSE, 0);
341         gtk_container_add(GTK_CONTAINER(window), vbox);
342
343         n_entries = sizeof(addressbook_entries) /
344                 sizeof(addressbook_entries[0]);
345         menubar = menubar_create(window, addressbook_entries, n_entries,
346                                  "<AddressBook>", NULL);
347         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
348
349         vbox2 = gtk_vbox_new(FALSE, 4);
350         gtk_container_set_border_width(GTK_CONTAINER(vbox2), BORDER_WIDTH);
351         gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
352
353         ctree_swin = gtk_scrolled_window_new(NULL, NULL);
354         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ctree_swin),
355                                        GTK_POLICY_AUTOMATIC,
356                                        GTK_POLICY_ALWAYS);
357         gtk_widget_set_usize(ctree_swin, COL_FOLDER_WIDTH + 40, -1);
358
359         ctree = gtk_ctree_new(1, 0);
360         gtk_container_add(GTK_CONTAINER(ctree_swin), ctree);
361         gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE);
362         gtk_clist_set_column_width(GTK_CLIST(ctree), 0, COL_FOLDER_WIDTH);
363         gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED);
364         gtk_ctree_set_expander_style(GTK_CTREE(ctree),
365                                      GTK_CTREE_EXPANDER_SQUARE);
366         gtk_ctree_set_indent(GTK_CTREE(ctree), CTREE_INDENT);
367         gtk_clist_set_compare_func(GTK_CLIST(ctree),
368                                    addressbook_list_compare_func);
369
370         gtk_signal_connect(GTK_OBJECT(ctree), "tree_select_row",
371                            GTK_SIGNAL_FUNC(addressbook_tree_selected), NULL);
372         gtk_signal_connect(GTK_OBJECT(ctree), "button_press_event",
373                            GTK_SIGNAL_FUNC(addressbook_tree_button_pressed),
374                            NULL);
375         gtk_signal_connect(GTK_OBJECT(ctree), "button_release_event",
376                            GTK_SIGNAL_FUNC(addressbook_tree_button_released),
377                            NULL);
378
379         clist_vbox = gtk_vbox_new(FALSE, 4);
380
381         clist_swin = gtk_scrolled_window_new(NULL, NULL);
382         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(clist_swin),
383                                        GTK_POLICY_AUTOMATIC,
384                                        GTK_POLICY_ALWAYS);
385         gtk_box_pack_start(GTK_BOX(clist_vbox), clist_swin, TRUE, TRUE, 0);
386
387         clist = gtk_clist_new_with_titles(N_COLS, titles);
388         gtk_container_add(GTK_CONTAINER(clist_swin), clist);
389         gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_EXTENDED);
390         gtk_clist_set_column_width(GTK_CLIST(clist), COL_NAME,
391                                    COL_NAME_WIDTH);
392         gtk_clist_set_column_width(GTK_CLIST(clist), COL_ADDRESS,
393                                    COL_ADDRESS_WIDTH);
394         gtk_clist_set_compare_func(GTK_CLIST(clist),
395                                    addressbook_list_compare_func);
396
397         for (i = 0; i < N_COLS; i++)
398                 GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(clist)->column[i].button,
399                                        GTK_CAN_FOCUS);
400
401         gtk_signal_connect(GTK_OBJECT(clist), "select_row",
402                            GTK_SIGNAL_FUNC(addressbook_list_selected), NULL);
403         gtk_signal_connect(GTK_OBJECT(clist), "button_press_event",
404                            GTK_SIGNAL_FUNC(addressbook_list_button_pressed),
405                            NULL);
406         gtk_signal_connect(GTK_OBJECT(clist), "button_release_event",
407                            GTK_SIGNAL_FUNC(addressbook_list_button_released),
408                            NULL);
409
410         hbox = gtk_hbox_new(FALSE, 4);
411         gtk_box_pack_start(GTK_BOX(clist_vbox), hbox, FALSE, FALSE, 0);
412
413         label = gtk_label_new(_("Name:"));
414         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
415
416         entry = gtk_entry_new();
417         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
418
419         address_completion_register_entry(GTK_ENTRY(entry));
420
421 #if 0
422         gtk_signal_connect(GTK_OBJECT(entry), "changed",
423                            GTK_SIGNAL_FUNC(addressbook_entry_changed), NULL);
424 #endif
425
426         paned = gtk_hpaned_new();
427         gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
428         gtk_paned_add1(GTK_PANED(paned), ctree_swin);
429         gtk_paned_add2(GTK_PANED(paned), clist_vbox);
430
431         hbbox = gtk_hbutton_box_new();
432         gtk_button_box_set_layout(GTK_BUTTON_BOX(hbbox), GTK_BUTTONBOX_END);
433         gtk_button_box_set_spacing(GTK_BUTTON_BOX(hbbox), 2);
434         gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
435
436         del_btn = gtk_button_new_with_label(_("Delete"));
437         GTK_WIDGET_SET_FLAGS(del_btn, GTK_CAN_DEFAULT);
438         gtk_box_pack_start(GTK_BOX(hbbox), del_btn, TRUE, TRUE, 0);
439         reg_btn = gtk_button_new_with_label(_("Add"));
440         GTK_WIDGET_SET_FLAGS(reg_btn, GTK_CAN_DEFAULT);
441         gtk_box_pack_start(GTK_BOX(hbbox), reg_btn, TRUE, TRUE, 0);
442         lup_btn = gtk_button_new_with_label(_("Lookup"));
443         GTK_WIDGET_SET_FLAGS(lup_btn, GTK_CAN_DEFAULT);
444         gtk_box_pack_start(GTK_BOX(hbbox), lup_btn, TRUE, TRUE, 0);
445
446         gtk_signal_connect(GTK_OBJECT(del_btn), "clicked",
447                            GTK_SIGNAL_FUNC(addressbook_del_clicked), NULL);
448         gtk_signal_connect(GTK_OBJECT(reg_btn), "clicked",
449                            GTK_SIGNAL_FUNC(addressbook_reg_clicked), NULL);
450
451         to_btn = gtk_button_new_with_label
452                 (prefs_common.trans_hdr ? _("To:") : "To:");
453         GTK_WIDGET_SET_FLAGS(to_btn, GTK_CAN_DEFAULT);
454         gtk_box_pack_start(GTK_BOX(hbbox), to_btn, TRUE, TRUE, 0);
455         cc_btn = gtk_button_new_with_label
456                 (prefs_common.trans_hdr ? _("Cc:") : "Cc:");
457         GTK_WIDGET_SET_FLAGS(cc_btn, GTK_CAN_DEFAULT);
458         gtk_box_pack_start(GTK_BOX(hbbox), cc_btn, TRUE, TRUE, 0);
459         bcc_btn = gtk_button_new_with_label
460                 (prefs_common.trans_hdr ? _("Bcc:") : "Bcc:");
461         GTK_WIDGET_SET_FLAGS(bcc_btn, GTK_CAN_DEFAULT);
462         gtk_box_pack_start(GTK_BOX(hbbox), bcc_btn, TRUE, TRUE, 0);
463
464         gtk_signal_connect(GTK_OBJECT(to_btn), "clicked",
465                            GTK_SIGNAL_FUNC(addressbook_to_clicked),
466                            GINT_TO_POINTER(COMPOSE_TO));
467         gtk_signal_connect(GTK_OBJECT(cc_btn), "clicked",
468                            GTK_SIGNAL_FUNC(addressbook_to_clicked),
469                            GINT_TO_POINTER(COMPOSE_CC));
470         gtk_signal_connect(GTK_OBJECT(bcc_btn), "clicked",
471                            GTK_SIGNAL_FUNC(addressbook_to_clicked),
472                            GINT_TO_POINTER(COMPOSE_BCC));
473
474         PIXMAP_CREATE(window, folderxpm, folderxpmmask, DIRECTORY_CLOSE_XPM);
475         PIXMAP_CREATE(window, folderopenxpm, folderopenxpmmask,
476                       DIRECTORY_OPEN_XPM);
477         PIXMAP_CREATE(window, groupxpm, groupxpmmask, group_xpm);
478
479         text = _("Common address");
480         addrbook.common =
481                 gtk_ctree_insert_node(GTK_CTREE(ctree),
482                                       NULL, NULL, &text, FOLDER_SPACING,
483                                       folderxpm, folderxpmmask,
484                                       folderopenxpm, folderopenxpmmask,
485                                       FALSE, FALSE);
486         text = _("Personal address");
487         addrbook.personal =
488                 gtk_ctree_insert_node(GTK_CTREE(ctree),
489                                       NULL, NULL, &text, FOLDER_SPACING,
490                                       folderxpm, folderxpmmask,
491                                       folderopenxpm, folderopenxpmmask,
492                                       FALSE, FALSE);
493
494         /* popup menu */
495         n_entries = sizeof(addressbook_tree_popup_entries) /
496                 sizeof(addressbook_tree_popup_entries[0]);
497         tree_popup = menu_create_items(addressbook_tree_popup_entries,
498                                        n_entries,
499                                        "<AddressBookTree>", &tree_factory,
500                                        NULL);
501         gtk_signal_connect(GTK_OBJECT(tree_popup), "selection_done",
502                            GTK_SIGNAL_FUNC(addressbook_popup_close), NULL);
503         n_entries = sizeof(addressbook_list_popup_entries) /
504                 sizeof(addressbook_list_popup_entries[0]);
505         list_popup = menu_create_items(addressbook_list_popup_entries,
506                                        n_entries,
507                                        "<AddressBookList>", &list_factory,
508                                        NULL);
509
510         addrbook.window = window;
511         addrbook.ctree  = ctree;
512         addrbook.clist  = clist;
513         addrbook.entry  = entry;
514
515         addrbook.del_btn = del_btn;
516         addrbook.reg_btn = reg_btn;
517         addrbook.lup_btn = lup_btn;
518         addrbook.to_btn  = to_btn;
519         addrbook.cc_btn  = cc_btn;
520         addrbook.bcc_btn = bcc_btn;
521
522         addrbook.tree_popup   = tree_popup;
523         addrbook.list_popup   = list_popup;
524         addrbook.tree_factory = tree_factory;
525         addrbook.list_factory = list_factory;
526
527         address_completion_start(window);
528
529         if (show) 
530                 gtk_widget_show_all(window);
531 }
532
533 static gint addressbook_close(void)
534 {
535         gtk_widget_hide(addrbook.window);
536         addressbook_export_to_file();
537         /* tell addr_compl that there's a new addressbook file */
538         invalidate_address_completion();
539         return TRUE;
540 }
541
542 static void addressbook_button_set_sensitive(void)
543 {
544         gboolean to_sens  = FALSE;
545         gboolean cc_sens  = FALSE;
546         gboolean bcc_sens = FALSE;
547
548         if (!addrbook.window) return;
549
550         if (addrbook.target_compose) {
551                 to_sens = TRUE;
552                 cc_sens = TRUE;
553                 if (addrbook.target_compose->use_bcc)
554                         bcc_sens = TRUE;
555         }
556
557         gtk_widget_set_sensitive(addrbook.to_btn, to_sens);
558         gtk_widget_set_sensitive(addrbook.cc_btn, cc_sens);
559         gtk_widget_set_sensitive(addrbook.bcc_btn, bcc_sens);
560 }
561
562 static void addressbook_del_clicked(GtkButton *button, gpointer data)
563 {
564         GtkCList *clist = GTK_CLIST(addrbook.clist);
565         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
566         AddressObject *pobj, *obj;
567         GList *cur, *next;
568         gint row;
569
570         if (!clist->selection) {
571                 addressbook_delete_folder_cb(NULL, 0, NULL);
572                 return;
573         }
574
575         pobj = gtk_ctree_node_get_row_data(ctree, addrbook.opened);
576         g_return_if_fail(pobj != NULL);
577
578         if (alertpanel(_("Delete address(es)"),
579                        _("Really delete the address(es)?"),
580                        _("Yes"), _("No"), NULL) != G_ALERTDEFAULT)
581                 return;
582
583         for (cur = clist->selection; cur != NULL; cur = next) {
584                 next = cur->next;
585                 row = GPOINTER_TO_INT(cur->data);
586
587                 obj = gtk_clist_get_row_data(clist, row);
588                 if (!obj) continue;
589
590                 if (pobj->type == ADDR_GROUP) {
591                         AddressGroup *group = ADDRESS_GROUP(pobj);
592                         group->items = g_list_remove(group->items, obj);
593                 } else if (pobj->type == ADDR_FOLDER) {
594                         AddressFolder *folder = ADDRESS_FOLDER(pobj);
595
596                         folder->items = g_list_remove(folder->items, obj);
597                         if (obj->type == ADDR_GROUP) {
598                                 GtkCTreeNode *node;
599
600                                 node = gtk_ctree_find_by_row_data
601                                         (ctree, addrbook.opened, obj);
602                                 if (node) gtk_ctree_remove_node(ctree, node);
603                         }
604                 } else
605                         continue;
606
607                 addressbook_delete_object(obj);
608
609                 gtk_clist_remove(clist, row);
610         }
611 }
612
613 static void addressbook_reg_clicked(GtkButton *button, gpointer data)
614 {
615         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
616         GtkEntry *entry = GTK_ENTRY(addrbook.entry);
617         AddressObject *obj;
618         AddressItem *item;
619         gchar *str;
620
621         if (*gtk_entry_get_text(entry) == '\0') {
622                 addressbook_new_address_cb(NULL, 0, NULL);
623                 return;
624         }
625         if (!addrbook.opened) return;
626
627         obj = gtk_ctree_node_get_row_data(ctree, addrbook.opened);
628         if (!obj) return;
629
630         g_return_if_fail(obj->type == ADDR_GROUP || obj->type == ADDR_FOLDER);
631
632         str = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
633
634         item = addressbook_parse_address(str);
635         g_free(str);
636         if (item) {
637                 if (addressbook_find_object_by_name
638                         (addrbook.opened, item->name) != NULL) {
639                         addressbook_delete_object(ADDRESS_OBJECT(item));
640                         item = NULL;
641                 } else if (addressbook_edit_address(item) == NULL) {
642                         addressbook_delete_object(ADDRESS_OBJECT(item));
643                         return;
644                 }
645         }
646
647         if (!item) {
648                 item = addressbook_edit_address(NULL);
649                 if (!item) return;
650         }
651
652         if (addressbook_find_object_by_name(addrbook.opened, item->name)) {
653                 addressbook_delete_object(ADDRESS_OBJECT(item));
654                 return;
655         }
656
657         addressbook_add_object(addrbook.opened, ADDRESS_OBJECT(item));
658         addrbook.open_folder = TRUE;
659         gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened);
660 }
661
662 static AddressItem *addressbook_parse_address(const gchar *str)
663 {
664         gchar *name    = NULL;
665         gchar *address = NULL;
666         AddressItem *item;
667         gchar *buf;
668         gchar *start, *end;
669
670         Xalloca(buf, strlen(str) + 1, return NULL);
671
672         strcpy(buf, str);
673         g_strstrip(buf);
674         if (*buf == '\0') return NULL;
675
676         if ((start = strchr(buf, '<'))) {
677                 if (start > buf) {
678                         *start = '\0';
679                         g_strstrip(buf);
680                         if (*buf != '\0')
681                                 name = g_strdup(buf);
682                 }
683                 start++;
684                 if ((end = strchr(start, '>'))) {
685                         *end = '\0';
686                         g_strstrip(start);
687                         if (*start != '\0')
688                                 address = g_strdup(start);
689                 }
690         } else
691                 name = g_strdup(buf);
692
693         if (!name && !address) return NULL;
694
695         item = g_new(AddressItem, 1);
696         ADDRESS_OBJECT_TYPE(item) = ADDR_ITEM;
697         item->name    = name;
698         item->address = address;
699         item->remarks = NULL;
700
701         return item;
702 }
703
704 static void addressbook_to_clicked(GtkButton *button, gpointer data)
705 {
706         GtkCList *clist = GTK_CLIST(addrbook.clist);
707         GList *cur;
708
709         if (!addrbook.target_compose) return;
710
711         for (cur = clist->selection; cur != NULL; cur = cur->next) {
712                 AddressObject *obj;
713
714                 obj = gtk_clist_get_row_data(clist,
715                                              GPOINTER_TO_INT(cur->data));
716                 if (!obj) return;
717
718                 if (obj->type == ADDR_ITEM) {
719                         addressbook_append_to_compose_entry
720                                 (ADDRESS_ITEM(obj), (ComposeEntryType)data);
721                 } else if (obj->type == ADDR_GROUP) {
722                         AddressGroup *group;
723                         GList *cur_item;
724
725                         group = ADDRESS_GROUP(obj);
726                         for (cur_item = group->items; cur_item != NULL;
727                              cur_item = cur_item->next) {
728                                 if (ADDRESS_OBJECT(cur_item->data)->type
729                                     != ADDR_ITEM)
730                                         continue;
731                                 addressbook_append_to_compose_entry
732                                         (ADDRESS_ITEM(cur_item->data),
733                                          (ComposeEntryType)data);
734                         }
735                 }
736         }
737 }
738
739 static void addressbook_append_to_compose_entry(AddressItem *item,
740                                                 ComposeEntryType type)
741 {
742         Compose *compose = addrbook.target_compose;
743
744         if (item->name && item->address) {
745                 gchar *buf;
746
747                 buf = g_strdup_printf
748                         ("%s <%s>", item->name, item->address);
749                 compose_entry_append(compose, buf, type);
750                 g_free(buf);
751         } else if (item->address)
752                 compose_entry_append(compose, item->address, type);
753 }
754
755 static void addressbook_tree_selected(GtkCTree *ctree, GtkCTreeNode *node,
756                                       gint column, gpointer data)
757 {
758         AddressObject *obj;
759
760         addrbook.selected = node;
761
762         if (!addrbook.open_folder) return;
763         addrbook.open_folder = FALSE;
764
765         gtk_entry_set_text(GTK_ENTRY(addrbook.entry), "");
766
767         obj = gtk_ctree_node_get_row_data(ctree, node);
768         g_return_if_fail(obj != NULL);
769
770         addrbook.opened = node;
771
772         if (obj->type == ADDR_GROUP || obj->type == ADDR_FOLDER)
773                 addressbook_set_clist(obj);
774 }
775
776 static void addressbook_list_selected(GtkCList *clist, gint row, gint column,
777                                       GdkEvent *event, gpointer data)
778 {
779         GtkEntry *entry = GTK_ENTRY(addrbook.entry);
780         AddressObject *obj;
781         GList *cur;
782
783         if (event && event->type == GDK_2BUTTON_PRESS) {
784                 if (prefs_common.add_address_by_click &&
785                     addrbook.target_compose)
786                         addressbook_to_clicked(NULL, NULL);
787                 else
788                         addressbook_edit_address_cb(NULL, 0, NULL);
789                 return;
790         }
791
792 #if 0
793         gtk_signal_handler_block_by_func
794                 (GTK_OBJECT(entry),
795                  GTK_SIGNAL_FUNC(addressbook_entry_changed), NULL);
796 #endif           
797
798         gtk_entry_set_text(entry, "");
799
800         for (cur = clist->selection; cur != NULL; cur = cur->next) {
801                 obj = gtk_clist_get_row_data(clist,
802                                              GPOINTER_TO_INT(cur->data));
803                 g_return_if_fail(obj != NULL);
804
805                 if (obj->type == ADDR_ITEM) {
806                         AddressItem *item;
807
808                         item = ADDRESS_ITEM(obj);
809                         if (item->name && item->address) {
810                                 gchar *buf;
811
812                                 buf = g_strdup_printf
813                                         ("%s <%s>", item->name, item->address);
814                                 if (*gtk_entry_get_text(entry) != '\0')
815                                         gtk_entry_append_text(entry, ", ");
816                                 gtk_entry_append_text(entry, buf);
817                                 g_free(buf);
818                         } else if (item->address) {
819                                 if (*gtk_entry_get_text(entry) != '\0')
820                                         gtk_entry_append_text(entry, ", ");
821                                 gtk_entry_append_text(entry, item->address);
822                         }
823                 }
824         }
825
826 #if 0
827         gtk_signal_handler_unblock_by_func
828                 (GTK_OBJECT(entry),
829                  GTK_SIGNAL_FUNC(addressbook_entry_changed), NULL);
830 #endif           
831 }
832
833 #if 0
834 static void addressbook_entry_changed(GtkWidget *widget)
835 {
836         GtkCList *clist = GTK_CLIST(addrbook.clist);
837         GtkEntry *entry = GTK_ENTRY(addrbook.entry);
838         const gchar *str;
839         gint len;
840         gint row;
841
842         //if (clist->selection && clist->selection->next) return;
843
844         str = gtk_entry_get_text(entry);
845         if (*str == '\0') {
846                 gtk_clist_unselect_all(clist);
847                 return;
848         }
849         len = strlen(str);
850
851         for (row = 0; row < clist->rows; row++) {
852                 AddressObject *obj;
853                 const gchar *name;
854
855                 obj = ADDRESS_OBJECT(gtk_clist_get_row_data(clist, row));
856                 if (!obj) continue;
857                 if (obj->type == ADDR_ITEM)
858                         name = ADDRESS_ITEM(obj)->name;
859                 else if (obj->type == ADDR_GROUP)
860                         name = ADDRESS_GROUP(obj)->name;
861                 else
862                         continue;
863
864                 if (name && !strncasecmp(name, str, len)) {
865                         gtk_clist_unselect_all(clist);
866                         gtk_clist_select_row(clist, row, -1);
867                         return;
868                 }
869         }
870
871         gtk_clist_unselect_all(clist);
872 }
873 #endif
874
875 static void addressbook_list_button_pressed(GtkWidget *widget,
876                                             GdkEventButton *event,
877                                             gpointer data)
878 {
879         GtkCList *clist = GTK_CLIST(widget);
880         gint row, column;
881         AddressObject *obj;
882
883         if (!event) return;
884         if (event->button != 3) return;
885
886         obj = gtk_ctree_node_get_row_data(GTK_CTREE(addrbook.ctree),
887                                           addrbook.opened);
888         g_return_if_fail(obj != NULL);
889
890         menu_set_insensitive_all(GTK_MENU_SHELL(addrbook.list_popup));
891
892         if (gtk_clist_get_selection_info
893                 (clist, event->x, event->y, &row, &column)) {
894                 GtkCListRow *clist_row;
895
896                 clist_row = g_list_nth(clist->row_list, row)->data;
897                 if (clist_row->state != GTK_STATE_SELECTED) {
898                         gtk_clist_unselect_all(clist);
899                         gtk_clist_select_row(clist, row, column);
900                 }
901                 gtkut_clist_set_focus_row(clist, row);
902
903                 menu_set_sensitive(addrbook.list_factory, "/Edit", TRUE);
904                 menu_set_sensitive(addrbook.list_factory, "/Delete", TRUE);
905         }
906
907         menu_set_sensitive(addrbook.list_factory, "/New address", TRUE);
908         if (obj->type == ADDR_FOLDER) {
909                 menu_set_sensitive(addrbook.list_factory, "/New folder", TRUE);
910                 menu_set_sensitive(addrbook.list_factory, "/New group", TRUE);
911         }
912
913         gtk_menu_popup(GTK_MENU(addrbook.list_popup), NULL, NULL, NULL, NULL,
914                        event->button, event->time);
915 }
916
917 static void addressbook_list_button_released(GtkWidget *widget,
918                                              GdkEventButton *event,
919                                              gpointer data)
920 {
921 }
922
923 static void addressbook_tree_button_pressed(GtkWidget *ctree,
924                                             GdkEventButton *event,
925                                             gpointer data)
926 {
927         GtkCList *clist = GTK_CLIST(ctree);
928         gint row, column;
929         AddressObject *obj;
930         GtkCTreeNode *node;
931
932         if (!event) return;
933         if (event->button == 1) {
934                 addrbook.open_folder = TRUE;
935                 return;
936         }
937         if (event->button != 3) return;
938
939         if (!gtk_clist_get_selection_info
940                 (clist, event->x, event->y, &row, &column)) return;
941         gtk_clist_select_row(clist, row, column);
942
943         obj = gtk_clist_get_row_data(clist, row);
944         g_return_if_fail(obj != NULL);
945
946         menu_set_insensitive_all(GTK_MENU_SHELL(addrbook.tree_popup));
947
948         if (obj->type == ADDR_FOLDER) {
949                 node = gtk_ctree_node_nth(GTK_CTREE(ctree), row);
950                 menu_set_sensitive(addrbook.tree_factory, "/New folder", TRUE);
951                 menu_set_sensitive(addrbook.tree_factory, "/New group", TRUE);
952                 if (node && GTK_CTREE_ROW(node)->level >= 2) {
953                         menu_set_sensitive(addrbook.tree_factory,
954                                            "/Edit", TRUE);
955                         menu_set_sensitive(addrbook.tree_factory,
956                                            "/Delete", TRUE);
957                 }
958         } else if (obj->type == ADDR_GROUP) {
959                 menu_set_sensitive(addrbook.tree_factory, "/Edit", TRUE);
960                 menu_set_sensitive(addrbook.tree_factory, "/Delete", TRUE);
961         } else
962                 return;
963
964         menu_set_sensitive(addrbook.tree_factory, "/New address", TRUE);
965
966         gtk_menu_popup(GTK_MENU(addrbook.tree_popup), NULL, NULL, NULL, NULL,
967                        event->button, event->time);
968 }
969
970 static void addressbook_tree_button_released(GtkWidget *ctree,
971                                              GdkEventButton *event,
972                                              gpointer data)
973 {
974         gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened);
975         gtkut_ctree_set_focus_row(GTK_CTREE(addrbook.ctree), addrbook.opened);
976 }
977
978 static void addressbook_popup_close(GtkMenuShell *menu_shell, gpointer data)
979 {
980         if (!addrbook.opened) return;
981
982         gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened);
983         gtkut_ctree_set_focus_row(GTK_CTREE(addrbook.ctree),
984                                   addrbook.opened);
985 }
986
987 static void addressbook_new_folder_cb(gpointer data, guint action,
988                                       GtkWidget *widget)
989 {
990         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
991         AddressObject *obj;
992         AddressFolder *folder;
993         gchar *new_folder;
994
995         if (!addrbook.selected) return;
996
997         obj = gtk_ctree_node_get_row_data(ctree, addrbook.selected);
998         g_return_if_fail(obj != NULL);
999         if (obj->type != ADDR_FOLDER) return;
1000
1001         new_folder = input_dialog(_("New folder"),
1002                                   _("Input the name of new folder:"),
1003                                   _("NewFolder"));
1004         if (!new_folder) return;
1005         g_strstrip(new_folder);
1006         if (*new_folder == '\0') {
1007                 g_free(new_folder);
1008                 return;
1009         }
1010
1011         if (gtk_ctree_find_by_row_data_custom(ctree, addrbook.selected,
1012                                               new_folder,
1013                                               addressbook_obj_name_compare)) {
1014                 alertpanel_error(_("The name already exists."));
1015                 g_free(new_folder);
1016                 return;
1017         }
1018
1019         folder = g_new(AddressFolder, 1);
1020         ADDRESS_OBJECT_TYPE(folder) = ADDR_FOLDER;
1021         folder->name = g_strdup(new_folder);
1022         folder->items = NULL;
1023
1024         addressbook_add_object(addrbook.selected, ADDRESS_OBJECT(folder));
1025
1026         g_free(new_folder);
1027
1028         if (addrbook.selected == addrbook.opened)
1029                 addressbook_set_clist(obj);
1030 }
1031
1032 static void addressbook_new_group_cb(gpointer data, guint action,
1033                                      GtkWidget *widget)
1034 {
1035         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1036         AddressObject *obj;
1037         AddressGroup *group;
1038         gchar *new_group;
1039
1040         if (!addrbook.selected) return;
1041
1042         obj = gtk_ctree_node_get_row_data(ctree, addrbook.selected);
1043         g_return_if_fail(obj != NULL);
1044         if (obj->type != ADDR_FOLDER) return;
1045
1046         new_group = input_dialog(_("New group"),
1047                                  _("Input the name of new group:"),
1048                                   _("NewGroup"));
1049         if (!new_group) return;
1050         g_strstrip(new_group);
1051         if (*new_group == '\0') {
1052                 g_free(new_group);
1053                 return;
1054         }
1055
1056         if (gtk_ctree_find_by_row_data_custom(ctree, addrbook.selected,
1057                                               new_group,
1058                                               addressbook_obj_name_compare)) {
1059                 alertpanel_error(_("The name already exists."));
1060                 g_free(new_group);
1061                 return;
1062         }
1063
1064         group = g_new(AddressGroup, 1);
1065         ADDRESS_OBJECT_TYPE(group) = ADDR_GROUP;
1066         group->name = g_strdup(new_group);
1067         group->items = NULL;
1068
1069         addressbook_add_object(addrbook.selected, ADDRESS_OBJECT(group));
1070
1071         g_free(new_group);
1072
1073         if (addrbook.selected == addrbook.opened)
1074                 addressbook_set_clist(obj);
1075 }
1076
1077 static void addressbook_change_node_name(GtkCTreeNode *node, const gchar *name)
1078 {
1079         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1080         gchar *text[1];
1081         guint8 spacing;
1082         GdkPixmap *pix_cl, *pix_op;
1083         GdkBitmap *mask_cl, *mask_op;
1084         gboolean is_leaf, expanded;
1085
1086         gtk_ctree_get_node_info(ctree, node, text, &spacing,
1087                                 &pix_cl, &mask_cl, &pix_op, &mask_op,
1088                                 &is_leaf, &expanded);
1089         gtk_ctree_set_node_info(ctree, node, name, spacing,
1090                                 pix_cl, mask_cl, pix_op, mask_op,
1091                                 is_leaf, expanded);
1092 }
1093
1094 static void addressbook_edit_group(GtkCTreeNode *group_node)
1095 {
1096         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1097         GtkCList *clist = GTK_CLIST(addrbook.clist);
1098         AddressObject *obj;
1099         AddressGroup *group;
1100         gchar *new_name;
1101         GtkCTreeNode *node;
1102
1103         if (!group_node && clist->selection) {
1104                 obj = gtk_clist_get_row_data(clist,
1105                                              GPOINTER_TO_INT(clist->selection->data));
1106                 g_return_if_fail(obj != NULL);
1107                 if (obj->type != ADDR_GROUP) return;
1108                 node = gtk_ctree_find_by_row_data
1109                         (ctree, addrbook.selected, obj);
1110                 if (!node) return;
1111         } else {
1112                 if (group_node)
1113                         node = group_node;
1114                 else
1115                         node = addrbook.selected;
1116                 obj = gtk_ctree_node_get_row_data(ctree, node);
1117                 g_return_if_fail(obj != NULL);
1118                 if (obj->type != ADDR_GROUP) return;
1119         }
1120
1121         group = ADDRESS_GROUP(obj);
1122
1123         new_name = input_dialog(_("Edit group"),
1124                                 _("Input the new name of group:"),
1125                                 group->name);
1126         if (!new_name) return;
1127         g_strstrip(new_name);
1128         if (*new_name == '\0') {
1129                 g_free(new_name);
1130                 return;
1131         }
1132
1133         if (gtk_ctree_find_by_row_data_custom(ctree, addrbook.selected,
1134                                               new_name,
1135                                               addressbook_obj_name_compare)) {
1136                 alertpanel_error(_("The name already exists."));
1137                 g_free(new_name);
1138                 return;
1139         }
1140
1141         g_free(group->name);
1142         group->name = g_strdup(new_name);
1143
1144         addressbook_change_node_name(node, new_name);
1145         gtk_ctree_sort_node(ctree, GTK_CTREE_ROW(node)->parent);
1146
1147         g_free(new_name);
1148
1149         addrbook.open_folder = TRUE;
1150         gtk_ctree_select(ctree, addrbook.opened);
1151 }
1152
1153 static void addressbook_edit_folder_cb(gpointer data, guint action,
1154                                        GtkWidget *widget)
1155 {
1156         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1157         AddressObject *obj;
1158         AddressFolder *folder;
1159         gchar *new_name;
1160
1161         if (!addrbook.selected) return;
1162         if (GTK_CTREE_ROW(addrbook.selected)->level == 1) return;
1163
1164         obj = gtk_ctree_node_get_row_data(ctree, addrbook.selected);
1165         g_return_if_fail(obj != NULL);
1166         g_return_if_fail(obj->type == ADDR_FOLDER || obj->type == ADDR_GROUP);
1167
1168         if (obj->type == ADDR_GROUP) {
1169                 addressbook_edit_group(addrbook.selected);
1170                 return;
1171         }
1172
1173         folder = ADDRESS_FOLDER(obj);
1174         new_name = input_dialog(_("Edit folder"),
1175                                 _("Input the new name of folder:"),
1176                                 folder->name);
1177
1178         if (!new_name) return;
1179         g_strstrip(new_name);
1180         if (*new_name == '\0') {
1181                 g_free(new_name);
1182                 return;
1183         }
1184
1185         if (gtk_ctree_find_by_row_data_custom(ctree, addrbook.selected,
1186                                               new_name,
1187                                               addressbook_obj_name_compare)) {
1188                 alertpanel_error(_("The name already exists."));
1189                 g_free(new_name);
1190                 return;
1191         }
1192
1193         g_free(folder->name);
1194         folder->name = g_strdup(new_name);
1195
1196         addressbook_change_node_name(addrbook.selected, new_name);
1197         gtk_ctree_sort_node(ctree, GTK_CTREE_ROW(addrbook.selected)->parent);
1198
1199         g_free(new_name);
1200 }
1201
1202 static void addressbook_delete_folder_cb(gpointer data, guint action,
1203                                          GtkWidget *widget)
1204 {
1205         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1206         AddressObject *obj, *pobj;
1207         gchar *name;
1208         gchar *message;
1209         AlertValue aval;
1210
1211         if (!addrbook.selected) return;
1212         if (GTK_CTREE_ROW(addrbook.selected)->level == 1) return;
1213
1214         obj = gtk_ctree_node_get_row_data(ctree, addrbook.selected);
1215         g_return_if_fail(obj != NULL);
1216
1217         if (obj->type == ADDR_GROUP)
1218                 name = ADDRESS_GROUP(obj)->name;
1219         else if (obj->type == ADDR_FOLDER)
1220                 name = ADDRESS_FOLDER(obj)->name;
1221         else
1222                 return;
1223
1224         message = g_strdup_printf(_("Really delete `%s' ?"), name);
1225         aval = alertpanel(_("Delete"), message, _("Yes"), _("No"), NULL);
1226         g_free(message);
1227         if (aval != G_ALERTDEFAULT) return;
1228
1229         pobj = gtk_ctree_node_get_row_data
1230                 (ctree, GTK_CTREE_ROW(addrbook.selected)->parent);
1231         if (!pobj) return;
1232         g_return_if_fail(pobj->type == ADDR_FOLDER);
1233         ADDRESS_FOLDER(pobj)->items =
1234                 g_list_remove(ADDRESS_FOLDER(pobj)->items, obj);
1235
1236         addressbook_delete_object(obj);
1237         addrbook.open_folder = TRUE;
1238         gtk_ctree_remove_node(ctree, addrbook.selected);
1239         addrbook.open_folder = FALSE;
1240 }
1241
1242 #define SET_LABEL_AND_ENTRY(str, entry, top) \
1243 { \
1244         label = gtk_label_new(str); \
1245         gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
1246                          GTK_FILL, 0, 0, 0); \
1247         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
1248  \
1249         entry = gtk_entry_new(); \
1250         gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
1251                          GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
1252 }
1253
1254 static void addressbook_edit_address_create(gboolean *cancelled)
1255 {
1256         GtkWidget *window;
1257         GtkWidget *vbox;
1258         GtkWidget *table;
1259         GtkWidget *label;
1260         GtkWidget *name_entry;
1261         GtkWidget *addr_entry;
1262         GtkWidget *rem_entry;
1263         GtkWidget *hbbox;
1264         GtkWidget *ok_btn;
1265         GtkWidget *cancel_btn;
1266
1267         debug_print("Creating edit_address window...\n");
1268
1269         window = gtk_window_new(GTK_WINDOW_DIALOG);
1270         gtk_widget_set_usize(window, 400, -1);
1271         gtk_container_set_border_width(GTK_CONTAINER(window), 8);
1272         gtk_window_set_title(GTK_WINDOW(window), _("Edit address"));
1273         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
1274         gtk_window_set_modal(GTK_WINDOW(window), TRUE); 
1275         gtk_signal_connect(GTK_OBJECT(window), "delete_event",
1276                            GTK_SIGNAL_FUNC(edit_address_delete_event),
1277                            cancelled);
1278         gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
1279                            GTK_SIGNAL_FUNC(edit_address_key_pressed),
1280                            cancelled);
1281
1282         vbox = gtk_vbox_new(FALSE, 8);
1283         gtk_container_add(GTK_CONTAINER(window), vbox);
1284
1285         table = gtk_table_new(3, 2, FALSE);
1286         gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
1287         gtk_table_set_row_spacings(GTK_TABLE(table), 8);
1288         gtk_table_set_col_spacings(GTK_TABLE(table), 8);
1289
1290         SET_LABEL_AND_ENTRY(_("Name"),    name_entry, 0);
1291         SET_LABEL_AND_ENTRY(_("Address"), addr_entry, 1);
1292         SET_LABEL_AND_ENTRY(_("Remarks"), rem_entry,  2);
1293
1294         gtkut_button_set_create(&hbbox, &ok_btn, _("OK"),
1295                                 &cancel_btn, _("Cancel"), NULL, NULL);
1296         gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
1297         gtk_widget_grab_default(ok_btn);
1298
1299         gtk_signal_connect(GTK_OBJECT(ok_btn), "clicked",
1300                            GTK_SIGNAL_FUNC(edit_address_ok), cancelled);
1301         gtk_signal_connect(GTK_OBJECT(cancel_btn), "clicked",
1302                            GTK_SIGNAL_FUNC(edit_address_cancel), cancelled);
1303
1304         gtk_widget_show_all(vbox);
1305
1306         addredit.window     = window;
1307         addredit.name_entry = name_entry;
1308         addredit.addr_entry = addr_entry;
1309         addredit.rem_entry  = rem_entry;
1310         addredit.ok_btn     = ok_btn;
1311         addredit.cancel_btn = cancel_btn;
1312 }
1313
1314 static void edit_address_ok(GtkWidget *widget, gboolean *cancelled)
1315 {
1316         *cancelled = FALSE;
1317         gtk_main_quit();
1318 }
1319
1320 static void edit_address_cancel(GtkWidget *widget, gboolean *cancelled)
1321 {
1322         *cancelled = TRUE;
1323         gtk_main_quit();
1324 }
1325
1326 static gint edit_address_delete_event(GtkWidget *widget, GdkEventAny *event,
1327                                       gboolean *cancelled)
1328 {
1329         *cancelled = TRUE;
1330         gtk_main_quit();
1331
1332         return TRUE;
1333 }
1334
1335 static void edit_address_key_pressed(GtkWidget *widget, GdkEventKey *event,
1336                                      gboolean *cancelled)
1337 {
1338         if (event && event->keyval == GDK_Escape) {
1339                 *cancelled = TRUE;
1340                 gtk_main_quit();
1341         }
1342 }
1343
1344 static AddressItem *addressbook_edit_address(AddressItem *item)
1345 {
1346         static gboolean cancelled;
1347         const gchar *str;
1348
1349         if (!addredit.window)
1350                 addressbook_edit_address_create(&cancelled);
1351         gtk_widget_grab_focus(addredit.ok_btn);
1352         gtk_widget_grab_focus(addredit.name_entry);
1353         gtk_widget_show(addredit.window);
1354         manage_window_set_transient(GTK_WINDOW(addredit.window));
1355
1356         gtk_entry_set_text(GTK_ENTRY(addredit.name_entry), "");
1357         gtk_entry_set_text(GTK_ENTRY(addredit.addr_entry), "");
1358         gtk_entry_set_text(GTK_ENTRY(addredit.rem_entry),  "");
1359
1360         if (item) {
1361                 if (item->name)
1362                         gtk_entry_set_text(GTK_ENTRY(addredit.name_entry),
1363                                            item->name);
1364                 if (item->address)
1365                         gtk_entry_set_text(GTK_ENTRY(addredit.addr_entry),
1366                                            item->address);
1367                 if (item->remarks)
1368                         gtk_entry_set_text(GTK_ENTRY(addredit.rem_entry),
1369                                            item->remarks);
1370         }
1371
1372         gtk_main();
1373         gtk_widget_hide(addredit.window);
1374         if (cancelled == TRUE) return NULL;
1375
1376         str = gtk_entry_get_text(GTK_ENTRY(addredit.name_entry));
1377         if (*str == '\0') return NULL;
1378
1379         if (!item) {
1380                 item = g_new0(AddressItem, 1);
1381                 ADDRESS_OBJECT_TYPE(item) = ADDR_ITEM;
1382         }
1383
1384         g_free(item->name);
1385         item->name = g_strdup(str);
1386
1387         str = gtk_entry_get_text(GTK_ENTRY(addredit.addr_entry));
1388         g_free(item->address);
1389         if (*str == '\0')
1390                 item->address = NULL;
1391         else
1392                 item->address = g_strdup(str);
1393
1394         str = gtk_entry_get_text(GTK_ENTRY(addredit.rem_entry));
1395         g_free(item->remarks);
1396         if (*str == '\0')
1397                 item->remarks = NULL;
1398         else
1399                 item->remarks = g_strdup(str);
1400
1401         return item;
1402 }
1403
1404 static void addressbook_new_address_cb(gpointer data, guint action,
1405                                        GtkWidget *widget)
1406 {
1407         AddressItem *item;
1408
1409         item = addressbook_edit_address(NULL);
1410
1411         if (item) {
1412                 addressbook_add_object(addrbook.selected,
1413                                        ADDRESS_OBJECT(item));
1414                 if (addrbook.selected == addrbook.opened) {
1415                         addrbook.open_folder = TRUE;
1416                         gtk_ctree_select(GTK_CTREE(addrbook.ctree),
1417                                          addrbook.opened);
1418                 }
1419         }
1420 }
1421
1422 static void addressbook_edit_address_cb(gpointer data, guint action,
1423                                         GtkWidget *widget)
1424 {
1425         GtkCList *clist = GTK_CLIST(addrbook.clist);
1426         AddressObject *obj;
1427
1428         if (!clist->selection) {
1429                 addressbook_edit_folder_cb(NULL, 0, NULL);
1430                 return;
1431         }
1432
1433         obj = gtk_clist_get_row_data(clist,
1434                                      GPOINTER_TO_INT(clist->selection->data));
1435         g_return_if_fail(obj != NULL);
1436
1437         if (obj->type == ADDR_ITEM) {
1438                 AddressItem *item = ADDRESS_ITEM(obj);
1439
1440                 if (addressbook_edit_address(item) == NULL) return;
1441
1442                 addrbook.open_folder = TRUE;
1443                 gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened);
1444
1445                 return;
1446         } else if (obj->type == ADDR_GROUP) {
1447                 addressbook_edit_group(NULL);
1448         }
1449 }
1450
1451 static void addressbook_delete_address_cb(gpointer data, guint action,
1452                                           GtkWidget *widget)
1453 {
1454         addressbook_del_clicked(NULL, NULL);
1455 }
1456
1457 static void close_cb(gpointer data, guint action, GtkWidget *widget)
1458 {
1459         addressbook_close();
1460 }
1461
1462 static void addressbook_set_clist(AddressObject *obj)
1463 {
1464         GtkCList *clist = GTK_CLIST(addrbook.clist);
1465         GList *items;
1466         gchar *text[N_COLS];
1467
1468         if (!obj) {
1469                 gtk_clist_clear(clist);
1470                 return;
1471         }
1472
1473         gtk_clist_freeze(clist);
1474
1475         gtk_clist_clear(clist);
1476
1477         if (obj->type == ADDR_GROUP)
1478                 items = ADDRESS_GROUP(obj)->items;
1479         else if (obj->type == ADDR_FOLDER)
1480                 items = ADDRESS_FOLDER(obj)->items;
1481         else {
1482                 gtk_clist_thaw(clist);
1483                 return;
1484         }
1485
1486         for (; items != NULL; items = items->next) {
1487                 AddressObject *iobj;
1488                 gint row;
1489
1490                 iobj = ADDRESS_OBJECT(items->data);
1491
1492                 if (iobj->type == ADDR_GROUP) {
1493                         AddressGroup *group;
1494
1495                         group = ADDRESS_GROUP(iobj);
1496                         text[COL_NAME]    = group->name;
1497                         text[COL_ADDRESS] = NULL;
1498                         text[COL_REMARKS] = NULL;
1499                         row = gtk_clist_append(clist, text);
1500                         gtk_clist_set_pixtext(clist, row, COL_NAME,
1501                                               group->name, 4,
1502                                               groupxpm, groupxpmmask);
1503                         gtk_clist_set_row_data(clist, row, iobj);
1504                 } else if (iobj->type == ADDR_ITEM) {
1505                         AddressItem *item;
1506
1507                         item = ADDRESS_ITEM(iobj);
1508                         text[COL_NAME]    = item->name;
1509                         text[COL_ADDRESS] = item->address;
1510                         text[COL_REMARKS] = item->remarks;
1511                         row = gtk_clist_append(clist, text);
1512                         gtk_clist_set_row_data(clist, row, iobj);
1513                 }
1514         }
1515
1516         gtk_clist_sort(clist);
1517         gtk_clist_thaw(clist);
1518 }
1519
1520 static void addressbook_read_file(void)
1521 {
1522         XMLFile *file;
1523         gchar *path;
1524
1525         debug_print(_("Reading addressbook file..."));
1526
1527         path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESS_BOOK, NULL);
1528         if ((file = xml_open_file(path)) == NULL) {
1529                 debug_print(_("%s doesn't exist.\n"), path);
1530                 g_free(path);
1531                 addressbook_get_tree(NULL, addrbook.common, "common_address");
1532                 addressbook_get_tree(NULL, addrbook.personal, "personal_address");
1533                 return;
1534         }
1535         g_free(path);
1536
1537         xml_get_dtd(file);
1538
1539         if (xml_parse_next_tag(file) < 0 ||
1540             xml_compare_tag(file, "addressbook") == FALSE) {
1541                 g_warning("Invalid addressbook data\n");
1542                 xml_close_file(file);
1543                 return;
1544         }
1545
1546         addressbook_get_tree(file, addrbook.common, "common_address");
1547         addressbook_get_tree(file, addrbook.personal, "personal_address");
1548
1549         xml_close_file(file);
1550
1551         debug_print(_("done.\n"));
1552 }
1553
1554 static void addressbook_get_tree(XMLFile *file, GtkCTreeNode *node,
1555                                  const gchar *folder_tag)
1556 {
1557         AddressFolder *folder;
1558
1559         g_return_if_fail(node != NULL);
1560
1561         folder = g_new(AddressFolder, 1);
1562         ADDRESS_OBJECT(folder)->type = ADDR_FOLDER;
1563         folder->name = g_strdup(folder_tag);
1564         folder->items = NULL;
1565         gtk_ctree_node_set_row_data(GTK_CTREE(addrbook.ctree), node, folder);
1566
1567         if (file) {
1568                 if (xml_parse_next_tag(file) < 0 ||
1569                     xml_compare_tag(file, folder_tag) == FALSE) {
1570                         g_warning("Invalid addressbook data\n");
1571                         return;
1572                 }
1573         }
1574
1575         if (file) addressbook_add_objs(file, node);
1576 }
1577
1578 static void addressbook_add_objs(XMLFile *file, GtkCTreeNode *node)
1579 {
1580         GList *attr;
1581         guint prev_level;
1582         GtkCTreeNode *new_node;
1583
1584         for (;;) {
1585                 prev_level = file->level;
1586                 if (xml_parse_next_tag(file) < 0) return;
1587                 if (file->level < prev_level) return;
1588
1589                 if (xml_compare_tag(file, "group")) {
1590                         AddressGroup *group;
1591
1592                         group = g_new(AddressGroup, 1);
1593                         ADDRESS_OBJECT_TYPE(group) = ADDR_GROUP;
1594                         attr = xml_get_current_tag_attr(file);
1595                         if (attr)
1596                                 group->name = g_strdup(((XMLAttr *)attr->data)->value);
1597                         else
1598                                 group->name = NULL;
1599                         group->items = NULL;
1600
1601                         new_node = addressbook_add_object
1602                                 (node, ADDRESS_OBJECT(group));
1603
1604                         addressbook_add_objs(file, new_node);
1605                 } else if (xml_compare_tag(file, "folder")) {
1606                         AddressFolder *folder;
1607
1608                         folder = g_new(AddressFolder, 1);
1609                         ADDRESS_OBJECT_TYPE(folder) = ADDR_FOLDER;
1610                         attr = xml_get_current_tag_attr(file);
1611                         if (attr)
1612                                 folder->name = g_strdup(((XMLAttr *)attr->data)->value);
1613                         else
1614                                 folder->name = NULL;
1615                         folder->items = NULL;
1616
1617                         new_node = addressbook_add_object
1618                                 (node, ADDRESS_OBJECT(folder));
1619
1620                         addressbook_add_objs(file, new_node);
1621                 } else if (xml_compare_tag(file, "item")) {
1622                         AddressItem *item;
1623
1624                         item = addressbook_parse_item(file);
1625                         if (!item) return;
1626                         new_node = addressbook_add_object
1627                                 (node, ADDRESS_OBJECT(item));
1628                 } else {
1629                         g_warning("Invalid tag\n");
1630                         return;
1631                 }
1632
1633                 if (!new_node) return;
1634         }
1635 }
1636
1637 static GtkCTreeNode *addressbook_add_object(GtkCTreeNode *node,
1638                                             AddressObject *obj)
1639 {
1640         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1641         GtkCTreeNode *added;
1642         AddressObject *pobj;
1643
1644         g_return_val_if_fail(node != NULL, NULL);
1645         g_return_val_if_fail(obj  != NULL, NULL);
1646
1647         pobj = gtk_ctree_node_get_row_data(ctree, node);
1648         g_return_val_if_fail(pobj != NULL, NULL);
1649         if (pobj->type == ADDR_ITEM) {
1650                 g_warning("Parent object mustn't be an item.\n");
1651                 return NULL;
1652         }
1653         if (pobj->type == ADDR_FOLDER &&
1654             (obj->type == ADDR_GROUP || obj->type == ADDR_FOLDER))
1655                 gtk_ctree_expand(ctree, node);
1656
1657         if (obj->type == ADDR_GROUP) {
1658                 AddressGroup *group = ADDRESS_GROUP(obj);
1659
1660                 if (pobj->type != ADDR_FOLDER) {
1661                         g_warning("Group can't be added in another group.\n");
1662                         return NULL;
1663                 }
1664
1665                 added = gtk_ctree_insert_node(ctree, node, NULL,
1666                                               &group->name, FOLDER_SPACING,
1667                                               groupxpm, groupxpmmask,
1668                                               groupxpm, groupxpmmask,
1669                                               TRUE, FALSE);
1670                 gtk_ctree_node_set_row_data(ctree, added, obj);
1671         } else if (obj->type == ADDR_FOLDER) {
1672                 AddressFolder *folder = ADDRESS_FOLDER(obj);
1673
1674                 if (pobj->type != ADDR_FOLDER) {
1675                         g_warning("Group can't contain folder.\n");
1676                         return NULL;
1677                 }
1678
1679                 added = gtk_ctree_insert_node(ctree, node, NULL,
1680                                               &folder->name, FOLDER_SPACING,
1681                                               folderxpm, folderxpmmask,
1682                                               folderopenxpm, folderopenxpmmask,
1683                                               FALSE, FALSE);
1684                 gtk_ctree_node_set_row_data(ctree, added, obj);
1685         } else {
1686                 added = node;
1687         }
1688
1689         if (obj->type == ADDR_GROUP || obj->type == ADDR_ITEM) {
1690                 if (pobj->type == ADDR_GROUP) {
1691                         AddressGroup *group = ADDRESS_GROUP(pobj);
1692
1693                         group->items = g_list_append(group->items, obj);
1694                 } else if (pobj->type == ADDR_FOLDER) {
1695                         AddressFolder *folder = ADDRESS_FOLDER(pobj);
1696
1697                         folder->items = g_list_append(folder->items, obj);
1698                 }
1699         }
1700
1701         gtk_ctree_sort_node(ctree, node);
1702
1703         return added;
1704 }
1705
1706 static void addressbook_delete_object(AddressObject *obj)
1707 {
1708         if (!obj) return;
1709
1710         if (obj->type == ADDR_ITEM) {
1711                 AddressItem *item = ADDRESS_ITEM(obj);
1712
1713                 g_free(item->name);
1714                 g_free(item->address);
1715                 g_free(item->remarks);
1716                 g_free(item);
1717         } else if (obj->type == ADDR_GROUP) {
1718                 AddressGroup *group = ADDRESS_GROUP(obj);
1719
1720                 g_free(group->name);
1721                 while (group->items != NULL) {
1722                         addressbook_delete_object
1723                                 (ADDRESS_OBJECT(group->items->data));
1724                         group->items = g_list_remove(group->items,
1725                                                      group->items->data);
1726                 }
1727                 g_free(group);
1728         } else if (obj->type == ADDR_FOLDER) {
1729                 AddressFolder *folder = ADDRESS_FOLDER(obj);
1730
1731                 g_free(folder->name);
1732                 while (folder->items != NULL) {
1733                         addressbook_delete_object
1734                                 (ADDRESS_OBJECT(folder->items->data));
1735                         folder->items = g_list_remove(folder->items,
1736                                                       folder->items->data);
1737                 }
1738                 g_free(folder);
1739         }
1740 }
1741
1742 static AddressObject *addressbook_find_object_by_name(GtkCTreeNode *node,
1743                                                       const gchar *name)
1744 {
1745         GtkCTree *ctree = GTK_CTREE(addrbook.ctree);
1746         AddressObject *obj;
1747         GList *found;
1748
1749         g_return_val_if_fail(node != NULL, NULL);
1750
1751         obj = gtk_ctree_node_get_row_data(ctree, node);
1752         g_return_val_if_fail(obj != NULL, NULL);
1753
1754         if (obj->type == ADDR_GROUP) {
1755                 AddressGroup *group = ADDRESS_GROUP(obj);
1756
1757                 found = g_list_find_custom(group->items, (gpointer)name,
1758                                            addressbook_obj_name_compare);
1759                 if (found) return ADDRESS_OBJECT(found->data);
1760         } else if (obj->type == ADDR_FOLDER) {
1761                 AddressFolder *folder = ADDRESS_FOLDER(obj);
1762
1763                 found = g_list_find_custom(folder->items, (gpointer)name,
1764                                            addressbook_obj_name_compare);
1765                 if (found) return ADDRESS_OBJECT(found->data);
1766         } else if (obj->type == ADDR_ITEM) {
1767                 if (!addressbook_obj_name_compare(obj, name)) return obj;
1768         }
1769
1770         return NULL;
1771 }
1772
1773 #define PARSE_ITEM_ERROR() \
1774 { \
1775         g_warning("addressbook_parse_item(): Parse error\n"); \
1776         g_free(item->name); \
1777         g_free(item->address); \
1778         g_free(item->remarks); \
1779         g_free(item); \
1780         return NULL; \
1781 }
1782
1783 static AddressItem *addressbook_parse_item(XMLFile *file)
1784 {
1785         gchar *element;
1786         AddressItem *item;
1787         guint level;
1788
1789         item = g_new0(AddressItem, 1);
1790         ADDRESS_OBJECT(item)->type = ADDR_ITEM;
1791
1792         level = file->level;
1793
1794         while (xml_parse_next_tag(file) == 0) {
1795                 if (file->level < level) return item;
1796                 if (file->level == level) break;
1797
1798                 element = xml_get_element(file);
1799
1800                 if (xml_compare_tag(file, "name")) {
1801                         item->name = element;
1802                 } else if (xml_compare_tag(file, "address")) {
1803                         item->address = element;
1804                 } else if (xml_compare_tag(file, "remarks")) {
1805                         item->remarks = element;
1806                 }
1807
1808                 if (xml_parse_next_tag(file) < 0) break;
1809                 if (file->level != level) break;
1810         }
1811
1812         PARSE_ITEM_ERROR();
1813 }
1814
1815 void addressbook_export_to_file(void)
1816 {
1817         PrefFile *pfile;
1818         gchar *path;
1819
1820         if (!addrbook.ctree) return;
1821
1822         debug_print(_("Exporting addressbook to file..."));
1823
1824         path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ADDRESS_BOOK, NULL);
1825         if ((pfile = prefs_write_open(path)) == NULL) {
1826                 g_free(path);
1827                 return;
1828         }
1829         g_free(path);
1830
1831         fprintf(pfile->fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
1832                 conv_get_current_charset_str());
1833         fputs("<addressbook>\n\n", pfile->fp);
1834
1835         addressbook_xml_recursive_write(NULL, pfile->fp);
1836
1837         fputs("</addressbook>\n", pfile->fp);
1838
1839         if (prefs_write_close(pfile) < 0) {
1840                 g_warning(_("failed to write addressbook data.\n"));
1841                 return;
1842         }
1843
1844         debug_print(_("done.\n"));
1845 }
1846
1847 /* Most part of this function was taken from gtk_ctree_pre_recursive() and
1848    gtk_ctree_post_recursive(). */
1849 static void addressbook_xml_recursive_write(GtkCTreeNode *node, FILE *fp)
1850 {
1851         GtkCTreeNode *work;
1852         GtkCTreeNode *tmp;
1853
1854         if (node) {
1855                 work = GTK_CTREE_ROW(node)->children;
1856                 addressbook_node_write_begin(node, fp);
1857         } else
1858                 work = GTK_CTREE_NODE(GTK_CLIST(addrbook.ctree)->row_list);
1859
1860         while (work) {
1861                 tmp = GTK_CTREE_ROW(work)->sibling;
1862                 addressbook_xml_recursive_write(work, fp);
1863                 work = tmp;
1864         }
1865
1866         if (node)
1867                 addressbook_node_write_end(node, fp);
1868 }
1869
1870 static void addressbook_node_write_begin(GtkCTreeNode *node, FILE *fp)
1871 {
1872         AddressObject *obj;
1873
1874         obj = gtk_ctree_node_get_row_data(GTK_CTREE(addrbook.ctree), node);
1875         g_return_if_fail(obj != NULL);
1876
1877         if (obj->type == ADDR_FOLDER) {
1878                 AddressFolder *folder = ADDRESS_FOLDER(obj);
1879
1880                 if (GTK_CTREE_ROW(node)->level == 1) {
1881                         fprintf(fp, "<%s>\n", folder->name);
1882                 } else {
1883                         tab_indent_out(fp, GTK_CTREE_ROW(node)->level - 1);
1884                         fputs("<folder name=\"", fp);
1885                         xml_file_put_escape_str(fp, folder->name);
1886                         fputs("\">\n", fp);
1887                 }
1888         } else if (obj->type == ADDR_GROUP) {
1889                 AddressGroup *group = ADDRESS_GROUP(obj);
1890
1891                 tab_indent_out(fp, GTK_CTREE_ROW(node)->level - 1);
1892                 fputs("<group name=\"", fp);
1893                 xml_file_put_escape_str(fp, group->name);
1894                 fputs("\">\n", fp);
1895         }
1896 }
1897
1898 static void addressbook_node_write_end(GtkCTreeNode *node, FILE *fp)
1899 {
1900         AddressObject *obj;
1901
1902         obj = gtk_ctree_node_get_row_data(GTK_CTREE(addrbook.ctree), node);
1903         g_return_if_fail(obj != NULL);
1904
1905         if (obj->type == ADDR_FOLDER) {
1906                 AddressFolder *folder = ADDRESS_FOLDER(obj);
1907
1908                 addressbook_write_items(fp, folder->items,
1909                                         GTK_CTREE_ROW(node)->level);
1910
1911                 if (GTK_CTREE_ROW(node)->level == 1) {
1912                         fprintf(fp, "</%s>\n\n", folder->name);
1913                 } else {
1914                         tab_indent_out(fp, GTK_CTREE_ROW(node)->level - 1);
1915                         fputs("</folder>\n", fp);
1916                 }
1917         } else if (obj->type == ADDR_GROUP) {
1918                 AddressGroup *group = ADDRESS_GROUP(obj);
1919
1920                 addressbook_write_items(fp, group->items,
1921                                         GTK_CTREE_ROW(node)->level);
1922
1923                 tab_indent_out(fp, GTK_CTREE_ROW(node)->level - 1);
1924                 fputs("</group>\n", fp);
1925         }
1926 }
1927
1928 static void addressbook_write_items(FILE *fp, GList *items, guint level)
1929 {
1930         AddressItem *item;
1931
1932         for (; items != NULL; items = items->next) {
1933                 if (ADDRESS_OBJECT_TYPE(items->data) == ADDR_ITEM) {
1934                         item = ADDRESS_ITEM(items->data);
1935
1936                         tab_indent_out(fp, level);
1937                         fputs("<item>\n", fp);
1938
1939                         tab_indent_out(fp, level + 1);
1940                         fputs("<name>", fp);
1941                         xml_file_put_escape_str(fp, item->name);
1942                         fputs("</name>\n", fp);
1943
1944                         tab_indent_out(fp, level + 1);
1945                         fputs("<address>", fp);
1946                         xml_file_put_escape_str(fp, item->address);
1947                         fputs("</address>\n", fp);
1948
1949                         tab_indent_out(fp, level + 1);
1950                         fputs("<remarks>", fp);
1951                         xml_file_put_escape_str(fp, item->remarks);
1952                         fputs("</remarks>\n", fp);
1953
1954                         tab_indent_out(fp, level);
1955                         fputs("</item>\n", fp);
1956                 }
1957         }
1958 }
1959
1960 static void tab_indent_out(FILE *fp, guint level)
1961 {
1962         gint i;
1963
1964         for (i = 0; i < level; i++)
1965                 fputs("    ", fp);
1966 }
1967
1968 static void key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
1969 {
1970         if (event && event->keyval == GDK_Escape)
1971                 addressbook_close();
1972 }
1973
1974 static gint addressbook_list_compare_func(GtkCList *clist,
1975                                           gconstpointer ptr1,
1976                                           gconstpointer ptr2)
1977 {
1978         AddressObject *obj1 = ((GtkCListRow *)ptr1)->data;
1979         AddressObject *obj2 = ((GtkCListRow *)ptr2)->data;
1980         gchar *name1, *name2;
1981
1982         if (obj1) {
1983                 if (obj1->type == ADDR_ITEM)
1984                         name1 = ADDRESS_ITEM(obj1)->name;
1985                 else if (obj1->type == ADDR_GROUP)
1986                         name1 = ADDRESS_GROUP(obj1)->name;
1987                 else if (obj1->type == ADDR_FOLDER)
1988                         name1 = ADDRESS_FOLDER(obj1)->name;
1989                 else
1990                         name1 = NULL;
1991         } else
1992                 name1 = NULL;
1993
1994         if (obj2) {
1995                 if (obj2->type == ADDR_ITEM)
1996                         name2 = ADDRESS_ITEM(obj2)->name;
1997                 else if (obj2->type == ADDR_GROUP)
1998                         name2 = ADDRESS_GROUP(obj2)->name;
1999                 else if (obj2->type == ADDR_FOLDER)
2000                         name2 = ADDRESS_FOLDER(obj2)->name;
2001                 else
2002                         name2 = NULL;
2003         } else
2004                 name2 = NULL;
2005
2006         if (!name1)
2007                 return (name2 != NULL);
2008         if (!name2)
2009                 return -1;
2010
2011         return strcasecmp(name1, name2);
2012 }
2013
2014 static gint addressbook_obj_name_compare(gconstpointer a, gconstpointer b)
2015 {
2016         const AddressObject *obj = a;
2017         const gchar *name = b;
2018
2019         if (!obj || !name) return -1;
2020
2021         if (obj->type == ADDR_GROUP) {
2022                 AddressGroup *group = ADDRESS_GROUP(obj);
2023                 if (!group->name)
2024                         return -1;
2025                 else
2026                         return strcasecmp(group->name, name);
2027         } else if (obj->type == ADDR_FOLDER) {
2028                 AddressFolder *folder = ADDRESS_FOLDER(obj);
2029                 if (!folder->name)
2030                         return -1;
2031                 else
2032                         return strcasecmp(folder->name, name);
2033         } else if (obj->type == ADDR_ITEM) {
2034                 AddressItem *item = ADDRESS_ITEM(obj);
2035                 if (!item->name)
2036                         return -1;
2037                 else
2038                         return strcasecmp(item->name, name);
2039         } else
2040                 return -1;
2041 }
2042
2043 /***/
2044
2045 typedef struct {
2046         gboolean                init;                   /* if FALSE should init jump buffer */
2047         GtkCTreeNode   *node_found;             /* match (can be used to backtrack folders)  */
2048         AddressObject  *addr_found;             /* match */
2049         int                             level;                  /* current recursion level (0 is root level) */
2050         jmp_buf                 jumper;                 /* jump buffer */
2051 } FindObject;
2052
2053 typedef struct {
2054         FindObject              ancestor;
2055         const gchar        *groupname;
2056 } FindGroup;
2057
2058 typedef struct {
2059         FindObject              ancestor;
2060         const gchar    *name;
2061         const gchar    *address;
2062 } FindAddress;
2063
2064 typedef struct {
2065         FindObject              ancestor;
2066         GList              *grouplist;
2067 } FindAllGroups;
2068
2069 typedef gboolean (*ADDRESSBOOK_TRAVERSE_FUNC)(AddressObject *node, gpointer data);
2070
2071 /***/
2072
2073 static gboolean traverse_find_group_by_name(AddressObject *ao, FindGroup *find)
2074 {
2075         AddressFolder *folder;
2076         AddressGroup  *group;
2077
2078         /* a group or folder: both are groups */
2079         if (ADDRESS_OBJECT_TYPE(ao) == ADDR_GROUP) {
2080                 group = ADDRESS_GROUP(ao);
2081                 if (0 == g_strcasecmp(group->name, find->groupname)) {
2082                         return TRUE;
2083                 }
2084         }
2085         else if (ADDRESS_OBJECT_TYPE(ao) == ADDR_FOLDER) {
2086                 folder = ADDRESS_FOLDER(ao);
2087                 if (0 == g_strcasecmp(folder->name, find->groupname)) {
2088                         return TRUE;
2089                 }
2090         }
2091         return FALSE;
2092 }
2093
2094 static gboolean traverse_find_name_email(AddressObject *ao, FindAddress *find)
2095 {
2096         AddressItem *item;
2097         if (ADDRESS_OBJECT_TYPE(ao) == ADDR_ITEM) {
2098                 gboolean nmatch = FALSE, amatch = FALSE;
2099                 item = ADDRESS_ITEM(ao);
2100                 /* conditions:
2101                  * o only match at the first characters in item strings 
2102                  * o match either name or address */
2103                 if (find->name && item->name) {
2104                         nmatch = item->name == strcasestr(item->name, find->name);
2105                 }
2106                 if (find->address && item->address) {
2107                         amatch = item->address == strcasestr(item->address, find->address);
2108                 }
2109                 return nmatch || amatch;
2110         }
2111         return FALSE;
2112 }
2113
2114 static gboolean traverse_find_all_groups(AddressObject *ao, FindAllGroups *find)
2115 {
2116         /* NOTE: added strings come from the address book. should perhaps 
2117          * strdup() them, especially if the address book is invalidated */
2118         if (ADDRESS_OBJECT_TYPE(ao) == ADDR_FOLDER) {
2119                 AddressFolder *folder = ADDRESS_FOLDER(ao);
2120                 find->grouplist = g_list_insert_sorted(find->grouplist, (gpointer) folder->name, (GCompareFunc) g_strcasecmp);
2121         }
2122         else if (ADDRESS_OBJECT_TYPE(ao) == ADDR_GROUP) {
2123                 AddressGroup *group = ADDRESS_GROUP(ao);
2124                 find->grouplist = g_list_insert_sorted(find->grouplist, (gpointer) group->name, (GCompareFunc) g_strcasecmp);
2125         }
2126         return FALSE;
2127 }
2128
2129 /* addressbook_traverse() - traverses all address objects stored in the address book. 
2130  * for some reason gtkctree's recursive tree functions don't allow a premature return, 
2131  * which is what we need if we need to enumerate the tree and check for a condition 
2132  * and then skipping other nodes. */ 
2133 static AddressObject *addressbook_traverse(GtkCTreeNode *node, ADDRESSBOOK_TRAVERSE_FUNC func, FindObject *data, int level)
2134 {
2135         GtkCTreeNode  *current, *tmp;
2136         AddressObject *ao;
2137
2138         if (data->init == FALSE) {
2139                 /* initialize non-local exit */
2140                 data->init  = TRUE;
2141                 data->level = 0;
2142                 /* HANDLE NON-LOCAL EXIT */
2143                 if (setjmp(data->jumper)) {
2144                         return data->addr_found;
2145                 }
2146         }
2147
2148         /* actual recursive code */
2149         if (!node) {
2150                 current = GTK_CTREE_NODE(GTK_CLIST(addrbook.ctree)->row_list);
2151         }
2152         else {
2153                 current = node;
2154         }
2155
2156         while (current) {
2157                 tmp = GTK_CTREE_ROW(current)->sibling;
2158                 ao = (AddressObject *) gtk_ctree_node_get_row_data(GTK_CTREE(addrbook.ctree), current);
2159                 if (ao) {
2160                         GList *next;
2161
2162                         next = (ADDRESS_OBJECT_TYPE(ao) == ADDR_FOLDER) ? 
2163                                    g_list_first(((ADDRESS_FOLDER(ao))->items)) :
2164                                    (ADDRESS_OBJECT_TYPE(ao)  == ADDR_GROUP) ?
2165                                    g_list_first(((ADDRESS_GROUP(ao))->items))  : NULL;
2166
2167                         while (ao) {
2168                                 /* NOTE: first iteration of the root calls callback for the tree 
2169                                  * node, other iterations call callback for the address book items */
2170                                 if (func(ao, data)) {
2171                                         /* unwind */
2172                                         data->node_found = current;
2173                                         data->addr_found = ao;
2174                                         longjmp(data->jumper, 1);
2175                                 }
2176                                 /* ctree node only stores folders and groups. now descend into
2177                                  * address object data, searching for address items. */
2178                                 for ( ; next && ADDRESS_OBJECT_TYPE((next->data)) != ADDR_ITEM
2179                                           ; next = g_list_next(next))
2180                                         ;                       
2181                                 ao   = next ? (AddressObject *) next->data : NULL;
2182                                 next = next ? g_list_next(next) : NULL;
2183                         }                               
2184                 }
2185                 /* check the children (if level permits) */
2186                 if (level == -1 || data->level < level) {
2187                         current = GTK_CTREE_ROW(current)->children;
2188                         if (current) {
2189                                 data->level++;
2190                                 addressbook_traverse(current, func, data, level);
2191                                 data->level--;
2192                         }                       
2193                 }                       
2194                 /* check the siblings */
2195                 current = tmp;
2196         }
2197         return NULL;
2198 }
2199
2200 static GtkCTreeNode *addressbook_get_group_node(const gchar *name)
2201 {
2202         FindGroup fg = { { FALSE, NULL, NULL }, NULL };
2203         fg.groupname = name;
2204         addressbook_traverse(NULL, (void *)traverse_find_group_by_name, (FindObject *)&fg, -1);
2205         return fg.ancestor.node_found;
2206 }
2207
2208 static void addressbook_free_item(AddressItem *item)
2209 {
2210         if (item) {
2211                 if (item->name) g_free(item->name);
2212                 if (item->address) g_free(item->address);
2213                 if (item->remarks) g_free(item->remarks);
2214                 g_free(item);
2215         }
2216 }
2217
2218 static AddressItem *addressbook_alloc_item(const gchar *name, const gchar *address, const gchar *remarks)
2219 {
2220         AddressItem *item = g_new0(AddressItem, 1);
2221         
2222         if (item) {
2223                 item->obj.type = ADDR_ITEM;
2224                 if (item->name = g_strdup(name))
2225                         if (item->address = g_strdup(address)) {
2226                                 if (remarks) {
2227                                         item->remarks = g_strdup(remarks);
2228                                 }
2229                                 return item;
2230                         }
2231         }
2232         addressbook_free_item(item);
2233         return NULL;
2234 }
2235
2236 /***/
2237
2238 /* public provisional API */
2239
2240 /* addressbook_access() - should be called before using any of the following apis. it
2241  * reloads the address book. */
2242 void addressbook_access(void)
2243 {
2244         log_message("accessing address book\n");
2245         if (!addrbook.window) {
2246                 addressbook_create(FALSE);
2247                 addressbook_read_file();
2248                 addrbook.open_folder = TRUE;
2249                 gtk_ctree_select(GTK_CTREE(addrbook.ctree), GTK_CTREE_NODE(GTK_CLIST(addrbook.ctree)->row_list));
2250         } 
2251 }
2252
2253 /* addressbook_unaccess() - should only be called after changing the address book's
2254  * contents */
2255 void addressbook_unaccess(void)
2256 {
2257         log_message("unaccessing address book\n");
2258         addressbook_export_to_file();
2259         invalidate_address_completion();
2260 }
2261
2262 const gchar *addressbook_get_personal_folder_name(void)
2263 {
2264         return _("Personal addresses"); /* human readable */
2265 }
2266
2267 const gchar *addressbook_get_common_folder_name(void)
2268 {
2269         return _("Common addresses"); /* human readable */
2270 }
2271
2272 /* addressbook_find_group_by_name() - finds a group (folder or group) by
2273  * its name */
2274 AddressObject *addressbook_find_group_by_name(const gchar *name)
2275 {
2276         FindGroup          fg = { { FALSE, NULL, NULL } };
2277         AddressObject *ao;
2278
2279         /* initialize obj members */
2280         fg.groupname = name;
2281         ao = addressbook_traverse(NULL, 
2282                                                           (ADDRESSBOOK_TRAVERSE_FUNC)traverse_find_group_by_name, 
2283                                                           (FindObject *)&fg, -1);
2284         return ao;
2285 }
2286
2287 /* addressbook_find_contact() - finds an address item by either name or address
2288  * or both. the comparison is done on the first few characters of the strings */
2289 AddressObject *addressbook_find_contact(const gchar *name, const gchar *address)
2290 {
2291         FindAddress   fa = { { FALSE, NULL, NULL } };
2292         AddressObject *ao;
2293
2294         fa.name = name;
2295         fa.address = address;
2296         ao = addressbook_traverse(NULL, (ADDRESSBOOK_TRAVERSE_FUNC)traverse_find_name_email,
2297                                                           (FindObject *)&fa, -1);
2298         return ao;                                                        
2299 }
2300
2301 /* addressbook_get_group_list() - returns a list of strings with group names (both
2302  * groups and folders). free the list using g_list_free(). note that another
2303  * call may invalidate the returned list */
2304 GList *addressbook_get_group_list(void)
2305 {
2306         FindAllGroups fag = { { FALSE, NULL, NULL }, NULL };
2307         addressbook_traverse(NULL, (ADDRESSBOOK_TRAVERSE_FUNC)traverse_find_all_groups,
2308                                                  (FindObject *)&fag, -1);
2309         return fag.grouplist;
2310 }
2311
2312 /* addressbook_add_contact() - adds a contact to the address book. returns 1
2313  * if succesful else error */
2314 gint addressbook_add_contact(const gchar *group, const gchar *name, const gchar *address,
2315                                                          const gchar *remarks) 
2316 {
2317         GtkCTreeNode *node;
2318         AddressItem *item;
2319         FindAddress  fa = { { FALSE, NULL, NULL } };
2320
2321         /* a healthy mix of hiro's and my code */
2322         if (name == NULL || strlen(name) == 0
2323         ||  address == NULL || strlen(address) == 0
2324         ||  group == NULL || strlen(group) == 0) {
2325                 return __LINE__;
2326         }
2327         node = addressbook_get_group_node(group);
2328         if (!node) {
2329                 return __LINE__;
2330         }
2331
2332         /* check if it's already in this group */
2333         fa.name = name;
2334         fa.address = address;
2335
2336         if (addressbook_traverse(node, (gpointer)traverse_find_name_email, (gpointer)&fa, 0)) {
2337                 log_message("address <%s> already in %s\n", address, group);
2338                 return __LINE__;
2339         }
2340
2341         item = addressbook_alloc_item(name, address, remarks);
2342         if (!item) {
2343                 return __LINE__;
2344         }
2345
2346         if (!addressbook_add_object(node, (AddressObject *)item)) {
2347                 addressbook_free_item(item);
2348                 return __LINE__;
2349         }
2350
2351         /* make sure it's updated if selected */
2352         log_message("updating addressbook widgets\n");
2353         addrbook.open_folder = TRUE;
2354         gtk_ctree_select(GTK_CTREE(addrbook.ctree), addrbook.opened);
2355
2356         /* not saved yet. only after unaccessing the address book */
2357         return 0;
2358 }
2359
2360 static void group_object_data_destroy(gchar *group)
2361 {
2362         if (group) {
2363                 g_free(group);
2364         }               
2365 }
2366
2367 /***/
2368
2369 typedef struct {
2370         gchar *name;
2371         gchar *address;
2372         gchar *remarks;
2373 } ContactInfo;
2374
2375 static void addressbook_destroy_contact(ContactInfo *ci)
2376 {
2377         g_return_if_fail(ci != NULL);
2378         if (ci->name) g_free(ci->name);
2379         if (ci->address) g_free(ci->address);
2380         if (ci->remarks) g_free(ci->remarks);
2381         g_free(ci);
2382 }
2383
2384 static ContactInfo *addressbook_new_contact(const gchar *name, const gchar *address, const gchar *remarks)
2385 {
2386         ContactInfo *ci = g_new0(ContactInfo, 1);
2387         
2388         g_return_val_if_fail(ci != NULL, NULL);
2389         g_return_val_if_fail(address != NULL, NULL); /* address should be valid */
2390         ci->name    = name ? g_strdup(name) : NULL;
2391         ci->address = g_strdup(address);
2392         ci->remarks = remarks ? g_strdup(remarks) : NULL;
2393         if (NULL == ci->address) {
2394                 addressbook_destroy_contact(ci);
2395                 ci = NULL;
2396         }
2397         return ci;
2398 }
2399
2400 static void addressbook_group_menu_selected(GtkMenuItem *menuitem,
2401                                                                                         ContactInfo *data)
2402 {
2403         const gchar *group_name = (const gchar *) gtk_object_get_data(GTK_OBJECT(menuitem),
2404                                                                                                                                   "group_name");
2405                                                                                                            
2406         if (!group_name) {
2407                 g_warning("%s(%d) - invalid group name\n", __FILE__, __LINE__);
2408                 return ;
2409         }
2410         g_return_if_fail(group_name != NULL); 
2411
2412         g_message("selected group %s from menu\n", group_name);
2413         g_message("selected %s <%s>\n", data->name ? data->name : data->address, data->address);
2414
2415         addressbook_access();
2416         addressbook_add_contact(group_name, data->name ? data->name : data->address, 
2417                                                         data->address, data->remarks ? data->remarks : data->address);
2418         addressbook_unaccess();
2419
2420         g_free(data);
2421 }
2422
2423 /* addressbook_add_contact_by_meny() - launches menu with group items. submenu may be
2424  * the menu item in the parent menu, or NULL for a normal right-click context menu */
2425 gboolean addressbook_add_contact_by_menu(GtkWidget   *submenu,
2426                                                                                  const gchar *name, 
2427                                                                                  const gchar *address, 
2428                                                                                  const gchar *remarks)
2429 {
2430         GtkWidget       *menu, *menuitem;
2431         GList           *groups, *tmp;
2432         ContactInfo *ci;
2433
2434         ci = addressbook_new_contact(name, address, remarks);
2435         g_return_val_if_fail(ci != NULL, FALSE);
2436
2437         addressbook_access();
2438         groups = addressbook_get_group_list();
2439         g_return_val_if_fail(groups != NULL, (addressbook_destroy_contact(ci), FALSE));
2440         
2441         menu = gtk_menu_new();
2442         g_return_val_if_fail(menu != NULL, (g_list_free(groups), addressbook_destroy_contact(ci), FALSE));
2443
2444         /* add groups to menu */
2445         for (tmp = g_list_first(groups); tmp != NULL; tmp = g_list_next(tmp)) {
2446                 const gchar *display_name;
2447                 gchar           *original_name = (gchar *) tmp->data;
2448                 
2449                 if (!g_strcasecmp(original_name, "personal_address")) {
2450                         display_name = addressbook_get_personal_folder_name();
2451                 }
2452                 else if (!g_strcasecmp(original_name, "common_address")) {
2453                         display_name = addressbook_get_common_folder_name();
2454                 }
2455                 else {
2456                         display_name = original_name;
2457                 }
2458
2459                 original_name = g_strdup(original_name);
2460                 menuitem = gtk_menu_item_new_with_label(display_name);
2461                 /* register the duplicated string pointer as object data,
2462                  * so we get the opportunity to free it */
2463                 gtk_object_set_data_full(GTK_OBJECT(menuitem), "group_name", 
2464                                                                  original_name, 
2465                                                                  (GtkDestroyNotify) group_object_data_destroy);
2466                 gtk_signal_connect(GTK_OBJECT(menuitem), "activate", 
2467                                                    GTK_SIGNAL_FUNC(addressbook_group_menu_selected),
2468                                                    (gpointer)(ci));
2469                 gtk_menu_append(GTK_MENU(menu), menuitem);
2470                 gtk_widget_show(menuitem);
2471         }
2472
2473         gtk_widget_show(menu);
2474
2475         if (submenu) {
2476                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(submenu), menu);
2477                 gtk_widget_set_sensitive(GTK_WIDGET(submenu), TRUE);
2478         } 
2479         else {
2480                 gtk_widget_grab_focus(GTK_WIDGET(menu));
2481                 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 1, GDK_CURRENT_TIME);
2482         }
2483
2484         if (groups) g_list_free(groups);
2485         return TRUE;
2486 }