7e4ee19e966b2989b2ad34db01c6a9d0f39f474e
[claws.git] / src / addr_compl.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  *
4  * Copyright (c) 2000-2003 by Alfons Hoogervorst <alfons@proteus.demon.nl>
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24 #include "intl.h"
25 #include "defs.h"
26
27 #include <glib.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/gtkclist.h>
34 #include <gtk/gtkscrolledwindow.h>
35
36 #include <string.h>
37 #include <ctype.h>
38 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
39 #  include <wchar.h>
40 #  include <wctype.h>
41 #endif
42
43 #include "addressbook.h"
44 #include "addr_compl.h"
45 #include "utils.h"
46 #include "addritem.h"
47 #include "addrquery.h"
48 #include <pthread.h>
49
50 /* How it works:
51  *
52  * The address book is read into memory. We set up an address list
53  * containing all address book entries. Next we make the completion
54  * list, which contains all the completable strings, and store a
55  * reference to the address entry it belongs to.
56  * After calling the g_completion_complete(), we get a reference
57  * to a valid email address.  
58  *
59  * Completion is very simplified. We never complete on another searchTerm,
60  * i.e. we neglect the next smallest possible searchTerm for the current
61  * completion cache. This is simply done so we might break up the
62  * addresses a little more (e.g. break up alfons@proteus.demon.nl into
63  * something like alfons, proteus, demon, nl; and then completing on
64  * any of those words).
65  */
66
67 typedef struct _CompletionWindow CompletionWindow;
68 struct _CompletionWindow {
69         gint      listCount;
70         gchar     *searchTerm;
71         GtkWidget *window;
72         GtkWidget *entry;
73         GtkWidget *clist;
74 };
75
76 /**
77  * Current query ID.
78  */
79 static gint _queryID_ = 0;
80
81 /**
82  * Completion idle ID.
83  */
84 static gint _completionIdleID_ = 0;
85
86 /**
87  * Completion window.
88  */
89 static CompletionWindow *_compWindow_ = NULL;
90
91 /**
92  * Mutex to protect callback from multiple threads.
93  */
94 static pthread_mutex_t _completionMutex_ = PTHREAD_MUTEX_INITIALIZER;
95
96 /**
97  * Completion queue list.
98  */
99 static GList *_displayQueue_ = NULL;
100
101 /**
102  * Create a completion window object.
103  * \return Initialized completion window.
104  */
105 static CompletionWindow *addrcompl_create_window( void ) {
106         CompletionWindow *cw;
107
108         cw = g_new0( CompletionWindow, 1 );
109         cw->listCount = 0;
110         cw->searchTerm = NULL;
111         cw->window = NULL;
112         cw->entry = NULL;
113         cw->clist = NULL;
114
115         return cw;      
116 }
117
118 /**
119  * Destroy completion window.
120  * \param cw Window to destroy.
121  */
122 static void addrcompl_destroy_window( CompletionWindow *cw ) {
123         /* Remove idler function... or application may not terminate */
124         if( _completionIdleID_ != 0 ) {
125                 gtk_idle_remove( _completionIdleID_ );
126                 _completionIdleID_ = 0;
127         }
128
129         /* Now destroy window */        
130         if( cw ) {
131                 /* Clear references to widgets */
132                 cw->entry = NULL;
133                 cw->clist = NULL;
134
135                 /* Free objects */
136                 if( cw->window ) {
137                         gtk_widget_hide( cw->window );
138                         gtk_widget_destroy( cw->window );
139                 }
140                 cw->window = NULL;
141         }
142 }
143
144 /**
145  * Free up completion window.
146  * \param cw Window to free.
147  */
148 static void addrcompl_free_window( CompletionWindow *cw ) {
149         /* printf( "addrcompl_free_window...\n" ); */
150         if( cw ) {
151                 addrcompl_destroy_window( cw );
152
153                 g_free( cw->searchTerm );
154                 cw->searchTerm = NULL;
155
156                 /* Clear references */          
157                 cw->listCount = 0;
158
159                 /* Free object */               
160                 g_free( cw );
161         }
162         /* printf( "addrcompl_free_window...done\n" ); */
163 }
164
165 /**
166  * Retrieve a possible address (or a part) from an entry box. To make life
167  * easier, we only look at the last valid address component; address
168  * completion only works at the last string component in the entry box.
169  *
170  * \param entry Address entry field.
171  * \param start_pos Address of start position of address.
172  * \return Possible address.
173  */ 
174 static gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
175 {
176         const gchar *edit_text;
177         gint cur_pos;
178         wchar_t *wtext;
179         wchar_t *wp;
180         wchar_t rfc_mail_sep;
181         wchar_t quote;
182         wchar_t lt;
183         wchar_t gt;
184         gboolean in_quote = FALSE;
185         gboolean in_bracket = FALSE;
186         gchar *str;
187
188         if (mbtowc(&rfc_mail_sep, ",", 1) < 0) return NULL;
189         if (mbtowc(&quote, "\"", 1) < 0) return NULL;
190         if (mbtowc(&lt, "<", 1) < 0) return NULL;
191         if (mbtowc(&gt, ">", 1) < 0) return NULL;
192
193         edit_text = gtk_entry_get_text(entry);
194         if (edit_text == NULL) return NULL;
195
196         wtext = strdup_mbstowcs(edit_text);
197         g_return_val_if_fail(wtext != NULL, NULL);
198
199         cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
200
201         /* scan for a separator. doesn't matter if walk points at null byte. */
202         for (wp = wtext + cur_pos; wp > wtext; wp--) {
203                 if (*wp == quote)
204                         in_quote ^= TRUE;
205                 else if (!in_quote) {
206                         if (!in_bracket && *wp == rfc_mail_sep)
207                                 break;
208                         else if (*wp == gt)
209                                 in_bracket = TRUE;
210                         else if (*wp == lt)
211                                 in_bracket = FALSE;
212                 }
213         }
214
215         /* have something valid */
216         if (wcslen(wp) == 0) {
217                 g_free(wtext);
218                 return NULL;
219         }
220
221 #define IS_VALID_CHAR(x) \
222         (iswalnum(x) || (x) == quote || (x) == lt || ((x) > 0x7f))
223
224         /* now scan back until we hit a valid character */
225         for (; *wp && !IS_VALID_CHAR(*wp); wp++)
226                 ;
227
228 #undef IS_VALID_CHAR
229
230         if (wcslen(wp) == 0) {
231                 g_free(wtext);
232                 return NULL;
233         }
234
235         if (start_pos) *start_pos = wp - wtext;
236
237         str = strdup_wcstombs(wp);
238         g_free(wtext);
239
240         return str;
241 }
242
243 /**
244  * Replace text in entry field with specified address.
245  * \param entry     Address entry field.
246  * \param newtext   New text.
247  * \param start_pos Insertion point in entry field.
248  */
249 static void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
250                              gint start_pos)
251 {
252         if (!newtext) return;
253         gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1);
254         gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext),
255                                  &start_pos);
256         gtk_editable_set_position(GTK_EDITABLE(entry), -1);
257 }
258
259 /**
260  * Format E-Mail address.
261  * \param email EMail item to format.
262  * \return Formatted string. Should be freed after use.
263  */
264 static gchar *addrcompl_format_email( ItemEMail *email ) {
265         gchar *address;
266         gchar *name;
267         ItemPerson *person;
268
269         address = NULL;
270         name = NULL;
271         if( ADDRITEM_NAME( email ) ) {
272                 if( strlen( ADDRITEM_NAME( email ) ) ) {
273                         name = ADDRITEM_NAME( email );
274                 }
275         }
276         if( ! name ) {
277                 person = ( ItemPerson * ) ADDRITEM_PARENT( email );
278                 name = ADDRITEM_NAME( person );
279         }
280
281         if( name ) {
282                 if( strchr_with_skip_quote( name, '"', ',' ) ) {
283                         address = g_strdup_printf( "\"%s\" <%s>", name, email->address );
284                 }
285                 else {
286                         address = g_strdup_printf( "%s <%s>", name, email->address );
287                 }
288         }
289         else {
290                 address = g_strdup_printf( "%s", email->address );
291         }
292         return address;
293 }
294
295 /**
296  * Resize window to accommodate maximum number of address entries.
297  * \param cw Completion window.
298  */
299 static void addrcompl_resize_window( CompletionWindow *cw ) {
300         GtkRequisition r;
301         gint x, y, width, height, depth;
302
303         /* Get current geometry of window */
304         gdk_window_get_geometry( cw->window->window, &x, &y, &width, &height, &depth );
305
306         gtk_widget_size_request( cw->clist, &r );
307         gtk_widget_set_usize( cw->window, width, r.height );
308         gtk_widget_show_all( cw->window );
309         gtk_widget_size_request( cw->clist, &r );
310
311         /* Adjust window height to available screen space */
312         if( ( y + r.height ) > gdk_screen_height() ) {
313                 gtk_window_set_policy( GTK_WINDOW( cw->window ), TRUE, FALSE, FALSE );
314                 gtk_widget_set_usize( cw->window, width, gdk_screen_height() - y );
315         }
316 }
317
318 /**
319  * Add an address the completion window address list.
320  * \param cw Completion window.
321  * \param address Address to add.
322  */
323 static void addrcompl_add_entry( CompletionWindow *cw, gchar *address ) {
324         gchar *text[] = { NULL, NULL };
325
326         /* printf( "\t\tAdding :%s\n", address ); */
327         text[0] = address;
328         gtk_clist_append( GTK_CLIST(cw->clist), text );
329         cw->listCount++;
330
331         /* Resize window */
332         addrcompl_resize_window( cw );
333         gtk_grab_add( cw->window );
334
335         if( cw->listCount == 2 ) {
336                 /* Move off first row */
337                 gtk_clist_select_row( GTK_CLIST(cw->clist), 1, 0);
338         }
339 }
340
341 /**
342  * Completion idle function. This function is called by the main (UI) thread
343  * during UI idle time while an address search is in progress. Items from the
344  * display queue are processed and appended to the address list.
345  *
346  * \param data Target completion window to receive email addresses.
347  * \return <i>TRUE</i> to ensure that idle event do not get ignored.
348  */
349 static gboolean addrcompl_idle( gpointer data ) {
350         GList *node;
351         gchar *address;
352         CompletionWindow *cw;
353
354         /* Process all entries in display queue */
355         pthread_mutex_lock( & _completionMutex_ );
356         if( _displayQueue_ ) {
357                 cw = data;
358                 node = _displayQueue_;
359                 while( node ) {
360                         ItemEMail *email = node->data;
361                         /* printf( "email/address ::%s::\n", email->address ); */
362                         address = addrcompl_format_email( email );
363                         /* printf( "address ::: %s :::\n", address ); */
364                         addrcompl_add_entry( cw, address );
365                         g_free( address );
366                         node = g_list_next( node );
367                 }
368                 g_list_free( _displayQueue_ );
369                 _displayQueue_ = NULL;
370         }
371         pthread_mutex_unlock( & _completionMutex_ );
372
373         return TRUE;
374 }
375
376 /**
377  * Callback entry point. The background thread (if any) appends the address
378  * list to the display queue.
379  * \param queryID    Query ID of search request.
380  * \param listEMail  List of zero of more email objects that met search
381  *                   criteria.
382  * \param target     Target object to received data.
383  */
384 static gint addrcompl_callback(
385         gint queryID, GList *listEMail, gpointer target )
386 {
387         GList *node;
388         gchar *address;
389
390         pthread_mutex_lock( & _completionMutex_ );
391         if( queryID == _queryID_ ) {
392                 /* Append contents to end of display queue */
393                 node = listEMail;
394                 while( node ) {
395                         ItemEMail *email = node->data;
396                         if( target ) {
397                                 /*
398                                 printf( "\temail/address ::%s::\n", email->address );
399                                 */
400                                 _displayQueue_ = g_list_append( _displayQueue_, email );
401                         }
402                         node = g_list_next( node );
403                 }
404         }
405         pthread_mutex_unlock( & _completionMutex_ );
406 }
407
408 /**
409  * Start the search.
410  */
411 static void addrcompl_start_search( void ) {
412         gchar *searchTerm;
413
414         searchTerm = g_strdup( _compWindow_->searchTerm );
415
416         /* Clear out display queue */
417         pthread_mutex_lock( & _completionMutex_ );
418
419         g_list_free( _displayQueue_ );
420         _displayQueue_ = NULL;
421
422         pthread_mutex_unlock( & _completionMutex_ );
423
424         /* Setup the search */
425         _queryID_ = addressbook_setup_search(
426                 searchTerm, _compWindow_, addrcompl_callback );
427         g_free( searchTerm );
428
429         /* Sit back and wait until something happens */
430         _completionIdleID_ =
431                 gtk_idle_add( ( GtkFunction ) addrcompl_idle, _compWindow_ );
432         addressbook_start_search( _queryID_ );
433 }
434
435 /*
436  * address completion entry ui. the ui (completion list was inspired by galeon's
437  * auto completion list). remaining things powered by sylpheed's completion engine.
438  */
439
440 #define ENTRY_DATA_TAB_HOOK     "tab_hook"      /* used to lookup entry */
441
442 static gboolean address_completion_entry_key_pressed    (GtkEntry    *entry,
443                                                          GdkEventKey *ev,
444                                                          gpointer     data);
445 static gboolean address_completion_complete_address_in_entry
446                                                         (GtkEntry    *entry);
447
448 static void address_completion_create_completion_window (GtkEntry    *entry);
449
450 static void completion_window_select_row(GtkCList        *clist,
451                                          gint             row,
452                                          gint             col,
453                                          GdkEvent        *event,
454                                          CompletionWindow *compWin );
455
456 static gboolean completion_window_button_press
457                                         (GtkWidget       *widget,
458                                          GdkEventButton  *event,
459                                          CompletionWindow *compWin );
460
461 static gboolean completion_window_key_press
462                                         (GtkWidget       *widget,
463                                          GdkEventKey     *event,
464                                          CompletionWindow *compWin );
465
466
467 /**
468  * Advance selection to previous/next item in list.
469  * \param clist   List to process.
470  * \param forward Set to <i>TRUE</i> to select next or <i>FALSE</i> for
471  *                previous entry.
472  */
473 static void completion_window_advance_selection(GtkCList *clist, gboolean forward)
474 {
475         int row;
476
477         g_return_if_fail(clist != NULL);
478
479         if( clist->selection ) {
480                 row = GPOINTER_TO_INT(clist->selection->data);
481                 row = forward ? ( row + 1 ) : ( row - 1 );
482                 gtk_clist_freeze(clist);
483                 gtk_clist_select_row(clist, row, 0);
484                 gtk_clist_thaw(clist);
485         }
486 }
487
488 /**
489  * Apply the current selection in the list to the entry field. Focus is also
490  * moved to the next widget so that Tab key works correctly.
491  * \param clist List to process.
492  * \param entry Address entry field.
493  */
494 static void completion_window_apply_selection(GtkCList *clist, GtkEntry *entry)
495 {
496         gchar *address = NULL, *text = NULL;
497         gint   cursor_pos, row;
498         GtkWidget *parent;
499
500         g_return_if_fail(clist != NULL);
501         g_return_if_fail(entry != NULL);
502         g_return_if_fail(clist->selection != NULL);
503
504         /* First remove the idler */
505         if( _completionIdleID_ != 0 ) {
506                 gtk_idle_remove( _completionIdleID_ );
507                 _completionIdleID_ = 0;
508         }
509
510         /* Process selected item */
511         row = GPOINTER_TO_INT(clist->selection->data);
512
513         address = get_address_from_edit(entry, &cursor_pos);
514         g_free(address);
515         gtk_clist_get_text(clist, row, 0, &text);
516         replace_address_in_edit(entry, text, cursor_pos);
517
518         /* Move focus to next widget */
519         parent = GTK_WIDGET(entry)->parent;
520         if( parent ) {
521                 gtk_container_focus( GTK_CONTAINER(parent), GTK_DIR_TAB_FORWARD );
522         }
523
524 }
525
526 /**
527  * Listener that watches for tab or other keystroke in address entry field.
528  * \param entry Address entry field.
529  * \param ev    Event object.
530  * \param data  User data.
531  * \return <i>TRUE</i>.
532  */
533 static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
534                                                      GdkEventKey *ev,
535                                                      gpointer     data)
536 {
537         if (ev->keyval == GDK_Tab) {
538                 if (address_completion_complete_address_in_entry(entry)) {
539                         /* route a void character to the default handler */
540                         /* this is a dirty hack; we're actually changing a key
541                          * reported by the system. */
542                         ev->keyval = GDK_AudibleBell_Enable;
543                         ev->state &= ~GDK_SHIFT_MASK;
544                         gtk_signal_emit_stop_by_name(GTK_OBJECT(entry),
545                                                      "key_press_event");
546
547                         /* printf( "entry_key_pressed::create window\n" ); */
548                         address_completion_create_completion_window( entry );
549                         /* printf( "entry_key_pressed::create window...done\n" ); */
550                         addrcompl_start_search();
551                 }
552         }
553         return TRUE;
554 }
555
556 /**
557  * Initialize search term for address completion.
558  * \param entry Address entry field.
559  */
560 static gboolean address_completion_complete_address_in_entry( GtkEntry *entry )
561 {
562         gint cursor_pos;
563         gchar *searchTerm;
564
565         g_return_val_if_fail(entry != NULL, FALSE);
566
567         if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE;
568
569         /* get an address component from the cursor */
570         searchTerm = get_address_from_edit( entry, &cursor_pos );
571         if (! searchTerm ) return FALSE;
572
573         /* Clear any existing search */
574         if( _compWindow_->searchTerm ) {
575                 g_free( _compWindow_->searchTerm );
576         }
577
578         /* Replace with new search term */
579         _compWindow_->searchTerm = searchTerm;
580
581         return TRUE;
582 }
583
584 /**
585  * Create new address completion window for specified entry.
586  * \param entry_ Entry widget to associate with window.
587  */
588 static void address_completion_create_completion_window( GtkEntry *entry_ )
589 {
590         gint x, y, height, width, depth;
591         GtkWidget *scroll, *clist;
592         GtkRequisition r;
593         GtkWidget *window;
594         GtkWidget *entry = GTK_WIDGET(entry_);
595         gchar *searchTerm;
596
597         /* Create new window and list */
598         window = gtk_window_new(GTK_WINDOW_POPUP);
599         clist  = gtk_clist_new(1);
600
601         /* Destroy any existing window */
602         addrcompl_destroy_window( _compWindow_ );
603
604         /* Create new object */
605         _compWindow_->window = window;
606         _compWindow_->entry = entry;
607         _compWindow_->clist = clist;
608         _compWindow_->listCount = 0;
609
610         scroll = gtk_scrolled_window_new(NULL, NULL);
611         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
612                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
613         gtk_container_add(GTK_CONTAINER(window), scroll);
614         gtk_container_add(GTK_CONTAINER(scroll), clist);
615         gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
616
617         /* Use entry widget to create initial window */
618         gdk_window_get_geometry(entry->window, &x, &y, &width, &height, &depth);
619         gdk_window_get_deskrelative_origin (entry->window, &x, &y);
620         y += height;
621         gtk_widget_set_uposition(window, x, y);
622
623         /* Resize window to fit initial (empty) address list */
624         gtk_widget_size_request( clist, &r );
625         gtk_widget_set_usize( window, width, r.height );
626         gtk_widget_show_all( window );
627         gtk_widget_size_request( clist, &r );
628
629         /* Setup handlers */
630         gtk_signal_connect(GTK_OBJECT(clist), "select_row",
631                            GTK_SIGNAL_FUNC(completion_window_select_row),
632                            _compWindow_ );
633         gtk_signal_connect(GTK_OBJECT(window),
634                            "button-press-event",
635                            GTK_SIGNAL_FUNC(completion_window_button_press),
636                            _compWindow_ );
637         gtk_signal_connect(GTK_OBJECT(window),
638                            "key-press-event",
639                            GTK_SIGNAL_FUNC(completion_window_key_press),
640                            _compWindow_ );
641         gdk_pointer_grab(window->window, TRUE,
642                          GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
643                          GDK_BUTTON_RELEASE_MASK,
644                          NULL, NULL, GDK_CURRENT_TIME);
645         gtk_grab_add( window );
646
647         /* this gets rid of the irritating focus rectangle that doesn't
648          * follow the selection */
649         GTK_WIDGET_UNSET_FLAGS(clist, GTK_CAN_FOCUS);
650
651         /* Add first entry into address list */
652         searchTerm = g_strdup( _compWindow_->searchTerm );
653         addrcompl_add_entry( _compWindow_, searchTerm );
654         gtk_clist_select_row(GTK_CLIST(clist), 0, 0);
655
656         g_free( searchTerm );
657 }
658
659 /**
660  * Respond to select row event in clist object. selection sends completed
661  * address to entry. Note: event is NULL if selected by anything else than a
662  * mouse button.
663  * \param widget   Window object.
664  * \param event    Event.
665  * \param compWind Reference to completion window.
666  */
667 static void completion_window_select_row(GtkCList *clist, gint row, gint col,
668                                          GdkEvent *event,
669                                          CompletionWindow *compWin )
670 {
671         GtkEntry *entry;
672
673         g_return_if_fail(compWin != NULL);
674
675         entry = GTK_ENTRY(compWin->entry);
676         g_return_if_fail(entry != NULL);
677
678         /* Don't update unless user actually selects ! */
679         if (!event || event->type != GDK_BUTTON_RELEASE)
680                 return;
681
682         /* User selected address by releasing the mouse in drop-down list*/
683         completion_window_apply_selection( clist, entry );
684
685         addrcompl_destroy_window( _compWindow_ );
686 }
687
688 /**
689  * Respond to button press in completion window. Check if mouse click is
690  * anywhere outside the completion window. In that case the completion
691  * window is destroyed, and the original searchTerm is restored.
692  *
693  * \param widget   Window object.
694  * \param event    Event.
695  * \param compWin  Reference to completion window.
696  */
697 static gboolean completion_window_button_press(GtkWidget *widget,
698                                                GdkEventButton *event,
699                                                CompletionWindow *compWin )
700
701 {
702         GtkWidget *event_widget, *entry;
703         gchar *searchTerm;
704         gint cursor_pos;
705         gboolean restore = TRUE;
706
707         g_return_val_if_fail(compWin != NULL, FALSE);
708
709         entry = compWin->entry;
710         g_return_val_if_fail(entry != NULL, FALSE);
711
712         /* Test where mouse was clicked */
713         event_widget = gtk_get_event_widget((GdkEvent *)event);
714         if (event_widget != widget) {
715                 while (event_widget) {
716                         if (event_widget == widget)
717                                 return FALSE;
718                         else if (event_widget == entry) {
719                                 restore = FALSE;
720                                 break;
721                         }
722                         event_widget = event_widget->parent;
723                 }
724         }
725
726         if (restore) {
727                 /* Clicked outside of completion window - restore */
728                 searchTerm = _compWindow_->searchTerm;
729                 g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
730                 replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos);
731         }
732
733         addrcompl_destroy_window( _compWindow_ );
734
735         return TRUE;
736 }
737
738 /**
739  * Respond to key press in completion window.
740  * \param widget   Window object.
741  * \param event    Event.
742  * \param compWind Reference to completion window.
743  */
744 static gboolean completion_window_key_press(GtkWidget *widget,
745                                             GdkEventKey *event,
746                                             CompletionWindow *compWin )
747 {
748         GdkEventKey tmp_event;
749         GtkWidget *entry;
750         gchar *searchTerm;
751         gint cursor_pos;
752         GtkWidget *clist;
753
754         g_return_val_if_fail(compWin != NULL, FALSE);
755
756         entry = compWin->entry;
757         clist = compWin->clist;
758         g_return_val_if_fail(entry != NULL, FALSE);
759
760         /* allow keyboard navigation in the alternatives clist */
761         if (event->keyval == GDK_Up || event->keyval == GDK_Down ||
762             event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down) {
763                 completion_window_advance_selection
764                         (GTK_CLIST(clist),
765                          event->keyval == GDK_Down ||
766                          event->keyval == GDK_Page_Down ? TRUE : FALSE);
767                 return FALSE;
768         }               
769
770         /* also make tab / shift tab go to next previous completion entry. we're
771          * changing the key value */
772         if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) {
773                 event->keyval = (event->state & GDK_SHIFT_MASK)
774                         ? GDK_Up : GDK_Down;
775                 /* need to reset shift state if going up */
776                 if (event->state & GDK_SHIFT_MASK)
777                         event->state &= ~GDK_SHIFT_MASK;
778                 completion_window_advance_selection(GTK_CLIST(clist), 
779                         event->keyval == GDK_Down ? TRUE : FALSE);
780                 return FALSE;
781         }
782
783         /* look for presses that accept the selection */
784         if (event->keyval == GDK_Return || event->keyval == GDK_space) {
785                 /* User selected address with a key press */
786
787                 /* Display selected address in entry field */           
788                 completion_window_apply_selection(
789                         GTK_CLIST(clist), GTK_ENTRY(entry) );
790
791                 /* Discard the window */
792                 addrcompl_destroy_window( _compWindow_ );
793                 return FALSE;
794         }
795
796         /* key state keys should never be handled */
797         if (event->keyval == GDK_Shift_L
798                  || event->keyval == GDK_Shift_R
799                  || event->keyval == GDK_Control_L
800                  || event->keyval == GDK_Control_R
801                  || event->keyval == GDK_Caps_Lock
802                  || event->keyval == GDK_Shift_Lock
803                  || event->keyval == GDK_Meta_L
804                  || event->keyval == GDK_Meta_R
805                  || event->keyval == GDK_Alt_L
806                  || event->keyval == GDK_Alt_R) {
807                 return FALSE;
808         }
809
810         /* some other key, let's restore the searchTerm (orignal text) */
811         searchTerm = _compWindow_->searchTerm;
812         g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
813         replace_address_in_edit(GTK_ENTRY(entry), searchTerm, cursor_pos);
814
815         /* make sure anything we typed comes in the edit box */
816         tmp_event.type       = event->type;
817         tmp_event.window     = entry->window;
818         tmp_event.send_event = TRUE;
819         tmp_event.time       = event->time;
820         tmp_event.state      = event->state;
821         tmp_event.keyval     = event->keyval;
822         tmp_event.length     = event->length;
823         tmp_event.string     = event->string;
824         gtk_widget_event(entry, (GdkEvent *)&tmp_event);
825
826         /* and close the completion window */
827         addrcompl_destroy_window( _compWindow_ );
828
829         return TRUE;
830 }
831
832 /*
833  * ============================================================================
834  * Publically accessible functions.
835  * ============================================================================
836  */
837 /**
838  * Start address completion operation.
839  */
840 gint start_address_completion(void)
841 {
842         if( ! _compWindow_ ) {
843                 _compWindow_ = addrcompl_create_window();
844                 _queryID_ = 0;
845         }
846         addressbook_read_all();
847         return 0;
848 }
849
850 /**
851  * complete_address() - tries to complete an addres, and returns the
852  * number of addresses found. use get_complete_address() to get one.
853  * returns zero if no match was found, otherwise the number of addresses,
854  * with the original searchTerm at index 0.
855  * TODO: move out of here. This should be used for retrieving an address
856  * without completion.
857  */
858 guint complete_address(const gchar *str)
859 {
860         g_return_val_if_fail(str != NULL, 0);
861
862         /* Xstrdup_a(d, str, return 0); */
863
864         /* Attempt completion */
865         /* printf( "addrcompl: Start search...\n" ); */
866         /* addressbook_start_search( 0, str, NULL, address_completion_callback ); */
867         /* printf( "addrcompl: Start search...done\n" ); */
868         /* Existing code is below */
869
870         return 0;
871 }
872
873 /**
874  * Returns a complete address. TODO: remove
875  * get_complete_address() - returns a complete address. the returned
876  * string should be freed 
877  */
878 gchar *get_complete_address(gint index)
879 {
880         return NULL;
881 }
882
883 /**
884  * Terminate addess completion.
885  */
886 gint end_address_completion(void)
887 {
888         return 0;
889 }
890
891 /**
892  * Start address completion. Should be called when creating the main window
893  * containing address completion entries. This originally cleared the cache.
894  * Function no longer required?
895  */
896 void address_completion_start(GtkWidget *mainwindow)
897 {
898         start_address_completion();
899 }
900
901 /**
902  * Need unique data to make unregistering signal handler possible for the auto
903  * completed entry.
904  */
905 #define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
906
907 /**
908  * Register specified entry widget for address completion.
909  * \param entry Address entry field.
910  */
911 void address_completion_register_entry(GtkEntry *entry)
912 {
913         g_return_if_fail(entry != NULL);
914         g_return_if_fail(GTK_IS_ENTRY(entry));
915
916         /* add hooked property */
917         gtk_object_set_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
918
919         /* add keypress event */
920         gtk_signal_connect_full(GTK_OBJECT(entry), "key_press_event",
921                                 GTK_SIGNAL_FUNC(address_completion_entry_key_pressed),
922                                 NULL,
923                                 COMPLETION_UNIQUE_DATA,
924                                 NULL,
925                                 0,
926                                 0); /* magic */
927 }
928
929 /**
930  * Unregister specified entry widget from address completion operations.
931  * \param entry Address entry field.
932  */
933 void address_completion_unregister_entry(GtkEntry *entry)
934 {
935         GtkObject *entry_obj;
936
937         g_return_if_fail(entry != NULL);
938         g_return_if_fail(GTK_IS_ENTRY(entry));
939
940         entry_obj = gtk_object_get_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
941         g_return_if_fail(entry_obj);
942         g_return_if_fail(entry_obj == GTK_OBJECT(entry));
943
944         /* has the hooked property? */
945         gtk_object_set_data(GTK_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
946
947         /* remove the hook */
948         gtk_signal_disconnect_by_func(GTK_OBJECT(entry), 
949                 GTK_SIGNAL_FUNC(address_completion_entry_key_pressed),
950                 COMPLETION_UNIQUE_DATA);
951 }
952
953 /**
954  * End address completion. Should be called when main window with address
955  * completion entries terminates. NOTE: this function assumes that it is
956  * called upon destruction of the window.
957  */
958 void address_completion_end(GtkWidget *mainwindow)
959 {
960         /* if address_completion_end() is really called on closing the window,
961          * we don't need to unregister the set_focus_cb */
962         end_address_completion();
963 }
964
965 /**
966  * Setup completion object.
967  */
968 void addrcompl_initialize( void ) {
969         printf( "addrcompl_initialize...\n" );
970         if( ! _compWindow_ ) {
971                 _compWindow_ = addrcompl_create_window();
972         }
973         _queryID_ = 0;
974         _completionIdleID_ = 0;
975         printf( "addrcompl_initialize...done\n" );
976 }
977
978 /**
979  * Teardown completion object.
980  */
981 void addrcompl_teardown( void ) {
982         printf( "addrcompl_teardown...\n" );
983         addrcompl_free_window( _compWindow_ );
984         _compWindow_ = NULL;
985         if( _displayQueue_ ) {
986                 g_list_free( _displayQueue_ );
987         }
988         _displayQueue_ = NULL;
989         _completionIdleID_ = 0;
990         printf( "addrcompl_teardown...done\n" );
991 }
992
993 /*
994  * End of Source.
995  */
996