Statistics
| Revision:

root / src / addr_compl.c @ 1857

History | View | Annotate | Download (28.8 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 *
4
 * Copyright (C) 2000-2005 by Alfons Hoogervorst & The Sylpheed Claws 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., 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 "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/gtkclist.h>
34
#include <gtk/gtkscrolledwindow.h>
35
36
#include <string.h>
37
#include <ctype.h>
38
39
#include "xml.h"
40
#include "addr_compl.h"
41
#include "utils.h"
42
#include "addressbook.h"
43
#include "main.h"
44
45
/* How it works:
46
 *
47
 * The address book is read into memory. We set up an address list
48
 * containing all address book entries. Next we make the completion
49
 * list, which contains all the completable strings, and store a
50
 * reference to the address entry it belongs to.
51
 * After calling the g_completion_complete(), we get a reference
52
 * to a valid email address.  
53
 *
54
 * Completion is very simplified. We never complete on another prefix,
55
 * i.e. we neglect the next smallest possible prefix for the current
56
 * completion cache. This is simply done so we might break up the
57
 * addresses a little more (e.g. break up alfons@proteus.demon.nl into
58
 * something like alfons, proteus, demon, nl; and then completing on
59
 * any of those words).
60
 */ 
61
        
62
/* address_entry - structure which refers to the original address entry in the
63
 * address book 
64
 */
65
typedef struct
66
{
67
        gchar *name;
68
        gchar *address;
69
} address_entry;
70
71
/* completion_entry - structure used to complete addresses, with a reference
72
 * the the real address information.
73
 */
74
typedef struct
75
{
76
        gchar                *string; /* string to complete */
77
        address_entry        *ref;         /* address the string belongs to  */
78
} completion_entry;
79
80
/*******************************************************************************/
81
82
static gint            ref_count;                /* list ref count */
83
static GList            *completion_list;        /* list of strings to be checked */
84
static GList            *address_list;        /* address storage */
85
static GCompletion *completion;                /* completion object */
86
87
/* To allow for continuing completion we have to keep track of the state
88
 * using the following variables. No need to create a context object. */
89
90
static gint            completion_count;                /* nr of addresses incl. the prefix */
91
static gint            completion_next;                /* next prev address */
92
static GSList           *completion_addresses;        /* unique addresses found in the
93
                                                   completion cache. */
94
static gchar           *completion_prefix;                /* last prefix. (this is cached here
95
                                                 * because the prefix passed to g_completion
96
                                                 * is g_strdown()'ed */
97
98
/*******************************************************************************/
99
100
/* completion_func() - used by GTK to find the string data to be used for 
101
 * completion 
102
 */
103
static gchar *completion_func(gpointer data)
104
{
105
        g_return_val_if_fail(data != NULL, NULL);
106
107
        return ((completion_entry *)data)->string;
108
} 
109
110
static void init_all(void)
111
{
112
        completion = g_completion_new(completion_func);
113
        g_return_if_fail(completion != NULL);
114
}
115
116
static void free_all(void)
117
{
118
        GList *walk;
119
120
        walk = g_list_first(completion_list);
121
        for (; walk != NULL; walk = g_list_next(walk)) {
122
                completion_entry *ce = (completion_entry *) walk->data;
123
                g_free(ce->string);
124
                g_free(ce);
125
        }
126
        g_list_free(completion_list);
127
        completion_list = NULL;
128
129
        walk = address_list;
130
        for (; walk != NULL; walk = g_list_next(walk)) {
131
                address_entry *ae = (address_entry *) walk->data;
132
                g_free(ae->name);
133
                g_free(ae->address);
134
                g_free(ae);
135
        }
136
        g_list_free(address_list);
137
        address_list = NULL;
138
139
        g_completion_free(completion);
140
        completion = NULL;
141
}
142
143
static gint address_entry_find_func(gconstpointer a, gconstpointer b)
144
{
145
        const address_entry *ae1 = a;
146
        const address_entry *ae2 = b;
147
        gint val;
148
149
        if (!a || !b)
150
                return -1;
151
152
        val = strcmp(ae1->name, ae2->name);
153
        if (val != 0)
154
                return val;
155
        val = strcmp(ae1->address, ae2->address);
156
        if (val != 0)
157
                return val;
158
159
        return 0;
160
}
161
162
/* add_address() - adds address to the completion list. this function looks
163
 * complicated, but it's only allocation checks.
164
 */
165
static gint add_address(const gchar *name, const gchar *address, const gchar *nickname)
166
{
167
        address_entry    *ae;
168
        completion_entry *name_ce = NULL;
169
        completion_entry *nick_ce = NULL;
170
        completion_entry *addr_ce;
171
        GList            *found;
172
173
        if (!address) return -1;
174
175
        if (name && *name)
176
                name_ce = g_new0(completion_entry, 1);
177
        if (nickname && *nickname)
178
                nick_ce = g_new0(completion_entry, 1);
179
        addr_ce = g_new0(completion_entry, 1);
180
181
        ae = g_new0(address_entry, 1);
182
        ae->name    = g_strdup(name);
183
        ae->address = g_strdup(address);                
184
        if ((found = g_list_find_custom(address_list, ae,
185
                                        address_entry_find_func))) {
186
                g_free(ae->name);
187
                g_free(ae->address);
188
                g_free(ae);
189
                ae = (address_entry *)found->data;
190
        } else
191
                address_list = g_list_append(address_list, ae);
192
193
        /* GCompletion list is case sensitive */
194
        if (name_ce)
195
                name_ce->string = g_utf8_strdown(name, -1);
196
        if (nick_ce)
197
                nick_ce->string = g_utf8_strdown(nickname, -1);
198
        addr_ce->string = g_utf8_strdown(address, -1);
199
200
        if (name_ce)
201
                name_ce->ref = ae;
202
        if (nick_ce)
203
                nick_ce->ref = ae;
204
        addr_ce->ref = ae;
205
206
        if (name_ce)
207
                completion_list = g_list_append(completion_list, name_ce);
208
        if (nick_ce)
209
                completion_list = g_list_append(completion_list, nick_ce);
210
        completion_list = g_list_append(completion_list, addr_ce);
211
212
        return 0;
213
}
214
215
/* read_address_book()
216
 */ 
217
static void read_address_book(void) {        
218
        addressbook_load_completion( add_address );
219
}
220
221
/* start_address_completion() - returns the number of addresses 
222
 * that should be matched for completion.
223
 */
224
gint start_address_completion(void)
225
{
226
        clear_completion_cache();
227
        if (!ref_count) {
228
                init_all();
229
                /* open the address book */
230
                read_address_book();
231
                /* merge the completion entry list into g_completion */
232
                if (completion_list)
233
                        g_completion_add_items(completion, completion_list);
234
        }
235
        ref_count++;
236
        debug_print("start_address_completion ref count %d\n", ref_count);
237
238
        return g_list_length(completion_list);
239
}
240
241
/* get_address_from_edit() - returns a possible address (or a part)
242
 * from an entry box. To make life easier, we only look at the last valid address 
243
 * component; address completion only works at the last string component in
244
 * the entry box. 
245
 */ 
246
gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
247
{
248
        const gchar *edit_text, *p;
249
        gint cur_pos;
250
        gboolean in_quote = FALSE;
251
        gboolean in_bracket = FALSE;
252
        gchar *str;
253
254
        edit_text = gtk_entry_get_text(entry);
255
        if (edit_text == NULL) return NULL;
256
257
        cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
258
259
        /* scan for a separator. doesn't matter if walk points at null byte. */
260
        for (p = g_utf8_offset_to_pointer(edit_text, cur_pos);
261
             p > edit_text;
262
             p = g_utf8_prev_char(p)) {
263
                if (*p == '"')
264
                        in_quote ^= TRUE;
265
                else if (!in_quote) {
266
                        if (!in_bracket && *p == ',')
267
                                break;
268
                        else if (*p == '>')
269
                                in_bracket = TRUE;
270
                        else if (*p == '<')
271
                                in_bracket = FALSE;
272
                }
273
        }
274
275
        /* have something valid */
276
        if (g_utf8_strlen(p, -1) == 0)
277
                return NULL;
278
279
#define IS_VALID_CHAR(x) \
280
        (g_ascii_isalnum(x) || (x) == '"' || (x) == '<' || ((guchar)(x) > 0x7f))
281
282
        /* now scan back until we hit a valid character */
283
        for (; *p && !IS_VALID_CHAR(*p); p = g_utf8_next_char(p))
284
                ;
285
286
#undef IS_VALID_CHAR
287
288
        if (g_utf8_strlen(p, -1) == 0)
289
                return NULL;
290
291
        if (start_pos) *start_pos = g_utf8_pointer_to_offset(edit_text, p);
292
293
        str = g_strdup(p);
294
295
        return str;
296
} 
297
298
/* replace_address_in_edit() - replaces an incompleted address with a completed one.
299
 */
300
void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
301
                             gint start_pos)
302
{
303
        if (!newtext) return;
304
305
        gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1);
306
        gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext),
307
                                 &start_pos);
308
        gtk_editable_set_position(GTK_EDITABLE(entry), -1);
309
}
310
311
#if 0
312
static gint insert_address_func(gconstpointer a, gconstpointer b)
313
{
314
        const address_entry *ae1 = a;
315
        const address_entry *ae2 = b;
316
        gchar *s1, *s2;
317
        gint val;
318
319
        if (!a || !b)
320
                return -1;
321
322
        s1 = g_utf8_casefold(ae1->address, -1);
323
        s2 = g_utf8_casefold(ae2->address, -1);
324
        val = g_utf8_collate(s1, s2);
325
        g_free(s2);
326
        g_free(s1);
327
        if (val != 0)
328
                return val;
329
        s1 = g_utf8_casefold(ae1->name, -1);
330
        s2 = g_utf8_casefold(ae2->name, -1);
331
        val = g_utf8_collate(s1, s2);
332
        g_free(s2);
333
        g_free(s1);
334
        if (val != 0)
335
                return val;
336
337
        return 0;
338
}
339
#endif
340
341
/* complete_address() - tries to complete an addres, and returns the
342
 * number of addresses found. use get_complete_address() to get one.
343
 * returns zero if no match was found, otherwise the number of addresses,
344
 * with the original prefix at index 0. 
345
 */
346
guint complete_address(const gchar *str)
347
{
348
        GList *result;
349
        gchar *d;
350
        guint  count, cpl;
351
        completion_entry *ce;
352
353
        g_return_val_if_fail(str != NULL, 0);
354
355
        clear_completion_cache();
356
        completion_prefix = g_strdup(str);
357
358
        /* g_completion is case sensitive */
359
        d = g_utf8_strdown(str, -1);
360
        result = g_completion_complete(completion, d, NULL);
361
362
        count = g_list_length(result);
363
        if (count) {
364
                /* create list with unique addresses  */
365
                for (cpl = 0, result = g_list_first(result);
366
                     result != NULL;
367
                     result = g_list_next(result)) {
368
                        ce = (completion_entry *)(result->data);
369
                        if (NULL == g_slist_find(completion_addresses,
370
                                                 ce->ref)) {
371
                                cpl++;
372
                                completion_addresses =
373
                                        g_slist_append(completion_addresses,
374
                                                       ce->ref);
375
#if 0
376
                                        g_slist_insert_sorted
377
                                                (completion_addresses, ce->ref,
378
                                                 insert_address_func);
379
#endif
380
                        }
381
                }
382
                count = cpl + 1;        /* index 0 is the original prefix */
383
                completion_next = 1;        /* we start at the first completed one */
384
        } else {
385
                g_free(completion_prefix);
386
                completion_prefix = NULL;
387
        }
388
389
        completion_count = count;
390
391
        g_free(d);
392
393
        return count;
394
}
395
396
/* get_complete_address() - returns a complete address. the returned
397
 * string should be freed 
398
 */
399
gchar *get_complete_address(gint index)
400
{
401
        const address_entry *p;
402
        gchar *address = NULL;
403
404
        if (index < completion_count) {
405
                if (index == 0)
406
                        address = g_strdup(completion_prefix);
407
                else {
408
                        /* get something from the unique addresses */
409
                        p = (address_entry *)g_slist_nth_data
410
                                (completion_addresses, index - 1);
411
                        if (p != NULL) {
412
                                if (!p->name || p->name[0] == '\0')
413
                                        address = g_strdup_printf(p->address);
414
                                else if (p->name[0] != '"' &&
415
                                         strpbrk(p->name, ",.[]<>") != NULL)
416
                                        address = g_strdup_printf
417
                                                ("\"%s\" <%s>", p->name, p->address);
418
                                else
419
                                        address = g_strdup_printf
420
                                                ("%s <%s>", p->name, p->address);
421
                        }
422
                }
423
        }
424
425
        return address;
426
}
427
428
gchar *get_next_complete_address(void)
429
{
430
        if (is_completion_pending()) {
431
                gchar *res;
432
433
                res = get_complete_address(completion_next);
434
                completion_next += 1;
435
                if (completion_next >= completion_count)
436
                        completion_next = 0;
437
438
                return res;
439
        } else
440
                return NULL;
441
}
442
443
gchar *get_prev_complete_address(void)
444
{
445
        if (is_completion_pending()) {
446
                int n = completion_next - 2;
447
448
                /* real previous */
449
                n = (n + (completion_count * 5)) % completion_count;
450
451
                /* real next */
452
                completion_next = n + 1;
453
                if (completion_next >=  completion_count)
454
                        completion_next = 0;
455
                return get_complete_address(n);
456
        } else
457
                return NULL;
458
}
459
460
guint get_completion_count(void)
461
{
462
        if (is_completion_pending())
463
                return completion_count;
464
        else
465
                return 0;
466
}
467
468
/* should clear up anything after complete_address() */
469
void clear_completion_cache(void)
470
{
471
        if (is_completion_pending()) {
472
                if (completion_prefix)
473
                        g_free(completion_prefix);
474
475
                if (completion_addresses) {
476
                        g_slist_free(completion_addresses);
477
                        completion_addresses = NULL;
478
                }
479
480
                completion_count = completion_next = 0;
481
        }
482
}
483
484
gboolean is_completion_pending(void)
485
{
486
        /* check if completion pending, i.e. we might satisfy a request for the next
487
         * or previous address */
488
         return completion_count;
489
}
490
491
/* invalidate_address_completion() - should be called if address book
492
 * changed; 
493
 */
494
gint invalidate_address_completion(void)
495
{
496
        if (ref_count) {
497
                /* simply the same as start_address_completion() */
498
                debug_print("Invalidation request for address completion\n");
499
                free_all();
500
                init_all();
501
                read_address_book();
502
                if (completion_list)
503
                        g_completion_add_items(completion, completion_list);
504
                clear_completion_cache();
505
        }
506
507
        return g_list_length(completion_list);
508
}
509
510
gint end_address_completion(void)
511
{
512
        clear_completion_cache();
513
514
        if (0 == --ref_count)
515
                free_all();
516
517
        debug_print("end_address_completion ref count %d\n", ref_count);
518
519
        return ref_count; 
520
}
521
522
523
/* address completion entry ui. the ui (completion list was inspired by galeon's
524
 * auto completion list). remaining things powered by sylpheed's completion engine.
525
 */
526
527
#define ENTRY_DATA_TAB_HOOK        "tab_hook"                        /* used to lookup entry */
528
#define WINDOW_DATA_COMPL_ENTRY        "compl_entry"        /* used to store entry for compl. window */
529
#define WINDOW_DATA_COMPL_CLIST "compl_clist"        /* used to store clist for compl. window */
530
531
static void address_completion_mainwindow_set_focus        (GtkWindow   *window,
532
                                                         GtkWidget   *widget,
533
                                                         gpointer     data);
534
static gboolean address_completion_entry_key_pressed        (GtkEntry    *entry,
535
                                                         GdkEventKey *ev,
536
                                                         gpointer     data);
537
static gboolean address_completion_complete_address_in_entry
538
                                                        (GtkEntry    *entry,
539
                                                         gboolean     next);
540
static void address_completion_create_completion_window        (GtkEntry    *entry);
541
542
static void completion_window_select_row(GtkCList         *clist,
543
                                         gint                  row,
544
                                         gint                  col,
545
                                         GdkEvent         *event,
546
                                         GtkWidget        **completion_window);
547
static gboolean completion_window_button_press
548
                                        (GtkWidget         *widget,
549
                                         GdkEventButton  *event,
550
                                         GtkWidget        **completion_window);
551
static gboolean completion_window_key_press
552
                                        (GtkWidget         *widget,
553
                                         GdkEventKey         *event,
554
                                         GtkWidget        **completion_window);
555
556
557
static void completion_window_advance_to_row(GtkCList *clist, gint row)
558
{
559
        g_return_if_fail(row < completion_count);
560
        gtk_clist_select_row(clist, row, 0);
561
}
562
563
static void completion_window_advance_selection(GtkCList *clist, gboolean forward)
564
{
565
        int row;
566
567
        g_return_if_fail(clist != NULL);
568
        g_return_if_fail(clist->selection != NULL);
569
570
        row = GPOINTER_TO_INT(clist->selection->data);
571
572
        row = forward ? (row + 1) % completion_count :
573
                        (row - 1) < 0 ? completion_count - 1 : row - 1;
574
575
        gtk_clist_freeze(clist);
576
        completion_window_advance_to_row(clist, row);                                        
577
        gtk_clist_thaw(clist);
578
}
579
580
#if 0
581
/* completion_window_accept_selection() - accepts the current selection in the
582
 * clist, and destroys the window */
583
static void completion_window_accept_selection(GtkWidget **window,
584
                                               GtkCList *clist,
585
                                               GtkEntry *entry)
586
{
587
        gchar *address = NULL, *text = NULL;
588
        gint   cursor_pos, row;
589
590
        g_return_if_fail(window != NULL);
591
        g_return_if_fail(*window != NULL);
592
        g_return_if_fail(clist != NULL);
593
        g_return_if_fail(entry != NULL);
594
        g_return_if_fail(clist->selection != NULL);
595
596
        /* FIXME: I believe it's acceptable to access the selection member directly  */
597
        row = GPOINTER_TO_INT(clist->selection->data);
598
599
        /* we just need the cursor position */
600
        address = get_address_from_edit(entry, &cursor_pos);
601
        g_free(address);
602
        gtk_clist_get_text(clist, row, 0, &text);
603
        replace_address_in_edit(entry, text, cursor_pos);
604
605
        clear_completion_cache();
606
        gtk_widget_destroy(*window);
607
        *window = NULL;
608
}
609
#endif
610
611
/* completion_window_apply_selection() - apply the current selection in the
612
 * clist */
613
static void completion_window_apply_selection(GtkCList *clist, GtkEntry *entry)
614
{
615
        gchar *address = NULL, *text = NULL;
616
        gint   cursor_pos, row;
617
618
        g_return_if_fail(clist != NULL);
619
        g_return_if_fail(entry != NULL);
620
        g_return_if_fail(clist->selection != NULL);
621
622
        row = GPOINTER_TO_INT(clist->selection->data);
623
624
        address = get_address_from_edit(entry, &cursor_pos);
625
        g_free(address);
626
        gtk_clist_get_text(clist, row, 0, &text);
627
        replace_address_in_edit(entry, text, cursor_pos);
628
}
629
630
static void completion_window_apply_selection_address_only(GtkCList *clist, GtkEntry *entry)
631
{
632
        gchar *address = NULL;
633
        address_entry *ae;
634
        gint   cursor_pos, row;
635
636
        g_return_if_fail(clist != NULL);
637
        g_return_if_fail(entry != NULL);
638
        g_return_if_fail(clist->selection != NULL);
639
640
        row = GPOINTER_TO_INT(clist->selection->data);
641
642
        ae = (address_entry *)g_slist_nth_data(completion_addresses, row - 1);
643
        if (ae && ae->address) {
644
                address = get_address_from_edit(entry, &cursor_pos);
645
                g_free(address);
646
                replace_address_in_edit(entry, ae->address, cursor_pos);
647
        }
648
}
649
650
/* should be called when creating the main window containing address
651
 * completion entries */
652
void address_completion_start(GtkWidget *mainwindow)
653
{
654
        start_address_completion();
655
656
        /* register focus change hook */
657
        g_signal_connect(G_OBJECT(mainwindow), "set_focus",
658
                         G_CALLBACK(address_completion_mainwindow_set_focus),
659
                         mainwindow);
660
}
661
662
/* Need unique data to make unregistering signal handler possible for the auto
663
 * completed entry */
664
#define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
665
666
void address_completion_register_entry(GtkEntry *entry)
667
{
668
        g_return_if_fail(entry != NULL);
669
        g_return_if_fail(GTK_IS_ENTRY(entry));
670
671
        /* add hooked property */
672
        g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
673
674
        /* add keypress event */
675
        g_signal_connect_closure
676
                (G_OBJECT(entry), "key_press_event",
677
                 g_cclosure_new
678
                        (G_CALLBACK(address_completion_entry_key_pressed),
679
                         COMPLETION_UNIQUE_DATA, NULL),
680
                 FALSE);
681
}
682
683
void address_completion_unregister_entry(GtkEntry *entry)
684
{
685
        GObject *entry_obj;
686
687
        g_return_if_fail(entry != NULL);
688
        g_return_if_fail(GTK_IS_ENTRY(entry));
689
690
        entry_obj = g_object_get_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
691
        g_return_if_fail(entry_obj);
692
        g_return_if_fail(entry_obj == G_OBJECT(entry));
693
694
        /* has the hooked property? */
695
        g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
696
697
        /* remove the hook */
698
        g_signal_handlers_disconnect_by_func
699
                (G_OBJECT(entry), 
700
                 G_CALLBACK(address_completion_entry_key_pressed),
701
                 COMPLETION_UNIQUE_DATA);
702
}
703
704
/* should be called when main window with address completion entries
705
 * terminates.
706
 * NOTE: this function assumes that it is called upon destruction of
707
 * the window */
708
void address_completion_end(GtkWidget *mainwindow)
709
{
710
        /* if address_completion_end() is really called on closing the window,
711
         * we don't need to unregister the set_focus_cb */
712
        end_address_completion();
713
}
714
715
/* if focus changes to another entry, then clear completion cache */
716
static void address_completion_mainwindow_set_focus(GtkWindow *window,
717
                                                    GtkWidget *widget,
718
                                                    gpointer   data)
719
{
720
        if (widget && GTK_IS_ENTRY(widget) &&
721
            g_object_get_data(G_OBJECT(widget), ENTRY_DATA_TAB_HOOK)) 
722
                clear_completion_cache();
723
}
724
725
/* watch for tabs in one of the address entries. if no tab then clear the
726
 * completion cache */
727
static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
728
                                                     GdkEventKey *ev,
729
                                                     gpointer     data)
730
{
731
        if (ev->keyval == GDK_Tab) {
732
                if (address_completion_complete_address_in_entry(entry, TRUE)) {
733
                        address_completion_create_completion_window(entry);
734
                        /* route a void character to the default handler */
735
                        /* this is a dirty hack; we're actually changing a key
736
                         * reported by the system. */
737
                        ev->keyval = GDK_AudibleBell_Enable;
738
                        ev->state &= ~GDK_SHIFT_MASK;
739
                        return TRUE;
740
                }
741
        } else if (ev->keyval == GDK_Shift_L
742
                || ev->keyval == GDK_Shift_R
743
                || ev->keyval == GDK_Control_L
744
                || ev->keyval == GDK_Control_R
745
                || ev->keyval == GDK_Caps_Lock
746
                || ev->keyval == GDK_Shift_Lock
747
                || ev->keyval == GDK_Meta_L
748
                || ev->keyval == GDK_Meta_R
749
                || ev->keyval == GDK_Alt_L
750
                || ev->keyval == GDK_Alt_R) {
751
                /* these buttons should not clear the cache... */
752
        } else
753
                clear_completion_cache();
754
755
        return FALSE;
756
}
757
758
/* initialize the completion cache and put first completed string
759
 * in entry. this function used to do back cycling but this is not
760
 * currently used. since the address completion behaviour has been
761
 * changed regularly, we keep the feature in case someone changes
762
 * his / her mind again. :) */
763
static gboolean address_completion_complete_address_in_entry(GtkEntry *entry,
764
                                                             gboolean  next)
765
{
766
        gint ncount, cursor_pos;
767
        gchar *address, *new = NULL;
768
        gboolean completed = FALSE;
769
770
        g_return_val_if_fail(entry != NULL, FALSE);
771
772
        if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE;
773
774
        /* get an address component from the cursor */
775
        address = get_address_from_edit(entry, &cursor_pos);
776
        if (!address) return FALSE;
777
778
        /* still something in the cache */
779
        if (is_completion_pending()) {
780
                new = next ? get_next_complete_address() :
781
                        get_prev_complete_address();
782
        } else {
783
                if (0 < (ncount = complete_address(address)))
784
                        new = get_next_complete_address();
785
        }
786
787
        if (new) {
788
                /* prevent "change" signal */
789
                /* replace_address_in_edit(entry, new, cursor_pos); */
790
                g_free(new);
791
                completed = TRUE;
792
        }
793
794
        g_free(address);
795
796
        return completed;
797
}
798
799
static void address_completion_create_completion_window(GtkEntry *entry_)
800
{
801
        static GtkWidget *completion_window;
802
        gint x, y, height, width, depth;
803
        GtkWidget *scroll, *clist;
804
        GtkRequisition r;
805
        guint count = 0;
806
        GtkWidget *entry = GTK_WIDGET(entry_);
807
808
        if (completion_window) {
809
                gtk_widget_destroy(completion_window);
810
                completion_window = NULL;
811
        }
812
813
        scroll = gtk_scrolled_window_new(NULL, NULL);
814
        clist  = gtk_clist_new(1);
815
        gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
816
        
817
        completion_window = gtk_window_new(GTK_WINDOW_POPUP);
818
819
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
820
                                       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
821
        gtk_container_add(GTK_CONTAINER(completion_window), scroll);
822
        gtk_container_add(GTK_CONTAINER(scroll), clist);
823
824
        /* set the unique data so we can always get back the entry and
825
         * clist window to which this completion window has been attached */
826
        g_object_set_data(G_OBJECT(completion_window),
827
                          WINDOW_DATA_COMPL_ENTRY, entry_);
828
        g_object_set_data(G_OBJECT(completion_window),
829
                          WINDOW_DATA_COMPL_CLIST, clist);
830
831
        g_signal_connect(G_OBJECT(clist), "select_row",
832
                         G_CALLBACK(completion_window_select_row),
833
                         &completion_window);
834
835
        for (count = 0; count < get_completion_count(); count++) {
836
                gchar *text[] = {NULL, NULL};
837
838
                text[0] = get_complete_address(count);
839
                gtk_clist_append(GTK_CLIST(clist), text);
840
                g_free(text[0]);
841
        }
842
843
        gdk_window_get_geometry(entry->window, &x, &y, &width, &height, &depth);
844
        gdk_window_get_deskrelative_origin (entry->window, &x, &y);
845
        y += height;
846
        gtk_window_move(GTK_WINDOW(completion_window), x, y);
847
848
        gtk_widget_size_request(clist, &r);
849
        gtk_widget_set_size_request(completion_window, width, r.height);
850
        gtk_widget_show_all(completion_window);
851
        gtk_widget_size_request(clist, &r);
852
853
        if ((y + r.height) > gdk_screen_height()) {
854
                gtk_window_set_policy(GTK_WINDOW(completion_window),
855
                                      TRUE, FALSE, FALSE);
856
                gtk_widget_set_size_request(completion_window, width,
857
                                            gdk_screen_height () - y);
858
        }
859
860
        g_signal_connect(G_OBJECT(completion_window),
861
                         "button-press-event",
862
                         G_CALLBACK(completion_window_button_press),
863
                         &completion_window);
864
        g_signal_connect(G_OBJECT(completion_window),
865
                         "key-press-event",
866
                         G_CALLBACK(completion_window_key_press),
867
                         &completion_window);
868
        gdk_pointer_grab(completion_window->window, TRUE,
869
                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
870
                         GDK_BUTTON_RELEASE_MASK,
871
                         NULL, NULL, GDK_CURRENT_TIME);
872
        gtk_grab_add(completion_window);
873
874
        /* this gets rid of the irritating focus rectangle that doesn't
875
         * follow the selection */
876
        GTK_WIDGET_UNSET_FLAGS(clist, GTK_CAN_FOCUS);
877
        gtk_clist_select_row(GTK_CLIST(clist), 1, 0);
878
}
879
880
881
/* row selection sends completed address to entry.
882
 * note: event is NULL if selected by anything else than a mouse button. */
883
static void completion_window_select_row(GtkCList *clist, gint row, gint col,
884
                                         GdkEvent *event,
885
                                         GtkWidget **completion_window)
886
{
887
        GtkEntry *entry;
888
889
        g_return_if_fail(completion_window != NULL);
890
        g_return_if_fail(*completion_window != NULL);
891
892
        entry = GTK_ENTRY(g_object_get_data(G_OBJECT(*completion_window),
893
                                            WINDOW_DATA_COMPL_ENTRY));
894
        g_return_if_fail(entry != NULL);
895
896
        completion_window_apply_selection(clist, entry);
897
898
        if (!event || event->type != GDK_BUTTON_RELEASE)
899
                return;
900
901
        clear_completion_cache();
902
        gtk_widget_destroy(*completion_window);
903
        *completion_window = NULL;
904
}
905
906
/* completion_window_button_press() - check is mouse click is anywhere
907
 * else (not in the completion window). in that case the completion
908
 * window is destroyed, and the original prefix is restored */
909
static gboolean completion_window_button_press(GtkWidget *widget,
910
                                               GdkEventButton *event,
911
                                               GtkWidget **completion_window)
912
{
913
        GtkWidget *event_widget, *entry;
914
        gchar *prefix;
915
        gint cursor_pos;
916
        gboolean restore = TRUE;
917
918
        g_return_val_if_fail(completion_window != NULL, FALSE);
919
        g_return_val_if_fail(*completion_window != NULL, FALSE);
920
921
        entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*completion_window),
922
                                             WINDOW_DATA_COMPL_ENTRY));
923
        g_return_val_if_fail(entry != NULL, FALSE);
924
925
        event_widget = gtk_get_event_widget((GdkEvent *)event);
926
        if (event_widget != widget) {
927
                while (event_widget) {
928
                        if (event_widget == widget)
929
                                return FALSE;
930
                        else if (event_widget == entry) {
931
                                restore = FALSE;
932
                                break;
933
                        }
934
                    event_widget = event_widget->parent;
935
                }
936
        }
937
938
        if (restore) {
939
                prefix = get_complete_address(0);
940
                g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
941
                replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
942
                g_free(prefix);
943
        }
944
945
        clear_completion_cache();
946
        gtk_widget_destroy(*completion_window);
947
        *completion_window = NULL;
948
949
        return TRUE;
950
}
951
952
static gboolean completion_window_key_press(GtkWidget *widget,
953
                                            GdkEventKey *event,
954
                                            GtkWidget **completion_window)
955
{
956
        GdkEventKey tmp_event;
957
        GtkWidget *entry;
958
        gchar *prefix;
959
        gint cursor_pos;
960
        GtkWidget *clist;
961
962
        g_return_val_if_fail(completion_window != NULL, FALSE);
963
        g_return_val_if_fail(*completion_window != NULL, FALSE);
964
965
        entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*completion_window),
966
                                             WINDOW_DATA_COMPL_ENTRY));
967
        clist = GTK_WIDGET(g_object_get_data(G_OBJECT(*completion_window),
968
                                             WINDOW_DATA_COMPL_CLIST));
969
        g_return_val_if_fail(entry != NULL, FALSE);
970
971
        /* allow keyboard navigation in the alternatives clist */
972
        if (event->keyval == GDK_Up || event->keyval == GDK_Down ||
973
            event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down) {
974
                completion_window_advance_selection
975
                        (GTK_CLIST(clist),
976
                         event->keyval == GDK_Down ||
977
                         event->keyval == GDK_Page_Down ? TRUE : FALSE);
978
                return FALSE;
979
        }                
980
981
        /* also make tab / shift tab go to next previous completion entry. we're
982
         * changing the key value */
983
        if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) {
984
                event->keyval = (event->state & GDK_SHIFT_MASK)
985
                        ? GDK_Up : GDK_Down;
986
                /* need to reset shift state if going up */
987
                if (event->state & GDK_SHIFT_MASK)
988
                        event->state &= ~GDK_SHIFT_MASK;
989
                completion_window_advance_selection(GTK_CLIST(clist), 
990
                        event->keyval == GDK_Down ? TRUE : FALSE);
991
                return FALSE;
992
        }
993
994
        /* look for presses that accept the selection */
995
        if (event->keyval == GDK_Return || event->keyval == GDK_space) {
996
                /* insert address only if shift or control is pressed */
997
                if (event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
998
                        completion_window_apply_selection_address_only
999
                                (GTK_CLIST(clist), GTK_ENTRY(entry));
1000
                }
1001
                clear_completion_cache();
1002
                gtk_widget_destroy(*completion_window);
1003
                *completion_window = NULL;
1004
                return FALSE;
1005
        }
1006
1007
        /* key state keys should never be handled */
1008
        if (event->keyval == GDK_Shift_L
1009
                 || event->keyval == GDK_Shift_R
1010
                 || event->keyval == GDK_Control_L
1011
                 || event->keyval == GDK_Control_R
1012
                 || event->keyval == GDK_Caps_Lock
1013
                 || event->keyval == GDK_Shift_Lock
1014
                 || event->keyval == GDK_Meta_L
1015
                 || event->keyval == GDK_Meta_R
1016
                 || event->keyval == GDK_Alt_L
1017
                 || event->keyval == GDK_Alt_R) {
1018
                return FALSE;
1019
        }
1020
1021
        /* other key, let's restore the prefix (orignal text) */
1022
        prefix = get_complete_address(0);
1023
        g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1024
        replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
1025
        g_free(prefix);
1026
        clear_completion_cache();
1027
1028
        /* make sure anything we typed comes in the edit box */
1029
        if (event->length > 0 && event->keyval != GDK_Escape) {
1030
                tmp_event.type       = event->type;
1031
                tmp_event.window     = entry->window;
1032
                tmp_event.send_event = TRUE;
1033
                tmp_event.time       = event->time;
1034
                tmp_event.state      = event->state;
1035
                tmp_event.keyval     = event->keyval;
1036
                tmp_event.length     = event->length;
1037
                tmp_event.string     = event->string;
1038
                gtk_widget_event(entry, (GdkEvent *)&tmp_event);
1039
        }
1040
1041
        /* and close the completion window */
1042
        gtk_widget_destroy(*completion_window);
1043
        *completion_window = NULL;
1044
1045
        return TRUE;
1046
}