2007-03-04 [colin] 2.8.0cvs13
[claws.git] / src / addr_compl.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  *
4  * Copyright (C) 2000-2007 by Alfons Hoogervorst & The Claws Mail Team.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkmain.h>
30 #include <gtk/gtkwindow.h>
31 #include <gtk/gtkentry.h>
32 #include <gtk/gtkeditable.h>
33 #include <gtk/gtkscrolledwindow.h>
34 #include <gtk/gtktreeview.h>
35 #include <gtk/gtktreemodel.h>
36 #include <gtk/gtkliststore.h>
37
38 #include <string.h>
39 #include <ctype.h>
40 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
41 #  include <wchar.h>
42 #  include <wctype.h>
43 #endif
44
45 #include "addrindex.h"
46 #include "addr_compl.h"
47 #include "utils.h"
48 #include "prefs_common.h"
49 #include "claws.h"
50 #include <pthread.h>
51
52 /*!
53  *\brief        For the GtkListStore
54  */
55 enum {
56         ADDR_COMPL_ICON,
57         ADDR_COMPL_ADDRESS,
58         ADDR_COMPL_ISGROUP,
59         ADDR_COMPL_GROUPLIST,
60         N_ADDR_COMPL_COLUMNS
61 };
62
63 /*
64  * How it works:
65  *
66  * The address book is read into memory. We set up an address list
67  * containing all address book entries. Next we make the completion
68  * list, which contains all the completable strings, and store a
69  * reference to the address entry it belongs to.
70  * After calling the g_completion_complete(), we get a reference
71  * to a valid email address.  
72  *
73  * Completion is very simplified. We never complete on another prefix,
74  * i.e. we neglect the next smallest possible prefix for the current
75  * completion cache. This is simply done so we might break up the
76  * addresses a little more (e.g. break up alfons@proteus.demon.nl into
77  * something like alfons, proteus, demon, nl; and then completing on
78  * any of those words).
79  */ 
80         
81 /**
82  * address_entry - structure which refers to the original address entry in the
83  * address book .
84  */
85 typedef struct
86 {
87         gchar *name;
88         gchar *address;
89         GList *grp_emails;
90 } address_entry;
91
92 /**
93  * completion_entry - structure used to complete addresses, with a reference
94  * the the real address information.
95  */
96 typedef struct
97 {
98         gchar           *string; /* string to complete */
99         address_entry   *ref;    /* address the string belongs to  */
100 } completion_entry;
101
102 /*******************************************************************************/
103
104 static gint         g_ref_count;        /* list ref count */
105 static GList       *g_completion_list = NULL;   /* list of strings to be checked */
106 static GList       *g_address_list = NULL;      /* address storage */
107 static GCompletion *g_completion;       /* completion object */
108
109 static GHashTable *_groupAddresses_ = NULL;
110 static gboolean _allowCommas_ = TRUE;
111
112 /* To allow for continuing completion we have to keep track of the state
113  * using the following variables. No need to create a context object. */
114
115 static gint         g_completion_count;         /* nr of addresses incl. the prefix */
116 static gint         g_completion_next;          /* next prev address */
117 static GSList      *g_completion_addresses;     /* unique addresses found in the
118                                                    completion cache. */
119 static gchar       *g_completion_prefix;        /* last prefix. (this is cached here
120                                                  * because the prefix passed to g_completion
121                                                  * is g_strdown()'ed */
122
123 static gchar *completion_folder_path = NULL;
124
125 /*******************************************************************************/
126
127 /*
128  * Define the structure of the completion window.
129  */
130 typedef struct _CompletionWindow CompletionWindow;
131 struct _CompletionWindow {
132         gint      listCount;
133         gchar     *searchTerm;
134         GtkWidget *window;
135         GtkWidget *entry;
136         GtkWidget *list_view;
137
138         gboolean   in_mouse;    /*!< mouse press pending... */
139         gboolean   destroying;  /*!< destruction in progress */
140 };
141
142 static GtkListStore *addr_compl_create_store    (void);
143
144 static GtkWidget *addr_compl_list_view_create   (CompletionWindow *window);
145
146 static void addr_compl_create_list_view_columns (GtkWidget *list_view);
147
148 static gboolean list_view_button_press          (GtkWidget *widget, 
149                                                  GdkEventButton *event,
150                                                  CompletionWindow *window);
151
152 static gboolean list_view_button_release        (GtkWidget *widget, 
153                                                  GdkEventButton *event,
154                                                  CompletionWindow *window);
155
156 static gboolean addr_compl_selected             (GtkTreeSelection *selector,
157                                                  GtkTreeModel *model, 
158                                                  GtkTreePath *path,
159                                                  gboolean currently_selected,
160                                                  gpointer data);
161                                                  
162 static gboolean addr_compl_defer_select_destruct(CompletionWindow *window);
163
164 /**
165  * Function used by GTK to find the string data to be used for completion.
166  * \param data Pointer to data being processed.
167  */
168 static gchar *completion_func(gpointer data)
169 {
170         g_return_val_if_fail(data != NULL, NULL);
171
172         return ((completion_entry *)data)->string;
173
174
175 /**
176  * Initialize all completion index data.
177  */
178 static void init_all(void)
179 {
180         g_completion = g_completion_new(completion_func);
181         g_return_if_fail(g_completion != NULL);
182 }
183
184 static void free_all_addresses(void)
185 {
186         GList *walk;
187         if (!g_address_list)
188                 return;
189         walk = g_address_list;
190         for (; walk != NULL; walk = g_list_next(walk)) {
191                 address_entry *ae = (address_entry *) walk->data;
192                 g_free(ae->name);
193                 g_free(ae->address);
194                 g_list_free(ae->grp_emails);
195                 g_free(walk->data);
196         }
197         g_list_free(g_address_list);
198         g_address_list = NULL;
199         if (_groupAddresses_)
200                 g_hash_table_destroy(_groupAddresses_);
201         _groupAddresses_ = NULL;
202 }
203
204 static void clear_completion_cache(void);
205 static void free_completion_list(void)
206 {
207         GList *walk;
208         if (!g_completion_list)
209                 return;
210         
211         clear_completion_cache();
212         if (g_completion)
213                 g_completion_clear_items(g_completion);
214
215         walk = g_list_first(g_completion_list);
216         for (; walk != NULL; walk = g_list_next(walk)) {
217                 completion_entry *ce = (completion_entry *) walk->data;
218                 g_free(ce->string);
219                 g_free(walk->data);
220         }
221         g_list_free(g_completion_list);
222         g_completion_list = NULL;
223 }
224 /**
225  * Free up all completion index data.
226  */
227 static void free_all(void)
228 {
229         free_completion_list(); 
230         free_all_addresses();   
231         g_completion_free(g_completion);
232         g_completion = NULL;
233 }
234
235 /**
236  * Append specified address entry to the index.
237  * \param str Index string value.
238  * \param ae  Entry containing address data.
239  */
240 static void add_address1(const char *str, address_entry *ae)
241 {
242         completion_entry *ce1;
243         ce1 = g_new0(completion_entry, 1),
244         /* GCompletion list is case sensitive */
245         ce1->string = g_utf8_strdown(str, -1);
246         ce1->ref = ae;
247
248         g_completion_list = g_list_prepend(g_completion_list, ce1);
249 }
250
251 /**
252  * Adds address to the completion list. This function looks complicated, but
253  * it's only allocation checks. Each value will be included in the index.
254  * \param name    Recipient name.
255  * \param address EMail address.
256  * \param alias   Alias to append.
257  * \param grp_emails the emails in case of a group. List should be freed later, 
258  * but not its strings
259  * \return <code>0</code> if entry appended successfully, or <code>-1</code>
260  *         if failure.
261  */
262 static gint add_address(const gchar *name, const gchar *address, 
263                         const gchar *nick, const gchar *alias, GList *grp_emails)
264 {
265         address_entry    *ae;
266         gboolean is_group = FALSE;
267
268         if (!name || !address) {
269                 if (!address && !nick && !alias && grp_emails) {
270                         is_group = TRUE;
271                 } else
272                         return -1;
273         }
274
275         ae = g_new0(address_entry, 1);
276
277         g_return_val_if_fail(ae != NULL, -1);
278
279         ae->name    = g_strdup(name);
280         ae->address = g_strdup(address);                
281         ae->grp_emails = grp_emails;
282         g_address_list = g_list_prepend(g_address_list, ae);
283
284         add_address1(name, ae);
285         if (address != NULL && *address != '\0')
286                 add_address1(address, ae);
287         
288         if (nick != NULL && *nick != '\0')
289                 add_address1(nick, ae);
290         
291         if ( alias != NULL && *alias != '\0') {
292                 add_address1(alias, ae);
293         }
294
295         return 0;
296 }
297
298 /**
299  * Read address book, creating all entries in the completion index.
300  */ 
301 static void read_address_book(gchar *folderpath) {
302         free_all_addresses();
303         free_completion_list();
304
305         addrindex_load_completion( add_address, folderpath );
306         g_address_list = g_list_reverse(g_address_list);
307         g_completion_list = g_list_reverse(g_completion_list);
308         /* merge the completion entry list into g_completion */
309         if (g_completion_list) {
310                 g_completion_add_items(g_completion, g_completion_list);
311                 if (debug_get_mode())
312                         debug_print("read %d items in %s\n", 
313                                 g_list_length(g_completion_list),
314                                 folderpath?folderpath:"(null)");
315         }
316 }
317
318 /**
319  * Test whether there is a completion pending.
320  * \return <code>TRUE</code> if pending.
321  */
322 static gboolean is_completion_pending(void)
323 {
324         /* check if completion pending, i.e. we might satisfy a request for the next
325          * or previous address */
326          return g_completion_count;
327 }
328
329 /**
330  * Clear the completion cache.
331  */
332 static void clear_completion_cache(void)
333 {
334         if (is_completion_pending()) {
335                 g_free(g_completion_prefix);
336
337                 if (g_completion_addresses) {
338                         g_slist_free(g_completion_addresses);
339                         g_completion_addresses = NULL;
340                 }
341
342                 g_completion_count = g_completion_next = 0;
343         }
344 }
345
346 /**
347  * Prepare completion index. This function should be called prior to attempting
348  * address completion.
349  * \return The number of addresses in the completion list.
350  */
351 gint start_address_completion(gchar *folderpath)
352 {
353         gboolean different_book = FALSE;
354         clear_completion_cache();
355
356         if (strcmp2(completion_folder_path,folderpath))
357                 different_book = TRUE;
358
359         g_free(completion_folder_path);
360         if (folderpath != NULL)
361                 completion_folder_path = g_strdup(folderpath);
362         else
363                 completion_folder_path = NULL;
364
365         if (!g_ref_count) {
366                 init_all();
367                 /* open the address book */
368                 read_address_book(folderpath);
369         } else if (different_book)
370                 read_address_book(folderpath);
371
372         g_ref_count++;
373         debug_print("start_address_completion(%s) ref count %d\n",
374                                 folderpath, g_ref_count);
375
376         return g_list_length(g_completion_list);
377 }
378
379 /**
380  * Retrieve a possible address (or a part) from an entry box. To make life
381  * easier, we only look at the last valid address component; address
382  * completion only works at the last string component in the entry box.
383  *
384  * \param entry Address entry field.
385  * \param start_pos Address of start position of address.
386  * \return Possible address.
387  */
388 static gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
389 {
390         const gchar *edit_text, *p;
391         gint cur_pos;
392         gboolean in_quote = FALSE;
393         gboolean in_bracket = FALSE;
394         gchar *str;
395
396         edit_text = gtk_entry_get_text(entry);
397         if (edit_text == NULL) return NULL;
398
399         cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
400
401         /* scan for a separator. doesn't matter if walk points at null byte. */
402         for (p = g_utf8_offset_to_pointer(edit_text, cur_pos);
403              p > edit_text;
404              p = g_utf8_prev_char(p)) {
405                 if (*p == '"') {
406                         in_quote = TRUE;
407                 } else if (!in_quote) {
408                         if (!in_bracket && *p == ',') {
409                                 break;
410                         } else if (*p == '<')
411                                 in_bracket = TRUE;
412                         else if (*p == '>')
413                                 in_bracket = FALSE;
414                 }
415         }
416
417         /* have something valid */
418         if (g_utf8_strlen(p, -1) == 0)
419                 return NULL;
420
421 #define IS_VALID_CHAR(x) \
422         (g_ascii_isalnum(x) || (x) == '"' || (x) == '<' || (((unsigned char)(x)) > 0x7f))
423
424         /* now scan back until we hit a valid character */
425         for (; *p && !IS_VALID_CHAR(*p); p = g_utf8_next_char(p))
426                 ;
427
428 #undef IS_VALID_CHAR
429
430         if (g_utf8_strlen(p, -1) == 0)
431                 return NULL;
432
433         if (start_pos) *start_pos = g_utf8_pointer_to_offset(edit_text, p);
434
435         str = g_strdup(p);
436
437         return str;
438
439
440 static gchar *get_complete_address_from_name_email(const gchar *name, const gchar *email)
441 {
442         gchar *address = NULL;
443         if (!name || name[0] == '\0')
444                 address = g_strdup_printf("<%s>", email);
445         else if (strchr_with_skip_quote(name, '"', ','))
446                 address = g_strdup_printf
447                         ("\"%s\" <%s>", name, email);
448         else
449                 address = g_strdup_printf
450                         ("%s <%s>", name, email);
451         return address;
452 }
453
454 /**
455  * Replace an incompleted address with a completed one.
456  * \param entry     Address entry field.
457  * \param newtext   New text.
458  * \param start_pos Insertion point in entry field.
459  */
460 static void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
461                              gint start_pos, gboolean is_group, GList *grp_emails)
462 {
463         if (!newtext) return;
464         gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1);
465         if (!is_group) {
466                 gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext),
467                                  &start_pos);
468         } else {
469                 gchar *addresses = NULL;
470                 GList *cur = grp_emails;
471                 for (; cur; cur = cur->next) {
472                         gchar *tmp;
473                         ItemEMail *email = (ItemEMail *)cur->data;
474                         ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(email);
475                         
476                         gchar *addr = get_complete_address_from_name_email(
477                                 ADDRITEM_NAME(person), email->address);
478                         if (addresses)
479                                 tmp = g_strdup_printf("%s, %s", addresses, addr);
480                         else
481                                 tmp = g_strdup_printf("%s", addr);
482                         g_free(addr);
483                         g_free(addresses);
484                         addresses = tmp;
485                 }
486                 gtk_editable_insert_text(GTK_EDITABLE(entry), addresses, strlen(addresses),
487                                  &start_pos);
488                 g_free(addresses);
489         }
490         gtk_editable_set_position(GTK_EDITABLE(entry), -1);
491 }
492
493 /**
494  * Attempt to complete an address, and returns the number of addresses found.
495  * Use <code>get_complete_address()</code> to get an entry from the index.
496  *
497  * \param  str Search string to find.
498  * \return Zero if no match was found, otherwise the number of addresses; the
499  *         original prefix (search string) will appear at index 0. 
500  */
501 guint complete_address(const gchar *str)
502 {
503         GList *result = NULL;
504         gchar *d = NULL;
505         guint  count = 0;
506         guint  cpl = 0;
507         completion_entry *ce = NULL;
508
509         g_return_val_if_fail(str != NULL, 0);
510
511         /* g_completion is case sensitive */
512         d = g_utf8_strdown(str, -1);
513
514         clear_completion_cache();
515         g_completion_prefix = g_strdup(str);
516
517         result = g_completion_complete(g_completion, d, NULL);
518
519         count = g_list_length(result);
520         if (count) {
521                 /* create list with unique addresses  */
522                 for (cpl = 0, result = g_list_first(result);
523                      result != NULL;
524                      result = g_list_next(result)) {
525                         ce = (completion_entry *)(result->data);
526                         if (NULL == g_slist_find(g_completion_addresses,
527                                                  ce->ref)) {
528                                 cpl++;
529                                 g_completion_addresses =
530                                         g_slist_append(g_completion_addresses,
531                                                        ce->ref);
532                         }
533                 }
534                 count = cpl + 1;        /* index 0 is the original prefix */
535                 g_completion_next = 1;  /* we start at the first completed one */
536         } else {
537                 g_free(g_completion_prefix);
538                 g_completion_prefix = NULL;
539         }
540
541         g_completion_count = count;
542
543         g_free(d);
544
545         return count;
546 }
547
548 /**
549  * Return a complete address from the index.
550  * \param index Index of entry that was found (by the previous call to
551  *              <code>complete_address()</code>
552  * \return Completed address string; this should be freed when done.
553  */
554 gchar *get_complete_address(gint index)
555 {
556         const address_entry *p;
557         gchar *address = NULL;
558
559         if (index < g_completion_count) {
560                 if (index == 0)
561                         address = g_strdup(g_completion_prefix);
562                 else {
563                         /* get something from the unique addresses */
564                         p = (address_entry *)g_slist_nth_data
565                                 (g_completion_addresses, index - 1);
566                         if (p != NULL && p->address != NULL) {
567                                 address = get_complete_address_from_name_email(p->name, p->address);
568                         } else if (p != NULL && p->address == NULL && p->name != NULL) {
569                                 /* that's a group */
570                                 address = g_strdup_printf("%s (%s) <!--___group___-->", p->name, _("Group"));
571                                 if (!_groupAddresses_) {
572                                         _groupAddresses_ = g_hash_table_new(NULL, g_direct_equal);
573                                 }
574                                 if (!g_hash_table_lookup(_groupAddresses_, GINT_TO_POINTER(g_str_hash(address)))) {
575                                         g_hash_table_insert(_groupAddresses_, GINT_TO_POINTER(g_str_hash(address)), p->grp_emails);
576
577                                 }
578                         }
579                 }
580         }
581
582         return address;
583 }
584
585 /**
586  * Return the next complete address match from the completion index.
587  * \return Completed address string; this should be freed when done.
588  */
589 static gchar *get_next_complete_address(void)
590 {
591         if (is_completion_pending()) {
592                 gchar *res;
593
594                 res = get_complete_address(g_completion_next);
595                 g_completion_next += 1;
596                 if (g_completion_next >= g_completion_count)
597                         g_completion_next = 0;
598
599                 return res;
600         } else
601                 return NULL;
602 }
603
604 /**
605  * Return a count of the completed matches in the completion index.
606  * \return Number of matched entries.
607  */
608 static guint get_completion_count(void)
609 {
610         if (is_completion_pending())
611                 return g_completion_count;
612         else
613                 return 0;
614 }
615
616 /**
617  * Invalidate address completion index. This function should be called whenever
618  * the address book changes. This forces data to be read into the completion
619  * data.
620  * \return Number of entries in index.
621  */
622 gint invalidate_address_completion(void)
623 {
624         if (g_ref_count) {
625                 /* simply the same as start_address_completion() */
626                 debug_print("Invalidation request for address completion\n");
627                 read_address_book(completion_folder_path);
628                 clear_completion_cache();
629         }
630
631         return g_list_length(g_completion_list);
632 }
633
634 /**
635  * Finished with completion index. This function should be called after
636  * matching addresses.
637  * \return Reference count.
638  */
639 gint end_address_completion(void)
640 {
641         gboolean different_folder = FALSE;
642         clear_completion_cache();
643
644         /* reset the folderpath to NULL */
645         if (completion_folder_path) {
646                 g_free(completion_folder_path);
647                 completion_folder_path = NULL;
648                 different_folder = TRUE;
649         }
650         if (0 == --g_ref_count)
651                 free_all();
652
653         debug_print("end_address_completion ref count %d\n", g_ref_count);
654         if (g_ref_count && different_folder) {
655                 debug_print("still ref'd, different folder\n");
656                 invalidate_address_completion();
657         }
658
659         return g_ref_count; 
660 }
661
662 /**
663  * Completion window.
664  */
665 static CompletionWindow *_compWindow_ = NULL;
666
667 /**
668  * Mutex to protect callback from multiple threads.
669  */
670 static pthread_mutex_t _completionMutex_ = PTHREAD_MUTEX_INITIALIZER;
671
672 /**
673  * Completion queue list.
674  */
675 static GList *_displayQueue_ = NULL;
676 /**
677  * Current query ID.
678  */
679 static gint _queryID_ = 0;
680
681 /**
682  * Completion idle ID.
683  */
684 static guint _completionIdleID_ = 0;
685
686 /*
687  * address completion entry ui. the ui (completion list was inspired by galeon's
688  * auto completion list). remaining things powered by claws's completion engine.
689  */
690
691 #define ENTRY_DATA_TAB_HOOK     "tab_hook"      /* used to lookup entry */
692 #define ENTRY_DATA_ALLOW_COMMAS "allowcommas"   /* used to know whether to present groups */
693
694 static void address_completion_mainwindow_set_focus     (GtkWindow   *window,
695                                                          GtkWidget   *widget,
696                                                          gpointer     data);
697 static gboolean address_completion_entry_key_pressed    (GtkEntry    *entry,
698                                                          GdkEventKey *ev,
699                                                          gpointer     data);
700 static gboolean address_completion_complete_address_in_entry
701                                                         (GtkEntry    *entry,
702                                                          gboolean     next);
703 static void address_completion_create_completion_window (GtkEntry    *entry);
704
705 static gboolean completion_window_button_press
706                                         (GtkWidget       *widget,
707                                          GdkEventButton  *event,
708                                          CompletionWindow *compWin );
709
710 static gboolean completion_window_key_press
711                                         (GtkWidget       *widget,
712                                          GdkEventKey     *event,
713                                          CompletionWindow *compWin );
714 static void address_completion_create_completion_window( GtkEntry *entry_ );
715
716 /**
717  * Create a completion window object.
718  * \return Initialized completion window.
719  */
720 static CompletionWindow *addrcompl_create_window( void ) {
721         CompletionWindow *cw;
722
723         cw = g_new0( CompletionWindow, 1 );
724         cw->listCount = 0;
725         cw->searchTerm = NULL;
726         cw->window = NULL;
727         cw->entry = NULL;
728         cw->list_view = NULL;
729         cw->in_mouse = FALSE;
730         cw->destroying = FALSE;
731
732         return cw;      
733 }
734
735 /**
736  * Destroy completion window.
737  * \param cw Window to destroy.
738  */
739 static void addrcompl_destroy_window( CompletionWindow *cw ) {
740         /* Stop all searches currently in progress */
741         addrindex_stop_search( _queryID_ );
742
743         /* Remove idler function... or application may not terminate */
744         if( _completionIdleID_ != 0 ) {
745                 g_source_remove( _completionIdleID_ );
746                 _completionIdleID_ = 0;
747         }
748
749         /* Now destroy window */        
750         if( cw ) {
751                 /* Clear references to widgets */
752                 cw->entry = NULL;
753                 cw->list_view = NULL;
754
755                 /* Free objects */
756                 if( cw->window ) {
757                         gtk_widget_hide( cw->window );
758                         gtk_widget_destroy( cw->window );
759                 }
760                 cw->window = NULL;
761                 cw->destroying = FALSE;
762                 cw->in_mouse = FALSE;
763         }
764         
765 }
766
767 /**
768  * Free up completion window.
769  * \param cw Window to free.
770  */
771 static void addrcompl_free_window( CompletionWindow *cw ) {
772         if( cw ) {
773                 addrcompl_destroy_window( cw );
774
775                 g_free( cw->searchTerm );
776                 cw->searchTerm = NULL;
777
778                 /* Clear references */          
779                 cw->listCount = 0;
780
781                 /* Free object */               
782                 g_free( cw );
783         }
784 }
785
786 /**
787  * Advance selection to previous/next item in list.
788  * \param list_view List to process.
789  * \param forward Set to <i>TRUE</i> to select next or <i>FALSE</i> for
790  *                previous entry.
791  */
792 static void completion_window_advance_selection(GtkTreeView *list_view, gboolean forward)
793 {
794         GtkTreeSelection *selection;
795         GtkTreeIter iter;
796         GtkTreeModel *model;
797
798         g_return_if_fail(list_view != NULL);
799
800         selection = gtk_tree_view_get_selection(list_view);
801         if (!gtk_tree_selection_get_selected(selection, &model, &iter))
802                 return;
803
804         if (forward) { 
805                 forward = gtk_tree_model_iter_next(model, &iter);
806                 if (forward) 
807                         gtk_tree_selection_select_iter(selection, &iter);
808         } else {
809                 GtkTreePath *prev;
810
811                 prev = gtk_tree_model_get_path(model, &iter);
812                 if (!prev) 
813                         return;
814
815                 if (gtk_tree_path_prev(prev))
816                         gtk_tree_selection_select_path(selection, prev);
817                 
818                 gtk_tree_path_free(prev);
819         }
820 }
821
822 /**
823  * Resize window to accommodate maximum number of address entries.
824  * \param cw Completion window.
825  */
826 static void addrcompl_resize_window( CompletionWindow *cw ) {
827         GtkRequisition r;
828         gint x, y, width, height, depth;
829
830         /* Get current geometry of window */
831         gdk_window_get_geometry( cw->window->window, &x, &y, &width, &height, &depth );
832
833         gtk_widget_hide_all( cw->window );
834         gtk_widget_show_all( cw->window );
835         gtk_widget_size_request( cw->list_view, &r );
836
837         /* Adjust window height to available screen space */
838         if( ( y + r.height ) > gdk_screen_height() ) {
839                 gtk_window_set_resizable(GTK_WINDOW(cw->window), FALSE);
840                 gtk_widget_set_size_request( cw->window, width, gdk_screen_height() - y );
841         } else
842                 gtk_widget_set_size_request(cw->window, width, r.height);
843 }
844
845 static GdkPixbuf *group_pixbuf = NULL;
846 static GdkPixbuf *email_pixbuf = NULL;
847
848 /**
849  * Add an address the completion window address list.
850  * \param cw      Completion window.
851  * \param address Address to add.
852  */
853 static void addrcompl_add_entry( CompletionWindow *cw, gchar *address ) {
854         GtkListStore *store;
855         GtkTreeIter iter;
856         GtkTreeSelection *selection;
857         gboolean is_group = FALSE;
858         GList *grp_emails = NULL;
859         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(cw->list_view)));
860         GdkPixbuf *pixbuf;
861         
862         if (!group_pixbuf) {
863                 stock_pixbuf_gdk(cw->list_view, STOCK_PIXMAP_ADDR_TWO, &group_pixbuf);
864                 g_object_ref(G_OBJECT(group_pixbuf));
865         }
866         if (!email_pixbuf) {
867                 stock_pixbuf_gdk(cw->list_view, STOCK_PIXMAP_ADDR_ONE, &email_pixbuf);
868                 g_object_ref(G_OBJECT(email_pixbuf));
869         }
870         /* printf( "\t\tAdding :%s\n", address ); */
871         if (strstr(address, " <!--___group___-->")) {
872                 is_group = TRUE;
873                 if (_groupAddresses_)
874                         grp_emails = g_hash_table_lookup(_groupAddresses_, GINT_TO_POINTER(g_str_hash(address)));
875                 *(strstr(address, " <!--___group___-->")) = '\0';
876                 pixbuf = group_pixbuf;
877         } else if (strchr(address, '@') && strchr(address, '<') &&
878                    strchr(address, '>')) {
879                 pixbuf = email_pixbuf;
880         } else
881                 pixbuf = NULL;
882         
883         if (is_group && !_allowCommas_)
884                 return;
885         gtk_list_store_append(store, &iter);
886         gtk_list_store_set(store, &iter, 
887                                 ADDR_COMPL_ICON, pixbuf,
888                                 ADDR_COMPL_ADDRESS, address, 
889                                 ADDR_COMPL_ISGROUP, is_group, 
890                                 ADDR_COMPL_GROUPLIST, grp_emails,
891                                 -1);
892         cw->listCount++;
893
894         /* Resize window */
895         addrcompl_resize_window( cw );
896         gtk_grab_add( cw->window );
897
898         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cw->list_view));
899         gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
900
901         if( cw->listCount == 1 ) {
902                 /* Select first row for now */
903                 gtk_tree_selection_select_iter(selection, &iter);
904         }
905         else if( cw->listCount == 2 ) {
906                 gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
907                 /* Move off first row */
908                 gtk_tree_selection_select_iter(selection, &iter);
909         }
910 }
911
912 /**
913  * Completion idle function. This function is called by the main (UI) thread
914  * during UI idle time while an address search is in progress. Items from the
915  * display queue are processed and appended to the address list.
916  *
917  * \param data Target completion window to receive email addresses.
918  * \return <i>TRUE</i> to ensure that idle event do not get ignored.
919  */
920 static gboolean addrcompl_idle( gpointer data ) {
921         GList *node;
922         gchar *address;
923
924         /* Process all entries in display queue */
925         pthread_mutex_lock( & _completionMutex_ );
926         if( _displayQueue_ ) {
927                 node = _displayQueue_;
928                 while( node ) {
929                         address = node->data;
930                         /* printf( "address ::: %s :::\n", address ); */
931                         addrcompl_add_entry( _compWindow_, address );
932                         g_free( address );
933                         node = g_list_next( node );
934                 }
935                 g_list_free( _displayQueue_ );
936                 _displayQueue_ = NULL;
937         }
938         pthread_mutex_unlock( & _completionMutex_ );
939         claws_do_idle();
940
941         return TRUE;
942 }
943
944 /**
945  * Callback entry point. The background thread (if any) appends the address
946  * list to the display queue.
947  * \param sender     Sender of query.
948  * \param queryID    Query ID of search request.
949  * \param listEMail  List of zero of more email objects that met search
950  *                   criteria.
951  * \param data       Query data.
952  */
953 static gint addrcompl_callback_entry(
954         gpointer sender, gint queryID, GList *listEMail, gpointer data )
955 {
956         GList *node;
957         gchar *address;
958
959         /* printf( "addrcompl_callback_entry::queryID=%d\n", queryID ); */
960         pthread_mutex_lock( & _completionMutex_ );
961         if( queryID == _queryID_ ) {
962                 /* Append contents to end of display queue */
963                 node = listEMail;
964                 while( node ) {
965                         ItemEMail *email = node->data;
966
967                         address = addritem_format_email( email );
968                         /* printf( "\temail/address ::%s::\n", address ); */
969                         _displayQueue_ = g_list_append( _displayQueue_, address );
970                         node = g_list_next( node );
971                 }
972         }
973         g_list_free( listEMail );
974         pthread_mutex_unlock( & _completionMutex_ );
975
976         return 0;
977 }
978
979 /**
980  * Clear the display queue.
981  */
982 static void addrcompl_clear_queue( void ) {
983         /* Clear out display queue */
984         pthread_mutex_lock( & _completionMutex_ );
985
986         g_list_free( _displayQueue_ );
987         _displayQueue_ = NULL;
988
989         pthread_mutex_unlock( & _completionMutex_ );
990 }
991
992 /**
993  * Add a single address entry into the display queue.
994  * \param address Address to append.
995  */
996 static void addrcompl_add_queue( gchar *address ) {
997         pthread_mutex_lock( & _completionMutex_ );
998         _displayQueue_ = g_list_append( _displayQueue_, address );
999         pthread_mutex_unlock( & _completionMutex_ );
1000 }
1001
1002 /**
1003  * Load list with entries from local completion index.
1004  */
1005 static void addrcompl_load_local( void ) {
1006         guint count = 0;
1007
1008         for (count = 0; count < get_completion_count(); count++) {
1009                 gchar *address;
1010
1011                 address = get_complete_address( count );
1012                 /* printf( "\taddress ::%s::\n", address ); */
1013
1014                 /* Append contents to end of display queue */
1015                 addrcompl_add_queue( address );
1016         }
1017 }
1018
1019 /**
1020  * Start the search.
1021  */
1022 static void addrcompl_start_search( void ) {
1023         gchar *searchTerm;
1024
1025         searchTerm = g_strdup( _compWindow_->searchTerm );
1026
1027         /* Setup the search */
1028         _queryID_ = addrindex_setup_search(
1029                 searchTerm, NULL, addrcompl_callback_entry );
1030         g_free( searchTerm );
1031         /* printf( "addrcompl_start_search::queryID=%d\n", _queryID_ ); */
1032
1033         /* Load local stuff */
1034         addrcompl_load_local();
1035
1036         /* Sit back and wait until something happens */
1037         _completionIdleID_ =
1038                 g_idle_add( ( GtkFunction ) addrcompl_idle, NULL );
1039         /* printf( "addrindex_start_search::queryID=%d\n", _queryID_ ); */
1040
1041         addrindex_start_search( _queryID_ );
1042 }
1043
1044 /**
1045  * Apply the current selection in the list to the entry field. Focus is also
1046  * moved to the next widget so that Tab key works correctly.
1047  * \param list_view List to process.
1048  * \param entry Address entry field.
1049  * \param move_focus Move focus to the next widget ?
1050  */
1051 static void completion_window_apply_selection(GtkTreeView *list_view,
1052                                                 GtkEntry *entry,
1053                                                 gboolean move_focus)
1054 {
1055         gchar *address = NULL, *text = NULL;
1056         gint   cursor_pos;
1057         GtkWidget *parent;
1058         GtkTreeSelection *selection;
1059         GtkTreeModel *model;
1060         GtkTreeIter iter;
1061         gboolean is_group = FALSE;
1062         g_return_if_fail(list_view != NULL);
1063         g_return_if_fail(entry != NULL);
1064         GList *grp_emails = NULL;
1065
1066         selection = gtk_tree_view_get_selection(list_view);
1067         if (! gtk_tree_selection_get_selected(selection, &model, &iter))
1068                 return;
1069
1070         /* First remove the idler */
1071         if( _completionIdleID_ != 0 ) {
1072                 g_source_remove( _completionIdleID_ );
1073                 _completionIdleID_ = 0;
1074         }
1075
1076         /* Process selected item */
1077         gtk_tree_model_get(model, &iter, ADDR_COMPL_ADDRESS, &text, 
1078                                 ADDR_COMPL_ISGROUP, &is_group, 
1079                                 ADDR_COMPL_GROUPLIST, &grp_emails,
1080                                 -1);
1081
1082         address = get_address_from_edit(entry, &cursor_pos);
1083         g_free(address);
1084         replace_address_in_edit(entry, text, cursor_pos, is_group, grp_emails);
1085         g_free(text);
1086
1087         /* Move focus to next widget */
1088         parent = GTK_WIDGET(entry)->parent;
1089         if( parent && move_focus) {
1090                 gtk_widget_child_focus( parent, GTK_DIR_TAB_FORWARD );
1091         }
1092 }
1093
1094 /**
1095  * Start address completion. Should be called when creating the main window
1096  * containing address completion entries.
1097  * \param mainwindow Main window.
1098  */
1099 void address_completion_start(GtkWidget *mainwindow)
1100 {
1101         start_address_completion(NULL);
1102
1103         /* register focus change hook */
1104         g_signal_connect(G_OBJECT(mainwindow), "set_focus",
1105                          G_CALLBACK(address_completion_mainwindow_set_focus),
1106                          mainwindow);
1107 }
1108
1109 /**
1110  * Need unique data to make unregistering signal handler possible for the auto
1111  * completed entry.
1112  */
1113 #define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
1114
1115 /**
1116  * Register specified entry widget for address completion.
1117  * \param entry Address entry field.
1118  */
1119 void address_completion_register_entry(GtkEntry *entry, gboolean allow_commas)
1120 {
1121         g_return_if_fail(entry != NULL);
1122         g_return_if_fail(GTK_IS_ENTRY(entry));
1123
1124         /* add hooked property */
1125         g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
1126         g_object_set_data(G_OBJECT(entry), ENTRY_DATA_ALLOW_COMMAS, GINT_TO_POINTER(allow_commas));
1127
1128         /* add keypress event */
1129         g_signal_connect_closure
1130                 (G_OBJECT(entry), "key_press_event",
1131                  g_cclosure_new(G_CALLBACK(address_completion_entry_key_pressed),
1132                                 COMPLETION_UNIQUE_DATA,
1133                                 NULL),
1134                  FALSE); /* magic */
1135 }
1136
1137 /**
1138  * Unregister specified entry widget from address completion operations.
1139  * \param entry Address entry field.
1140  */
1141 void address_completion_unregister_entry(GtkEntry *entry)
1142 {
1143         GtkObject *entry_obj;
1144
1145         g_return_if_fail(entry != NULL);
1146         g_return_if_fail(GTK_IS_ENTRY(entry));
1147
1148         entry_obj = g_object_get_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
1149         g_return_if_fail(entry_obj);
1150         g_return_if_fail(G_OBJECT(entry_obj) == G_OBJECT(entry));
1151
1152         /* has the hooked property? */
1153         g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
1154
1155         /* remove the hook */
1156         g_signal_handlers_disconnect_by_func(G_OBJECT(entry), 
1157                         G_CALLBACK(address_completion_entry_key_pressed),
1158                         COMPLETION_UNIQUE_DATA);
1159 }
1160
1161 /**
1162  * End address completion. Should be called when main window with address
1163  * completion entries terminates. NOTE: this function assumes that it is
1164  * called upon destruction of the window.
1165  * \param mainwindow Main window.
1166  */
1167 void address_completion_end(GtkWidget *mainwindow)
1168 {
1169         /* if address_completion_end() is really called on closing the window,
1170          * we don't need to unregister the set_focus_cb */
1171         end_address_completion();
1172 }
1173
1174 /* if focus changes to another entry, then clear completion cache */
1175 static void address_completion_mainwindow_set_focus(GtkWindow *window,
1176                                                     GtkWidget *widget,
1177                                                     gpointer   data)
1178 {
1179         
1180         if (widget && GTK_IS_ENTRY(widget) &&
1181             g_object_get_data(G_OBJECT(widget), ENTRY_DATA_TAB_HOOK)) {
1182                 _allowCommas_ = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), ENTRY_DATA_ALLOW_COMMAS));
1183                 clear_completion_cache();
1184         }
1185 }
1186
1187 /**
1188  * Listener that watches for tab or other keystroke in address entry field.
1189  * \param entry Address entry field.
1190  * \param ev    Event object.
1191  * \param data  User data.
1192  * \return <i>TRUE</i>.
1193  */
1194 static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
1195                                                      GdkEventKey *ev,
1196                                                      gpointer     data)
1197 {
1198         if (ev->keyval == GDK_Tab) {
1199                 addrcompl_clear_queue();
1200                 _allowCommas_ = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(entry), ENTRY_DATA_ALLOW_COMMAS));
1201                 if( address_completion_complete_address_in_entry( entry, TRUE ) ) {
1202                         /* route a void character to the default handler */
1203                         /* this is a dirty hack; we're actually changing a key
1204                          * reported by the system. */
1205                         ev->keyval = GDK_AudibleBell_Enable;
1206                         ev->state &= ~GDK_SHIFT_MASK;
1207
1208                         /* Create window */                     
1209                         address_completion_create_completion_window(entry);
1210
1211                         /* Start remote queries */
1212                         addrcompl_start_search();
1213
1214                         return TRUE;
1215                 }
1216                 else {
1217                         /* old behaviour */
1218                 }
1219         } else if (ev->keyval == GDK_Shift_L
1220                 || ev->keyval == GDK_Shift_R
1221                 || ev->keyval == GDK_Control_L
1222                 || ev->keyval == GDK_Control_R
1223                 || ev->keyval == GDK_Caps_Lock
1224                 || ev->keyval == GDK_Shift_Lock
1225                 || ev->keyval == GDK_Meta_L
1226                 || ev->keyval == GDK_Meta_R
1227                 || ev->keyval == GDK_Alt_L
1228                 || ev->keyval == GDK_Alt_R) {
1229                 /* these buttons should not clear the cache... */
1230         } else
1231                 clear_completion_cache();
1232
1233         return FALSE;
1234 }
1235 /**
1236  * Initialize search term for address completion.
1237  * \param entry Address entry field.
1238  */
1239 static gboolean address_completion_complete_address_in_entry(GtkEntry *entry,
1240                                                              gboolean  next)
1241 {
1242         gint ncount, cursor_pos;
1243         gchar *searchTerm, *new = NULL;
1244
1245         g_return_val_if_fail(entry != NULL, FALSE);
1246
1247         if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE;
1248
1249         /* get an address component from the cursor */
1250         searchTerm = get_address_from_edit( entry, &cursor_pos );
1251         if( ! searchTerm ) return FALSE;
1252         /* printf( "search for :::%s:::\n", searchTerm ); */
1253
1254         /* Clear any existing search */
1255         g_free( _compWindow_->searchTerm );
1256         _compWindow_->searchTerm = g_strdup( searchTerm );
1257
1258         /* Perform search on local completion index */
1259         ncount = complete_address( searchTerm );
1260         if( 0 < ncount ) {
1261                 new = get_next_complete_address();
1262                 g_free( new );
1263         }
1264 #ifndef USE_LDAP
1265         /* Select the address if there is only one match */
1266         if (ncount == 2) {
1267                 /* Display selected address in entry field */           
1268                 gchar *addr = get_complete_address(1);
1269                 if (addr && !strstr(addr, " <!--___group___-->")) {
1270                         replace_address_in_edit(entry, addr, cursor_pos, FALSE, NULL);
1271                         /* Discard the window */
1272                         clear_completion_cache();
1273                 } 
1274                 g_free(addr);
1275         }
1276         /* Make sure that drop-down appears uniform! */
1277         else 
1278 #endif
1279         if( ncount == 0 ) {
1280                 addrcompl_add_queue( g_strdup( searchTerm ) );
1281         }
1282         g_free( searchTerm );
1283
1284         return TRUE;
1285 }
1286
1287 /**
1288  * Create new address completion window for specified entry.
1289  * \param entry_ Entry widget to associate with window.
1290  */
1291 static void address_completion_create_completion_window( GtkEntry *entry_ )
1292 {
1293         gint x, y, height, width, depth;
1294         GtkWidget *scroll, *list_view;
1295         GtkRequisition r;
1296         GtkWidget *window;
1297         GtkWidget *entry = GTK_WIDGET(entry_);
1298
1299         /* Create new window and list */
1300         window = gtk_window_new(GTK_WINDOW_POPUP);
1301         list_view  = addr_compl_list_view_create(_compWindow_);
1302
1303         /* Destroy any existing window */
1304         addrcompl_destroy_window( _compWindow_ );
1305
1306         /* Create new object */
1307         _compWindow_->window    = window;
1308         _compWindow_->entry     = entry;
1309         _compWindow_->list_view = list_view;
1310         _compWindow_->listCount = 0;
1311         _compWindow_->in_mouse  = FALSE;
1312
1313         scroll = gtk_scrolled_window_new(NULL, NULL);
1314         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
1315                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1316         gtk_container_add(GTK_CONTAINER(window), scroll);
1317         gtk_container_add(GTK_CONTAINER(scroll), list_view);
1318         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
1319                 GTK_SHADOW_OUT);
1320         /* Use entry widget to create initial window */
1321         gdk_window_get_geometry(entry->window, &x, &y, &width, &height, &depth);
1322         gdk_window_get_deskrelative_origin (entry->window, &x, &y);
1323         y += height;
1324         gtk_window_move(GTK_WINDOW(window), x, y);
1325
1326         /* Resize window to fit initial (empty) address list */
1327         gtk_widget_size_request( list_view, &r );
1328         gtk_widget_set_size_request( window, width, r.height );
1329         gtk_widget_show_all( window );
1330         gtk_widget_size_request( list_view, &r );
1331
1332         /* Setup handlers */
1333         g_signal_connect(G_OBJECT(list_view), "button_press_event",
1334                          G_CALLBACK(list_view_button_press),
1335                          _compWindow_);
1336                          
1337         g_signal_connect(G_OBJECT(list_view), "button_release_event",
1338                          G_CALLBACK(list_view_button_release),
1339                          _compWindow_);
1340         
1341         g_signal_connect(G_OBJECT(window),
1342                          "button-press-event",
1343                          G_CALLBACK(completion_window_button_press),
1344                          _compWindow_ );
1345         g_signal_connect(G_OBJECT(window),
1346                          "key-press-event",
1347                          G_CALLBACK(completion_window_key_press),
1348                          _compWindow_ );
1349         gdk_pointer_grab(window->window, TRUE,
1350                          GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
1351                          GDK_BUTTON_RELEASE_MASK,
1352                          NULL, NULL, GDK_CURRENT_TIME);
1353         gtk_grab_add( window );
1354
1355         /* XXX: GTK2 too??? 
1356          *
1357          * GTK1: this gets rid of the irritating focus rectangle that doesn't
1358          * follow the selection */
1359         GTK_WIDGET_UNSET_FLAGS(list_view, GTK_CAN_FOCUS);
1360 }
1361
1362 /**
1363  * Respond to button press in completion window. Check if mouse click is
1364  * anywhere outside the completion window. In that case the completion
1365  * window is destroyed, and the original searchTerm is restored.
1366  *
1367  * \param widget   Window object.
1368  * \param event    Event.
1369  * \param compWin  Reference to completion window.
1370  */
1371 static gboolean completion_window_button_press(GtkWidget *widget,
1372                                                GdkEventButton *event,
1373                                                CompletionWindow *compWin )
1374 {
1375         GtkWidget *event_widget, *entry;
1376         gchar *searchTerm;
1377         gint cursor_pos;
1378         gboolean restore = TRUE;
1379
1380         g_return_val_if_fail(compWin != NULL, FALSE);
1381
1382         entry = compWin->entry;
1383         g_return_val_if_fail(entry != NULL, FALSE);
1384
1385         /* Test where mouse was clicked */
1386         event_widget = gtk_get_event_widget((GdkEvent *)event);
1387         if (event_widget != widget) {
1388                 while (event_widget) {
1389                         if (event_widget == widget)
1390                                 return FALSE;
1391                         else if (event_widget == entry) {
1392                                 restore = FALSE;
1393                                 break;
1394                         }
1395                         event_widget = event_widget->parent;
1396                 }
1397         }
1398
1399         if (restore) {
1400                 /* Clicked outside of completion window - restore */
1401                 searchTerm = _compWindow_->searchTerm;
1402                 g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1403                 replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos, FALSE, NULL);
1404         }
1405
1406         clear_completion_cache();
1407         addrcompl_destroy_window( _compWindow_ );
1408
1409         return TRUE;
1410 }
1411
1412 /**
1413  * Respond to key press in completion window.
1414  * \param widget   Window object.
1415  * \param event    Event.
1416  * \param compWind Reference to completion window.
1417  */
1418 static gboolean completion_window_key_press(GtkWidget *widget,
1419                                             GdkEventKey *event,
1420                                             CompletionWindow *compWin )
1421 {
1422         GdkEventKey tmp_event;
1423         GtkWidget *entry;
1424         gchar *searchTerm;
1425         gint cursor_pos;
1426         GtkWidget *list_view;
1427         GtkWidget *parent;
1428         g_return_val_if_fail(compWin != NULL, FALSE);
1429
1430         entry = compWin->entry;
1431         list_view = compWin->list_view;
1432         g_return_val_if_fail(entry != NULL, FALSE);
1433
1434         /* allow keyboard navigation in the alternatives tree view */
1435         if (event->keyval == GDK_Up || event->keyval == GDK_Down ||
1436             event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down) {
1437                 completion_window_advance_selection
1438                         (GTK_TREE_VIEW(list_view),
1439                          event->keyval == GDK_Down ||
1440                          event->keyval == GDK_Page_Down ? TRUE : FALSE);
1441                 return FALSE;
1442         }               
1443
1444         /* make tab move to next field */
1445         if( event->keyval == GDK_Tab ) {
1446                 /* Reference to parent */
1447                 parent = GTK_WIDGET(entry)->parent;
1448
1449                 /* Discard the window */
1450                 clear_completion_cache();
1451                 addrcompl_destroy_window( _compWindow_ );
1452
1453                 /* Move focus to next widget */
1454                 if( parent ) {
1455                         gtk_widget_child_focus( parent, GTK_DIR_TAB_FORWARD );
1456                 }
1457                 return FALSE;
1458         }
1459
1460         /* make backtab move to previous field */
1461         if( event->keyval == GDK_ISO_Left_Tab ) {
1462                 /* Reference to parent */
1463                 parent = GTK_WIDGET(entry)->parent;
1464
1465                 /* Discard the window */
1466                 clear_completion_cache();
1467                 addrcompl_destroy_window( _compWindow_ );
1468
1469                 /* Move focus to previous widget */
1470                 if( parent ) {
1471                         gtk_widget_child_focus( parent, GTK_DIR_TAB_BACKWARD );
1472                 }
1473                 return FALSE;
1474         }
1475         _allowCommas_ = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(entry), ENTRY_DATA_ALLOW_COMMAS));
1476
1477         /* look for presses that accept the selection */
1478         if (event->keyval == GDK_Return || event->keyval == GDK_space
1479                         || (_allowCommas_ && event->keyval == GDK_comma)) {
1480                 /* User selected address with a key press */
1481
1482                 /* Display selected address in entry field */           
1483                 completion_window_apply_selection(
1484                         GTK_TREE_VIEW(list_view), GTK_ENTRY(entry),
1485                         event->keyval != GDK_comma);
1486
1487                 if (event->keyval == GDK_comma) {
1488                         gint pos = gtk_editable_get_position(GTK_EDITABLE(entry));
1489                         gtk_editable_insert_text(GTK_EDITABLE(entry), ", ", 2, &pos);
1490                         gtk_editable_set_position(GTK_EDITABLE(entry), pos + 1);
1491                 }
1492
1493                 /* Discard the window */
1494                 clear_completion_cache();
1495                 addrcompl_destroy_window( _compWindow_ );
1496                 return FALSE;
1497         }
1498
1499         /* key state keys should never be handled */
1500         if (event->keyval == GDK_Shift_L
1501                  || event->keyval == GDK_Shift_R
1502                  || event->keyval == GDK_Control_L
1503                  || event->keyval == GDK_Control_R
1504                  || event->keyval == GDK_Caps_Lock
1505                  || event->keyval == GDK_Shift_Lock
1506                  || event->keyval == GDK_Meta_L
1507                  || event->keyval == GDK_Meta_R
1508                  || event->keyval == GDK_Alt_L
1509                  || event->keyval == GDK_Alt_R) {
1510                 return FALSE;
1511         }
1512
1513         /* some other key, let's restore the searchTerm (orignal text) */
1514         searchTerm = _compWindow_->searchTerm;
1515         g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1516         replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos, FALSE, NULL);
1517
1518         /* make sure anything we typed comes in the edit box */
1519         tmp_event.type       = event->type;
1520         tmp_event.window     = entry->window;
1521         tmp_event.send_event = TRUE;
1522         tmp_event.time       = event->time;
1523         tmp_event.state      = event->state;
1524         tmp_event.keyval     = event->keyval;
1525         tmp_event.length     = event->length;
1526         tmp_event.string     = event->string;
1527         gtk_widget_event(entry, (GdkEvent *)&tmp_event);
1528
1529         /* and close the completion window */
1530         clear_completion_cache();
1531         addrcompl_destroy_window( _compWindow_ );
1532
1533         return TRUE;
1534 }
1535
1536 /*
1537  * ============================================================================
1538  * Publically accessible functions.
1539  * ============================================================================
1540  */
1541
1542 /**
1543  * Setup completion object.
1544  */
1545 void addrcompl_initialize( void ) {
1546         /* printf( "addrcompl_initialize...\n" ); */
1547         if( ! _compWindow_ ) {
1548                 _compWindow_ = addrcompl_create_window();
1549         }
1550         _queryID_ = 0;
1551         _completionIdleID_ = 0;
1552         /* printf( "addrcompl_initialize...done\n" ); */
1553 }
1554
1555 /**
1556  * Teardown completion object.
1557  */
1558 void addrcompl_teardown( void ) {
1559         /* printf( "addrcompl_teardown...\n" ); */
1560         addrcompl_free_window( _compWindow_ );
1561         _compWindow_ = NULL;
1562         if( _displayQueue_ ) {
1563                 g_list_free( _displayQueue_ );
1564         }
1565         _displayQueue_ = NULL;
1566         _completionIdleID_ = 0;
1567         /* printf( "addrcompl_teardown...done\n" ); */
1568 }
1569
1570 /*
1571  * tree view functions
1572  */
1573
1574 static GtkListStore *addr_compl_create_store(void)
1575 {
1576         return gtk_list_store_new(N_ADDR_COMPL_COLUMNS,
1577                                   GDK_TYPE_PIXBUF,
1578                                   G_TYPE_STRING,
1579                                   G_TYPE_BOOLEAN,
1580                                   G_TYPE_POINTER,
1581                                   -1);
1582 }
1583                                              
1584 static GtkWidget *addr_compl_list_view_create(CompletionWindow *window)
1585 {
1586         GtkTreeView *list_view;
1587         GtkTreeSelection *selector;
1588         GtkTreeModel *model;
1589
1590         model = GTK_TREE_MODEL(addr_compl_create_store());
1591         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
1592         g_object_unref(model);  
1593         
1594         gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
1595         gtk_tree_view_set_headers_visible(list_view, FALSE);
1596         
1597         selector = gtk_tree_view_get_selection(list_view);
1598         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
1599         gtk_tree_selection_set_select_function(selector, addr_compl_selected,
1600                                                window, NULL);
1601
1602         /* create the columns */
1603         addr_compl_create_list_view_columns(GTK_WIDGET(list_view));
1604
1605         return GTK_WIDGET(list_view);
1606 }
1607
1608 static void addr_compl_create_list_view_columns(GtkWidget *list_view)
1609 {
1610         GtkTreeViewColumn *column;
1611         GtkCellRenderer *renderer;
1612
1613         renderer = gtk_cell_renderer_pixbuf_new();
1614         column = gtk_tree_view_column_new_with_attributes
1615                 ("", renderer,
1616                  "pixbuf", ADDR_COMPL_ICON, NULL);
1617         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1618         renderer = gtk_cell_renderer_text_new();
1619         column = gtk_tree_view_column_new_with_attributes
1620                 ("", renderer, "text", ADDR_COMPL_ADDRESS, NULL);
1621         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1622 }
1623
1624 static gboolean list_view_button_press(GtkWidget *widget, GdkEventButton *event,
1625                                        CompletionWindow *window)
1626 {
1627         if (window && event && event->type == GDK_BUTTON_PRESS) {
1628                 window->in_mouse = TRUE;
1629         }
1630         return FALSE;
1631 }
1632
1633 static gboolean list_view_button_release(GtkWidget *widget, GdkEventButton *event,
1634                                          CompletionWindow *window)
1635 {
1636         if (window && event && event->type == GDK_BUTTON_RELEASE) {
1637                 window->in_mouse = FALSE;
1638         }
1639         return FALSE;
1640 }
1641
1642 static gboolean addr_compl_selected(GtkTreeSelection *selector,
1643                                     GtkTreeModel *model, 
1644                                     GtkTreePath *path,
1645                                     gboolean currently_selected,
1646                                     gpointer data)
1647 {
1648         CompletionWindow *window = data;
1649
1650         if (currently_selected)
1651                 return TRUE;
1652         
1653         if (!window->in_mouse)
1654                 return TRUE;
1655
1656         /* XXX: select the entry and kill window later... select is called before
1657          * any other mouse events handlers including the tree view internal one;
1658          * not using a time out would result in a crash. if this doesn't work
1659          * safely, maybe we should set variables when receiving button presses
1660          * in the tree view. */
1661         if (!window->destroying) {       
1662                 window->destroying = TRUE;       
1663                 g_idle_add((GSourceFunc) addr_compl_defer_select_destruct, data);
1664         }               
1665         
1666         return TRUE;
1667 }
1668
1669 static gboolean addr_compl_defer_select_destruct(CompletionWindow *window)
1670 {
1671         GtkEntry *entry = GTK_ENTRY(window->entry);
1672
1673         completion_window_apply_selection(GTK_TREE_VIEW(window->list_view), 
1674                                           entry, TRUE);
1675
1676         clear_completion_cache();
1677
1678         addrcompl_destroy_window(window);
1679         return FALSE;
1680 }
1681
1682
1683 /*
1684  * End of Source.
1685  */
1686